@rsc-kit/mcp 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,338 @@
1
+ # Installation
2
+
3
+ > From an empty directory to a streaming RSC app.
4
+
5
+ There is no scaffolding command. An app is a Vite project with one plugin and
6
+ one request handler, so this is the whole of it.
7
+
8
+ ## Create an app
9
+
10
+ One command, and it asks the rest:
11
+
12
+ ```sh
13
+ bun create rsc-kit@latest my-app
14
+ ```
15
+
16
+ It asks where it will run (Bun, Node or Cloudflare Workers), whether you want the
17
+ React Compiler and which implementation, and whether to include Tailwind. Every
18
+ answer has a flag, so it also runs unattended:
19
+
20
+ ```sh
21
+ bun create rsc-kit@latest my-app --host=bun --compiler=oxc --tailwind
22
+ ```
23
+
24
+ What comes out builds, prerenders, typechecks and serves before you edit it.
25
+
26
+ The rest of this page is for adding rsc-kit to an app you already have.
27
+
28
+ ## Install
29
+
30
+ <PackageManagers pkg="@rsc-kit/core react react-dom" />
31
+
32
+ <PackageManagers pkg="vite @vitejs/plugin-rsc @vitejs/plugin-react @types/react @types/react-dom" dev />
33
+
34
+ `@rsc-kit/core` brings the Vite plugin, the render engine and the client
35
+ runtime. React 19 and Vite 8 are peer dependencies — the versions the RSC build
36
+ needs, not ones this package pins for you.
37
+
38
+ <Aside type="note" title="What @vitejs/plugin-react is for">
39
+ Fast Refresh: edit a client component and React keeps its state, instead of
40
+ the page reloading and losing it. Put it *after* `rscKit()`.
41
+ </Aside>
42
+
43
+ ## Configure Vite
44
+
45
+ ```ts title="vite.config.ts"
46
+ import { defineConfig } from 'vite'
47
+ import react from '@vitejs/plugin-react'
48
+ import { nitro } from 'nitro/vite'
49
+ import { rscKit } from '@rsc-kit/core/vite'
50
+
51
+ /**
52
+ * The full route tree, built the way every scaffolded app is built.
53
+ *
54
+ * Nitro owns the server and the entry is generated — there is no server file
55
+ * in this directory, which is the point.
56
+ */
57
+ export default defineConfig({
58
+ plugins: [
59
+ nitro({ preset: 'bun', serveStatic: 'inline' }),
60
+ rscKit({
61
+ sourceDir: 'src',
62
+ outDir: 'build',
63
+ viewTransitions: true,
64
+ offline: true,
65
+ }),
66
+ react(),
67
+ ],
68
+ })
69
+ ```
70
+
71
+ `sourceDir` is where your route tree lives. The defaults are plain Vite ones —
72
+ `src/app` in, `dist/client` and `.rsc` out — so a project that adopts them can
73
+ call `rscKit()` with nothing at all.
74
+
75
+ <Aside type="note" title="Add your own plugins after it">
76
+ `rscKit()` includes `@vitejs/plugin-rsc`, which has to see modules before
77
+ any React layer transforms them. Tailwind, the React Compiler and anything
78
+ else go after it in the array.
79
+ </Aside>
80
+
81
+ ## Write a root layout
82
+
83
+ The root layout renders the **whole document** — there is no separate HTML
84
+ template, and the build injects the bootstrap script and stylesheet links into
85
+ what you return here.
86
+
87
+ ```tsx title="src/app/layout.tsx"
88
+ import './styles.css'
89
+ // Preloaded so the browser finds it before the stylesheet does. ?url is Vite's
90
+ // and hands back the hashed path the build serves.
91
+ import frauncesLatin from '@fontsource-variable/fraunces/files/fraunces-latin-full-normal.woff2?url'
92
+ import type { ReactNode } from 'react'
93
+ import { Nav } from '../components/Nav'
94
+ import type { Metadata } from '@rsc-kit/core/metadata'
95
+
96
+ export const metadata: Metadata = {
97
+ title: { template: '%s · rsc-kit', default: 'rsc-kit' },
98
+ description: 'React Server Components as a Vite plugin',
99
+ // Once, here. A share-card scraper needs an absolute image url, and this is
100
+ // what turns the opengraph-image.png in app/ into one.
101
+ metadataBase: new URL('https://example.rsc-kit.dev'),
102
+ openGraph: {
103
+ siteName: 'rsc-kit example',
104
+ type: 'website',
105
+ },
106
+ twitter: {
107
+ card: 'summary_large_image',
108
+ site: '@rsckit',
109
+ },
110
+ }
111
+
112
+ // `modal` is a parallel slot: the @modal directory beside this file fills it.
113
+ // It renders alongside children, not instead of them.
114
+ export default function RootLayout({ children, modal }: { children: ReactNode; modal?: ReactNode }) {
115
+ return (
116
+ <html lang="en">
117
+ <head>
118
+ <meta charSet="utf-8" />
119
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
120
+ <link rel="preload" href={frauncesLatin} as="font" type="font/woff2" crossOrigin="anonymous" />
121
+ </head>
122
+ <body>
123
+ <Nav />
124
+ <main>{children}</main>
125
+ {modal}
126
+ </body>
127
+ </html>
128
+ )
129
+ }
130
+ ```
131
+
132
+ ## Write a page
133
+
134
+ ```tsx title="src/app/page.tsx"
135
+ export default function HomePage() {
136
+ return <h1>Hello</h1>;
137
+ }
138
+ ```
139
+
140
+ `app/page.tsx` serves `GET /`, wrapped in `app/layout.tsx`. Nothing registers
141
+ it — the plugin reads the directory at build time. See
142
+ [Routing](/guides/routing) for the rest of the conventions.
143
+
144
+ ## Serve it
145
+
146
+ There is no server file to write. Nitro builds one around the route tree, and
147
+ `npm run build` leaves it in `.output/`:
148
+
149
+ ```sh
150
+ npm run build
151
+ npm run start
152
+ ```
153
+
154
+ Where it runs is the preset in `vite.config.ts` — see [Where it
155
+ runs](/hosts/where-it-runs).
156
+
157
+ ## Scripts
158
+
159
+ ```json title="package.json"
160
+ {
161
+ "type": "module",
162
+ "scripts": {
163
+ "dev": "vite",
164
+ "build": "vite build",
165
+ "start": "bun .output/server/index.mjs",
166
+ "compile": "bun build --compile .output/server/index.mjs --outfile my-app"
167
+ }
168
+ }
169
+ ```
170
+
171
+ `build` freezes every page it can, so there is no separate prerender step and
172
+ no command for one — freezing runs the app, which needs the bundle the build
173
+ just wrote, and only the build knows where that is. Turn it off with
174
+ `rscKit({ prerender: false })`.
175
+
176
+ `start` runs what the build wrote. There is no server file in a scaffolded app —
177
+ Nitro builds the server around the route tree, and where it runs is a preset in
178
+ `vite.config.ts`. See [Where it runs](/hosts/where-it-runs).
179
+
180
+ `compile` produces a standalone binary containing the engine, the route tree,
181
+ the frozen pages **and** the assets. That last part is `serveStatic: 'inline'`
182
+ in the vite config; without it the binary serves pages and 404s every asset.
183
+
184
+ <Aside type="note" title="Do not set NODE_ENV">
185
+ The build stamps its mode into the bundle, so `npm run start` is
186
+ production because it was *built* that way. Setting `NODE_ENV` yourself adds a
187
+ second source of truth that can disagree — and when it disagrees, every page
188
+ renders and none of them become interactive.
189
+
190
+ For a development build with React's real error messages, use
191
+ `vite build --mode development`.
192
+ </Aside>
193
+
194
+ Then:
195
+
196
+ ```bash
197
+ npm run build # bundles, and freezes every page it can
198
+ npm run start
199
+ ```
200
+
201
+ Worth adding a `typecheck` script beside them (`tsc --noEmit`) and running it
202
+ in CI. Server components are ordinary functions and pages are ordinary
203
+ modules, so the typechecker sees the whole route tree — a page whose props do
204
+ not match its `[param]` segment is a compile error rather than a blank page.
205
+
206
+ ## TypeScript
207
+
208
+ ```json title="tsconfig.json"
209
+ {
210
+ "compilerOptions": {
211
+ "target": "ESNext",
212
+ "module": "ESNext",
213
+ "moduleResolution": "bundler",
214
+ "jsx": "react-jsx",
215
+ "strict": true,
216
+ "noEmit": true,
217
+ "skipLibCheck": true,
218
+ "resolveJsonModule": true,
219
+ "types": [
220
+ "@types/bun",
221
+ "vite/client"
222
+ ]
223
+ },
224
+ "include": ["src/**/*", "server/**/*", ".rsc-kit/**/*", "vite.config.ts"]
225
+ }
226
+ ```
227
+
228
+ `vite/client` is doing real work in that list: without it a side-effect import
229
+ of a stylesheet (`import './styles.css'` in the root layout) is an error, and
230
+ `import.meta.env` is untyped.
231
+
232
+ ### Environment variables
233
+
234
+ There is nothing to install and nothing this package adds — Vite already owns
235
+ this. Two rules and one declaration.
236
+
237
+ Anything named `VITE_*` is **inlined into the client bundle** and ships to the
238
+ browser, whether or not a browser file reads it. Everything else stays on the
239
+ server, read through `process.env`.
240
+
241
+ The prefix is the whole boundary, so never put a secret behind it.
242
+ `VITE_STRIPE_KEY` is a published key.
243
+
244
+ Declare the ones you use, and Vite types them:
245
+
246
+ ```ts title="src/env.d.ts"
247
+ interface ViteTypeOptions {
248
+ // Turns an unknown import.meta.env key into an error instead of `any`.
249
+ strictImportMetaEnv: unknown;
250
+ }
251
+
252
+ interface ImportMetaEnv {
253
+ readonly VITE_API_URL: string;
254
+ }
255
+ ```
256
+
257
+ <Aside type="caution" title="On Bun, a misspelled variable still compiles">
258
+ `@types/bun` declares its own `ImportMetaEnv`, which cancels out the strictness
259
+ Vite's option adds. You still get autocomplete and `string` instead of
260
+ `string | undefined` — but a typo is `any`, not an error.
261
+ </Aside>
262
+
263
+ #### When they are read
264
+
265
+ `import.meta.env.VITE_*` is a literal in the bundle: changing one means a
266
+ rebuild. `process.env.*` is a live read, so the deploy's environment wins —
267
+ with one exception the build makes for you. A route that reads an environment
268
+ variable and nothing request-bound is frozen at build time, value included,
269
+ because nothing marked it as needing a request. That is the right answer for
270
+ `PUBLIC_SITE_NAME` and the wrong one for a feature flag flipped per deploy. For
271
+ the second kind, read it after `await connection()`, which keeps the route on
272
+ the server. See [Static generation](/guides/static-generation).
273
+
274
+ #### Validating them
275
+
276
+ Vite checks the prefix and nothing else: a missing `DATABASE_URL` is
277
+ `undefined` until the first query fails. There is nothing native for this
278
+ because nothing native is needed — [t3-env](https://env.t3.gg/docs/core) works
279
+ unchanged, with any Standard Schema validator:
280
+
281
+ ```ts title="src/env.ts"
282
+ import { createEnv } from '@t3-oss/env-core';
283
+ import { z } from 'zod';
284
+
285
+ export const env = createEnv({
286
+ server: { DATABASE_URL: z.string().url() },
287
+ clientPrefix: 'VITE_',
288
+ client: { VITE_API_URL: z.string().url() },
289
+ runtimeEnv: typeof process === 'undefined' ? import.meta.env : { ...import.meta.env, ...process.env },
290
+ emptyStringAsUndefined: true,
291
+ });
292
+ ```
293
+
294
+ Read `env.DATABASE_URL` instead of `process.env.DATABASE_URL` and three things
295
+ follow. It is a `string`, not `string | undefined`. A server variable touched
296
+ from a client component throws by name rather than being silently `undefined`.
297
+ And because the module validates when it is first imported, a missing variable
298
+ fails the **build** — the prerender imports it — instead of the first request
299
+ in production.
300
+
301
+ The build writes its declaration files into `.rsc-kit/` at the project root,
302
+ not among your own source. Three of them:
303
+
304
+ - `rsc-routes.d.ts` — the routes it found, which is what makes `Link` and
305
+ `redirect()` [typed](/guides/routing#typed-links)
306
+ - `rsc-env.d.ts` — the host global
307
+ - `rsc-engine.d.ts` — the generated bundle
308
+
309
+ Only what has to be generated is. Anything you can import, you import —
310
+ [`Metadata`](/guides/metadata/) comes from the package and works before you have
311
+ built anything.
312
+
313
+ These are ambient, which needs only that they are inside the project and that
314
+ the typechecker is told the directory exists:
315
+
316
+ ```json title="tsconfig.json"
317
+ { "include": ["src/**/*", ".rsc-kit/**/*"] }
318
+ ```
319
+
320
+ A scaffolded app has that line, and `rsc-kit init` adds it. Leave it out and
321
+ the build says so — otherwise typed routes fall back to plain `string` and
322
+ nothing else would tell you.
323
+
324
+ Add `.rsc-kit/` to `.gitignore`.
325
+
326
+ ## Where next
327
+
328
+ <CardGrid>
329
+ <Card title="Routing" icon="ph:tree-structure">
330
+ [Pages, layouts, slots and navigation →](/guides/routing)
331
+ </Card>
332
+ <Card title="Server actions" icon="ph:lightning">
333
+ [Calling the server from a client component →](/guides/server-actions)
334
+ </Card>
335
+ <Card title="Static generation" icon="ph:snowflake">
336
+ [Rendering ahead of time, and exporting →](/guides/static-generation)
337
+ </Card>
338
+ </CardGrid>
@@ -0,0 +1,119 @@
1
+ # Introduction
2
+
3
+ > React Server Components as a Vite plugin, deployed wherever you like.
4
+
5
+ `rsc-kit` is a Vite plugin that gives you React Server Components. You write
6
+ pages, layouts and server actions the way you would in the Next.js App Router;
7
+ it renders them, streams the HTML, and turns links into navigations that replace
8
+ only the part of the page that changed.
9
+
10
+ It brings no build system of its own — it is a Vite plugin — and it does not
11
+ write a server for you either. [Nitro](https://nitro.build) builds one around
12
+ your route tree, so where an app runs is a preset: Bun, Node, a Cloudflare
13
+ Worker, Vercel, Netlify, Deno.
14
+
15
+ ## What an app looks like
16
+
17
+ ```text
18
+ src/app/
19
+ layout.tsx the document — <html>, <head>, <body>
20
+ loading.tsx Suspense fallback for everything below
21
+ page.tsx GET /
22
+ posts/
23
+ [slug]/
24
+ page.tsx GET /posts/:slug
25
+ @modal/
26
+ default.tsx a parallel slot, empty until something fills it
27
+ ```
28
+
29
+ Nothing registers those files. The plugin reads the directory at build time and
30
+ generates the entry that knows about them.
31
+
32
+ ```tsx title="src/app/posts/[slug]/page.tsx"
33
+ import { findPost } from '../../../data'
34
+
35
+ export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
36
+ const { slug } = await params
37
+ const post = await findPost(slug)
38
+
39
+ if (!post) return <h1>No such post</h1>
40
+
41
+ return (
42
+ <article>
43
+ <h1>{post.title}</h1>
44
+ <p>{post.body}</p>
45
+ </article>
46
+ )
47
+ }
48
+ ```
49
+
50
+ A server component is an ordinary async function. It imports its data module
51
+ directly, and neither the module nor its secrets reach the browser.
52
+
53
+ ## What you get
54
+
55
+ <CardGrid>
56
+ <Card title="Streaming, not waterfalls" icon="ph:waves">
57
+ The shell paints before the data resolves. Suspense boundaries fill in as
58
+ they finish, in whatever order they finish.
59
+ </Card>
60
+ <Card title="Navigations that keep the page" icon="ph:arrows-left-right">
61
+ A navigation sends the layout chain it already has, and the server answers
62
+ with only the part that changed. Scroll, focus and half-typed forms survive.
63
+ </Card>
64
+ <Card title="Parallel routes and interception" icon="ph:frame-corners">
65
+ `@slot` directories render alongside the page. `(.)folder` opens a route as
66
+ a modal over the page you were on, and as a real page on refresh.
67
+ </Card>
68
+ <Card title="Server actions" icon="ph:lightning">
69
+ `"use server"` makes an async function callable from a client component.
70
+ The body never ships; the call becomes one POST.
71
+ </Card>
72
+ <Card title="Queries, without an endpoint" icon="ph:magnifying-glass">
73
+ `query()` marks a read. It travels as a GET, so it can be cached and
74
+ prefetched — and reads that happen together leave as one request.
75
+ </Card>
76
+ <Card title="Frozen where it can be" icon="ph:snowflake">
77
+ Pages that ask for nothing dynamic are rendered at build time. Pages that do
78
+ get their shell frozen and their data streamed.
79
+ </Card>
80
+ <Card title="An export target" icon="ph:folder-open">
81
+ The same app can build to a directory of files and be served by anything —
82
+ including a CDN with no origin at all.
83
+ </Card>
84
+ </CardGrid>
85
+
86
+ ## Where it runs
87
+
88
+ You do not write a server. [Nitro](https://nitro.build) builds one from your
89
+ route tree, and where it runs is one line in your Vite config:
90
+
91
+ ```ts title="vite.config.ts"
92
+ nitro({ preset: 'bun' })
93
+ ```
94
+
95
+ Swap the preset for `node`, `cloudflare_module`, `vercel`, `netlify` or `deno`
96
+ and the same app deploys there instead. The build produces a `.output`
97
+ directory; nothing else changes.
98
+
99
+ ## What it is not
100
+
101
+ <Aside type="note" title="No data layer, no CSS pipeline, no dev server of its own">
102
+ The build runs your project's Vite config, so Tailwind, PostCSS and the rest
103
+ are configured the way they are in any Vite app. Data access is an import.
104
+ There is no `getServerSideProps` equivalent, and no request context to learn.
105
+ </Aside>
106
+
107
+ ## Where to go next
108
+
109
+ <CardGrid>
110
+ <Card title="Getting started" icon="ph:play">
111
+ [Build and deploy your first app →](/getting-started)
112
+ </Card>
113
+ <Card title="Routing" icon="ph:tree-structure">
114
+ [Pages, layouts, slots and navigation →](/guides/routing)
115
+ </Card>
116
+ <Card title="How it works" icon="ph:gear">
117
+ [What happens between a request and a page →](/reference/how-it-works)
118
+ </Card>
119
+ </CardGrid>
@@ -1,39 +1,71 @@
1
1
  # A page with no JavaScript
2
2
 
3
- > Rendering a route to HTML and stopping there.
3
+ > The default is none. A client component is how a page asks for it.
4
4
 
5
- Some pages have nothing to hydrate. A marketing page, a changelog, an article.
6
- For those, shipping React is paying about 70 kB gzipped for nothing.
5
+ There is no JavaScript on a page until something on it needs some. A
6
+ marketing page, a changelog, an article, a policy: the build stores them as
7
+ HTML and stops. No bootstrap script, so no React, no Flight client, no router.
7
8
 
8
- ```tsx title="src/app/about/page.tsx"
9
- export const clientJs = false
10
-
11
- export default function AboutPage() {
12
- return <h1>About</h1>
13
- }
9
+ ```
10
+ ○ /about no js
11
+ no client components, so ships no javascript; stylesheet inlined
14
12
  ```
15
13
 
16
- That route now renders to HTML and stops. No bootstrap script, so no React, no
17
- Flight client, no router.
14
+ **`"use client"` is the opt-in.** A route that freezes whole, renders none of
15
+ your client components, and has no server action in its tree ships nothing.
16
+ Put a client component in it — a counter, a `<Link>`, an update prompt — and
17
+ that page has the runtime, because there is now something for the runtime to
18
+ do. That is the whole rule, and there is no switch beside it in either
19
+ direction, on purpose:
18
20
 
19
- ## What stops working
21
+ - Nothing can *need* the runtime without a client component or an action in
22
+ its tree, so there is nothing an "on" switch could say that the tree does
23
+ not already say.
24
+ - A page that must *stay* this way is an assertion, not a setting: the route's
25
+ `clientJs` in `build-report.json` is `null` when it shipped none, and a CI
26
+ step can hold it there.
20
27
 
21
- Links on that page are ordinary links a full page load rather than a segment
22
- swap. Anything interactive stops working, which is why the build refuses the
23
- combination rather than shipping it:
28
+ The check reads the rendered tree, not the source, so a client component
29
+ reached through a shared layout counts. Most pages under a layout with a
30
+ `<Link>` in its header keep the runtime; a route group with its own plain
31
+ layout drops it. That is the honest shape of it: the pages that can be
32
+ nothing are nothing, and the page with a nav is a page with a nav.
24
33
 
34
+ ## What a page without the runtime still does
35
+
36
+ **Its stylesheet is inlined** into the document when it is small on the wire
37
+ (at most 10 kB gzipped), so the first paint waits on no request but the
38
+ document itself. The build says so on the route's line. Fonts are the one
39
+ thing left to you, because only you know which face the first paint needs —
40
+ the [fonts guide](/guides/fonts) shows the two-line preload.
41
+
42
+ The default is not always the right call. A site of many small pages — a
43
+ blog, documentation — may prefer one stylesheet the browser caches once over
44
+ the same 4 kB repeated in every document, and a sheet just over the cap may
45
+ be worth inlining anyway. It is one setting on the plugin:
46
+
47
+ ```ts title="vite.config.ts"
48
+ rscKit({ inlineStylesheets: 'auto' }) // the default: when at most 10 kB gzipped
49
+ rscKit({ inlineStylesheets: false }) // never — keep the link, cache one file
50
+ rscKit({ inlineStylesheets: true }) // always, whatever the size
51
+ rscKit({ inlineStylesheets: 24_000 }) // your own cap, in gzipped bytes
25
52
  ```
26
- app/about/page.tsx declares clientJs = false but renders client components:
27
- src/components/Counter.tsx
28
- ```
29
53
 
30
- That is a build failure, not a warning. A client component with no runtime is
31
- inert markup a button that does nothing and it is usually inherited from a
32
- shared layout rather than written on the page, so the message names the
33
- components responsible.
54
+ **It still registers the [service worker](/guides/offline)** when the app has
55
+ one: the runtime's one-line registration is inlined in its place, so a visitor
56
+ who lands here first gets the worker the second visit is for. What such a
57
+ page cannot show is the update prompt — that is a client component, and a
58
+ page that uses it has the runtime.
59
+
60
+ **Navigation is unchanged in both directions.** A page with no client
61
+ component has no `<Link>`, so its anchors were already full loads. A `<Link>`
62
+ elsewhere that points *at* it still fetches its flight payload and swaps the
63
+ segment, because that payload is written from the render with the runtime.
34
64
 
35
- ## It cannot be a PPR route
65
+ ## Only a page stored whole
36
66
 
37
67
  With no client runtime there is nothing to fill a Suspense hole after the
38
- shell arrives. A `clientJs = false` route should be fully static: fetch what it
39
- needs at build time and freeze the whole page.
68
+ shell arrives, so a partial prerender always keeps it, and so does a page
69
+ rendered per request. The line is what the runtime would *do*: hydrate a
70
+ client component, submit a `<form action={serverFn}>`, fill a hole. A page
71
+ with none of those is HTML, and is stored as HTML.
@@ -0,0 +1,99 @@
1
+ # Quick start
2
+
3
+ > A running app in one command, or added to a project you already have.
4
+
5
+ ## A new app
6
+
7
+ ```sh
8
+ bun create rsc-kit@latest my-app
9
+ cd my-app
10
+ bun run dev
11
+ ```
12
+
13
+ That is it. You get a page, a layout, a client component and a Vite config,
14
+ wired together and running. There is no server file — Nitro builds one from the
15
+ preset in that config when you build.
16
+
17
+ When you are ready to ship:
18
+
19
+ ```sh
20
+ bun run build
21
+ bun run start
22
+ ```
23
+
24
+ The build prints what it did:
25
+
26
+ ```
27
+ ○ /
28
+
29
+ ○ (Static) prerendered as static content
30
+
31
+ 1 static
32
+ ```
33
+
34
+ Pages marked `○` are **static**: rendered once at build time rather than for
35
+ each visitor.
36
+
37
+ ## An app you already have
38
+
39
+ Already have a project? Add rsc-kit to it:
40
+
41
+ ```sh
42
+ bunx rsc-kit@latest init
43
+ ```
44
+
45
+ It reads your `package.json`, works out which server you use and where your
46
+ source lives, and writes only what is missing:
47
+
48
+ ```
49
+ server hono
50
+ source src
51
+ react will be added
52
+
53
+ + src/app/layout.tsx
54
+ + src/app/page.tsx
55
+ + vite.config.ts
56
+ ~ package.json — added @rsc-kit/core, react, react-dom, vite, nitro, …
57
+ ~ scripts — dev, build, start, compile, typecheck
58
+ ```
59
+
60
+ No server file. Nitro builds one around the route tree, so the app owns a route
61
+ tree and a vite config and nothing in between.
62
+
63
+ **It never overwrites anything.** If you already have a `vite.config.ts`, it
64
+ prints the edit to make instead of replacing work you have done. Running it twice is safe — the second run just tells you what is already
65
+ in place.
66
+
67
+ Then:
68
+
69
+ ```sh
70
+ bun install
71
+ bun run dev
72
+ ```
73
+
74
+ ## Add a page
75
+
76
+ Routes are directories under `src/app`. Create a folder, put a `page.tsx` in
77
+ it, and it exists:
78
+
79
+ ```tsx title="src/app/about/page.tsx"
80
+ export default function AboutPage() {
81
+ return <h1>About</h1>
82
+ }
83
+ ```
84
+
85
+ ```sh
86
+ bun run build # the route tree is read at build time
87
+ ```
88
+
89
+ ## Doing it by hand
90
+
91
+ If you would rather wire it up yourself — or the CLI cannot reach your
92
+ setup — [Installation](/installation) walks through the same result one file at
93
+ a time.
94
+
95
+ ## Where next
96
+
97
+ - [Routing](/guides/routing) — layouts, dynamic segments, parallel slots
98
+ - [Server actions](/guides/server-actions) — mutations without an API route
99
+ - [Where it runs](/hosts/where-it-runs) — presets, and compiling to a binary
@@ -118,6 +118,12 @@ rscKit({ sourceDir: 'src', prerender: false });
118
118
  **Try [`connection()`](/guides/connection) first.** It is almost always the
119
119
  better answer, and this is almost always too big a hammer.
120
120
 
121
+ It is also one hammer for two things. The decision that a page has
122
+ [nothing to hydrate](/guides/no-javascript) is made in the same build-time
123
+ render, so with prerendering off every page renders per request, with the
124
+ runtime. That is the right trade for a build machine that cannot reach the
125
+ data — the reason this exists — and the wrong one for anything else.
126
+
121
127
  Prerendering **runs your application code**, so it needs whatever that code
122
128
  needs — a page that queries a database needs that database reachable from the
123
129
  build. When it is not, marking that one query is a smaller and more accurate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsc-kit/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "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.",
5
5
  "type": "module",
6
6
  "license": "MIT",