> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withnubo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React (Vite)

> Ship a Vite-powered React app to Nubo

A React app built with Vite is just a Node.js project that produces static files. Nubo runs your `build`, then a small server to serve the `dist/` folder.

## Set up

```bash theme={null}
npm create vite@latest my-app -- --template react
cd my-app
npm install
npm install serve
```

`serve` is what runs in production. Vite is for building.

In `package.json`, set a `start` script that boots `serve` against the build output:

```json package.json theme={null}
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "start": "serve -s dist -l ${PORT:-3000}"
  }
}
```

The `-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:

```bash theme={null}
npm run build
PORT=3000 npm start
# open http://localhost:3000
```

## Deploy

Push to GitHub, then in the Nubo dashboard:

<Steps>
  <Step title="New Frame">
    Open your Project and Space, hit **+ Frame**.
  </Step>

  <Step title="Pick the repo and branch">
    Connect your GitHub repo. Choose the branch Nubo should watch.
  </Step>

  <Step title="Confirm the port">
    Default 3000. Set it to whatever your `start` script listens on.
  </Step>

  <Step title="Deploy">
    Hit deploy. Nubo runs `npm ci`, `npm run build`, then `npm start`. (`npm ci` needs a committed `package-lock.json`.)
  </Step>
</Steps>

## 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 `start` script.** Without `npm start`, Nubo tries `node index.js` which doesn't exist in a Vite project. Define `start` explicitly.
* **`vite preview` in `start`.** `vite preview` is for local checking only; it's not a production server. Use `serve` or another static server.
* **Hardcoded `serve -l 3000`.** Use `${PORT:-3000}` so the Frame's port setting actually applies.

## Environment variables

<Warning>
  Vite bakes `VITE_`-prefixed variables into the bundle at **build** time, but Nubo injects Project and Frame variables only at **runtime**. A `VITE_` variable you set in the dashboard never reaches the built bundle: the build already finished before it exists. Either bake the value in at build time, or fetch config from your own runtime endpoint after the app loads. See [Environment variables](/environment-variables) for details.
</Warning>

## Going truly static

If you don't need a Node container, build locally and deploy with the [Static pack](/raypacks/packs/static) instead. The Static pack serves on port 80, so set the Frame port to 80. It also looks for `index.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.

## How the build works

Uses the [Node.js pack](/raypacks/packs/node) of [Raypacks](/raypacks/overview). Same flow as any Node app: install, build, start.
