Skip to content

Where it runs

A host is a Nitro preset, not a server you write.

Updated View as Markdown

There used to be a page here for each runtime, and a server file generated to match. There is no server file now, and no choice about it either: Nitro builds the server around the route tree, and choosing where an app runs is choosing a preset.

vite.config.tsts
import { defineConfig } from 'vite';
import { nitro } from 'nitro/vite';
import react from '@vitejs/plugin-react';
import { rscKit } from '@rsc-kit/core/vite';

export default defineConfig({
  plugins: [
    nitro({ preset: 'bun', serveStatic: 'inline' }),
    rscKit(),
    react(),
  ],
});

bun create rsc-kit@latest my-app writes that for you. Changing where it deploys is changing the one string.

The presets

rsc-kit asks about three, because those are the ones it can check for you. Nitro carries many more and they need nothing from this package — a preset is a string, not an integration.

--host preset what you get
bun bun .output/server/index.mjs, and bun run compile for a single binary
node node .output/server/index.mjs, run with node
worker cloudflare_module a Worker, plus the wrangler.json and _headers Nitro generates

Everything else — Vercel, Netlify, Azure, Deno, AWS Amplify — is the same change:

nitro({ preset: 'vercel', serveStatic: 'inline' })

Running it

npm run dev      # vite is the renderer; nothing is prebuilt
npm run build    # writes .output/
npm run start    # runs .output/server/index.mjs

A Worker has no start, because it is deployed rather than started:

npm run preview  # wrangler dev, on workerd
npm run deploy   # nitro deploy --prebuilt

Compiling to a single binary

Bun only, and the whole application ends up inside one file — engine, route tree and assets:

npm run compile   # builds, then bun build --compile
./dist/app

It builds first on purpose. Compiling whatever .output happens to hold means a binary one version behind the source with nothing to say so — and on a project that has never been built, an ENOENT naming a path the app did not write.

Offline

rscKit({ offline: true }) writes a service worker into .output/public alongside the assets, so a page someone has visited survives a reload with no network. Off by default, and covered in Offline.

Why nitro is pinned

The generated package.json pins an exact version rather than a range:

"nitro": "3.0.260903-beta"

Nitro’s own latest tag is a dated prerelease, and it sorts above the plain 3.0.0 on npm. So ^3.0.0 resolves to the older release, which builds without complaint and then answers 404 to every route. TanStack Start pins a dated beta for the same reason.

Assets are Nitro’s

The build writes browser assets to .output/public, and Nitro serves them from its own root. There is no assetsDir or assetsUrl to set: both were removed, and rscKit() refuses a config that still passes them rather than reading a prefix and ignoring it.

The alternative was the silent version — the markup asks for the app’s prefix, Nitro answers at its own, and the page arrives unstyled and never hydrates with nothing logged anywhere.

Assets live in .output/public and are served by the same process that serves your pages. If you want nginx or a CDN serving them instead, point it at .output/public — that directory is the deployment.


Next: Deploying →

Navigation

Type to search…

↑↓ navigate↵ selectEsc close