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.
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.mjsA Worker has no start, because it is deployed rather than started:
npm run preview # wrangler dev, on workerd
npm run deploy # nitro deploy --prebuiltCompiling 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/appIt 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 →