build, then a small server to serve the dist/ folder.
Set up
serve is what runs in production. Vite is for building.
In package.json, set a start script that boots serve against the build output:
package.json
-s flag tells serve to fall back to index.html for unknown routes (so client-side routing works). The -l ${PORT:-3000} makes it respect Nubo’s port.
Test locally:
Deploy
Push to GitHub, then in the Nubo dashboard:Client-side routing (React Router)
serve -s already redirects unknown paths to index.html. If you use a different static server, make sure it does the same. Otherwise hitting /dashboard directly returns 404.
Things that trip people up
- No
startscript. Withoutnpm start, Nubo triesnode index.jswhich doesn’t exist in a Vite project. Definestartexplicitly. vite previewinstart.vite previewis for local checking only; it’s not a production server. Useserveor another static server.- Hardcoded
serve -l 3000. Use${PORT:-3000}so the Frame’s port setting actually applies.
Environment variables
Going truly static
If you don’t need a Node container, build locally and deploy with the Static pack instead. The Static pack serves on port 80, so set the Frame port to 80. It also looks forindex.html at the repo root or in public/, so committing dist/ on its own isn’t enough: rename dist/ to public/ (giving you public/index.html) or move the built files to the repo root, then remove package.json. Nubo will then ship the files behind nginx with no Node runtime at all. Useful for cheap landing pages; not useful when you want to redeploy from a git push.
