UNPKG

@rsc-kit/mcp

Version:

An MCP server over what an rsc-kit build decided: the routes, why each one is static or not, and what it costs the browser.

133 lines (99 loc) • 5.5 kB
# Getting started > Serve React Server Components from any JavaScript backend. A host is a `Request` in and a `Response` out. You do not normally write the server that calls it: [Nitro](https://nitro.build) 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: ```bash 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. A page the build machine cannot render says so with `await connection()` and renders per request; there is no switch for the whole app. See [Static generation](/guides/static-generation). <Aside type="note" title="There is no NODE_ENV to set"> React picks its build from it, and getting it wrong gives you a page that renders perfectly and never hydrates. The build stamps the mode it ran in into the server bundles, so a server is production because it was built that way — `npm run start` needs no environment at all. </Aside> ## Compiling to a single binary With the Bun preset the whole application ends up in one file: ```bash 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. <Aside type="note" title="What compiling is actually for"> Deployment shape, not speed. One file, no `node_modules`, no runtime to install. Throughput between the two is inside the noise of any benchmark I could run on one machine — the only differences I can defend are the ~15 ms of cold start and the shape of what you ship. </Aside> <Aside type="caution" title="What embedding costs"> Every asset goes in whole. Three files is nothing; a media-heavy app is hundreds of megabytes of executable, with no CDN in front and no streaming from disk. Keep large or rarely-read files outside the binary and serve them from wherever they already live. Frozen pages are not embedded either. The build writes them to `.output/server/rsc-static` and the server reads them from beside itself; a binary has no filesystem to read, so it renders those pages live. Everything still answers — what you lose is the stored render, not the page. </Aside> ## 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](/guides/mcp): 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: ```sh 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](/guides/mcp/).