Skip to content

Getting started

Serve React Server Components from any JavaScript backend.

Updated View as Markdown

A host is a Request in and a Response out. You do not normally write the server that calls it: Nitro builds one around your route tree, and where it runs is a preset in vite.config.ts.

Building

Three steps, and only the first is required:

npm run dev         # vite — serves from source, no build step
npm run build       # bundles, renders every route once, then Nitro assembles .output/
npm run start       # runs .output/server/index.mjs

dev is Vite’s own dev server, and edits reach the browser without a reload:

you edit what happens
a client component Fast Refresh — the code updates and its state survives
a page or layout the payload is re-fetched and the tree re-rendered
adding or deleting a page the server restarts to pick up the new route table

The browser never holds a server component, so Vite cannot hot-swap it. Instead the client re-fetches the page — which remounts any client component below, so their state resets.

Edit a client component directly and its state does survive; that is Fast Refresh.

The restart on a new page is because the route tree is read when the server starts: a page that appears later would otherwise 404 while sitting right there on disk.

build ends by rendering every route once and storing what it can, which is what turns a route into a file on disk instead of a render per request.

It is part of build rather than a separate command because forgetting it costs you everything and looks like nothing — every page still works, each one just renders again for every visitor.

Turn it off with rscKit({ prerender: false }) when the build machine cannot do what the pages need. See Static generation.

Compiling to a single binary

With the Bun preset the whole application ends up in one file:

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

That works because the generated config sets serveStatic: 'inline'. Without it the binary compiles, starts, serves pages, and 404s every asset — the static path resolves into Bun’s virtual filesystem, where the files on disk are not.

What it costs

Measured on the example — eleven routes, 240 KB of assets, 164 KB of stored pages:

compiled run from .output/
Cold start 45 ms 56–65 ms
Resident, idle 18 MB 19 MB
Resident, after 300 requests 27 MB 32 MB
On disk 62 MB 240 KB + a runtime

Memory is flat between the two: the binary holds its assets as Response objects built at boot, which for 240 KB is nothing, and both settle in the high twenties once React has warmed up. A live render is what moves it — 40 MB after 200 of them — and that is the renderer, not the packaging.

If you work with an AI agent

The scaffold writes an AGENTS.md beside your README.md. Claude Code, Cursor and the rest read it, and it covers the things an agent otherwise gets wrong from React or Next habits — "use client" versus "use server", where an authorisation check belongs, which props are async, and that the build output is worth reading rather than ignoring.

Beside it is .mcp.json, which connects the MCP server: Claude Code asks you to approve it on first use, and from then on an agent can read what your last build actually did instead of guessing.

And there is a test to extend, tests/app.test.ts, which goes through the real build: bun run check runs typecheck, lint and tests together, and is the one command an agent — or you — runs before calling something done.

Edit it as your project grows. It is yours; nothing regenerates it.

There is also an MCP server, which answers from your actual build rather than from memory:

claude mcp add rsc-kit -- npx -y @rsc-kit/mcp

It can say why a particular page is not static, what each route ships to the browser, and how to build a form or an api route the way this framework expects. See Working with an AI agent.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close