---
title: "Getting started"
description: "Serve React Server Components from any JavaScript backend."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.rsc-kit.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting started

import CodeFromFile from "@/components/CodeFromFile.astro";

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.

Turn it off with `rscKit({ prerender: false })` when the build machine
cannot do what the pages need. See [Static
generation](/guides/static-generation).

> **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.

## 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.

> **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.

> **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.

## 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/).

Source: https://docs.rsc-kit.dev/getting-started/index.mdx
