@rsc-kit/mcp 0.14.0 → 0.16.0
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.
- package/dist/answers.d.ts +7 -0
- package/dist/answers.js +28 -0
- package/dist/answers.js.map +1 -1
- package/dist/bundleGuides.d.ts +27 -0
- package/dist/bundleGuides.js +138 -0
- package/dist/bundleGuides.js.map +1 -0
- package/dist/index.js +21 -1
- package/dist/index.js.map +1 -1
- package/dist/recipes.js +138 -17
- package/dist/recipes.js.map +1 -1
- package/dist/report.d.ts +13 -0
- package/dist/report.js +1 -1
- package/dist/report.js.map +1 -1
- package/guides/api-routes.md +168 -0
- package/guides/authorization.md +288 -0
- package/guides/caching.md +57 -0
- package/guides/coming-from-next.md +151 -0
- package/guides/connection.md +98 -0
- package/guides/edge-caching.md +159 -0
- package/guides/errors.md +109 -0
- package/guides/file-uploads.md +119 -0
- package/guides/fonts.md +117 -0
- package/guides/forms.md +528 -0
- package/guides/getting-started.md +132 -0
- package/guides/images.md +83 -0
- package/guides/index.json +187 -0
- package/guides/installation.md +338 -0
- package/guides/introduction.md +119 -0
- package/guides/mcp.md +113 -0
- package/guides/metadata.md +289 -0
- package/guides/navigation.md +84 -0
- package/guides/no-javascript.md +76 -0
- package/guides/offline.md +215 -0
- package/guides/ppr.md +181 -0
- package/guides/pwa.md +260 -0
- package/guides/queries.md +340 -0
- package/guides/quick-start.md +99 -0
- package/guides/react-compiler.md +153 -0
- package/guides/redirects.md +143 -0
- package/guides/response-headers.md +66 -0
- package/guides/route-interception.md +206 -0
- package/guides/routing.md +458 -0
- package/guides/sections.md +74 -0
- package/guides/server-actions.md +444 -0
- package/guides/static-generation.md +347 -0
- package/guides/testing.md +158 -0
- package/guides/third-party-scripts.md +105 -0
- package/guides/typed-routes.md +139 -0
- package/guides/url-validation.md +143 -0
- package/guides/validation.md +175 -0
- package/guides/view-transitions.md +120 -0
- package/package.json +4 -3
package/guides/images.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Images
|
|
2
|
+
|
|
3
|
+
> Responsive images with no optimizer to run — unpic for a CDN, imagetools for files in the repo.
|
|
4
|
+
|
|
5
|
+
There is no image component and no image server. `next/image` is two things
|
|
6
|
+
glued together: a component that writes `srcset` and `sizes` for you, and an
|
|
7
|
+
optimizer that resizes on the fly — a process with sharp in it, a cache to
|
|
8
|
+
manage, and a CPU bill on every cold hit. The first half is worth having. The
|
|
9
|
+
second belongs to whatever already serves your images.
|
|
10
|
+
|
|
11
|
+
## Images on a CDN
|
|
12
|
+
|
|
13
|
+
If the file lives on Cloudinary, imgix, Cloudflare Images, Bunny, Vercel,
|
|
14
|
+
Netlify or any of the [other providers unpic knows](https://unpic.pics/img/react/),
|
|
15
|
+
it already has a resizing url. [unpic](https://unpic.pics) writes the `srcset`
|
|
16
|
+
against it:
|
|
17
|
+
|
|
18
|
+
```tsx title="src/app/page.tsx"
|
|
19
|
+
import { Image } from '@unpic/react';
|
|
20
|
+
|
|
21
|
+
<Image
|
|
22
|
+
src="https://res.cloudinary.com/demo/image/upload/sample.jpg"
|
|
23
|
+
layout="constrained"
|
|
24
|
+
width={800}
|
|
25
|
+
height={600}
|
|
26
|
+
alt="A sample"
|
|
27
|
+
/>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
It is a plain component, so it renders in a server component and the browser
|
|
31
|
+
receives an `<img>` — `srcset` from 640w up, `sizes`, `aspect-ratio`,
|
|
32
|
+
`loading="lazy"`, `decoding="async"` — and none of unpic's code. The route's
|
|
33
|
+
javascript does not change. The CDN is detected from the url; nothing to
|
|
34
|
+
configure.
|
|
35
|
+
|
|
36
|
+
TanStack Start and Astro point at the same library, for the same reason.
|
|
37
|
+
|
|
38
|
+
## Images in the repository
|
|
39
|
+
|
|
40
|
+
A file under `src/` is resized once, at build time, by
|
|
41
|
+
[vite-imagetools](https://github.com/JonasKruckenberg/imagetools). Query
|
|
42
|
+
parameters on the import say what you want:
|
|
43
|
+
|
|
44
|
+
```ts title="vite.config.ts"
|
|
45
|
+
import { imagetools } from 'vite-imagetools';
|
|
46
|
+
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
plugins: [imagetools(), rscKit(), ...],
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```tsx title="src/app/page.tsx"
|
|
53
|
+
import hero from '../hero.png?w=400;800;1200&format=webp&as=srcset';
|
|
54
|
+
import heroSrc from '../hero.png?w=800&format=webp';
|
|
55
|
+
|
|
56
|
+
<img srcSet={hero} src={heroSrc} sizes="(min-width: 800px) 800px, 100vw" width={800} height={600} alt="…" />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The build emits `hero-<hash>.webp` at each width into `assets/`, hashed and
|
|
60
|
+
cacheable forever, and a page that is frozen carries the urls. It costs the
|
|
61
|
+
build what resizing costs, once per image per width, and nothing at request
|
|
62
|
+
time. It is opt-in for that reason: a hundred hero images at four widths is a
|
|
63
|
+
noticeable build, and most of them belong on a CDN.
|
|
64
|
+
|
|
65
|
+
Declare the query so TypeScript stops asking:
|
|
66
|
+
|
|
67
|
+
```ts title="src/images.d.ts"
|
|
68
|
+
declare module '*?*' {
|
|
69
|
+
const value: string;
|
|
70
|
+
export default value;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## What to pick
|
|
75
|
+
|
|
76
|
+
| the image is | use |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| user-uploaded, or on a CDN already | unpic |
|
|
79
|
+
| in the repo, a handful | imagetools |
|
|
80
|
+
| in the repo, hundreds | put them on a CDN and use unpic |
|
|
81
|
+
| an icon or a logo | `<img>`, or inline the svg |
|
|
82
|
+
|
|
83
|
+
Neither one runs at request time, and neither is on the page's javascript.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"slug": "api-routes",
|
|
4
|
+
"title": "API routes",
|
|
5
|
+
"description": "Web-standard endpoints, colocated with your pages, with no configuration."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"slug": "authorization",
|
|
9
|
+
"title": "Authorization",
|
|
10
|
+
"description": "Protecting pages, server actions and API routes."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"slug": "caching",
|
|
14
|
+
"title": "Asking once per request",
|
|
15
|
+
"description": "cache() — one lookup, however many places need it."
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"slug": "coming-from-next",
|
|
19
|
+
"title": "Coming from Next.js",
|
|
20
|
+
"description": "What carries over unchanged, what to rename, and what is different on purpose."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"slug": "connection",
|
|
24
|
+
"title": "Rendering per request",
|
|
25
|
+
"description": "Marking work that belongs to the visitor, not to the build."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"slug": "edge-caching",
|
|
29
|
+
"title": "Serving shells from a CDN",
|
|
30
|
+
"description": "Putting build-time shells on the edge, and what rsc-kit does not do."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"slug": "errors",
|
|
34
|
+
"title": "Errors and 404s",
|
|
35
|
+
"description": "What a visitor sees when a page throws, or asks for a url nothing answers."
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"slug": "file-uploads",
|
|
39
|
+
"title": "File uploads",
|
|
40
|
+
"description": "Sending files through a server action without encoding them."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"slug": "fonts",
|
|
44
|
+
"title": "Fonts",
|
|
45
|
+
"description": "Self-hosted fonts from npm, and what next/font was doing for you."
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"slug": "forms",
|
|
49
|
+
"title": "Forms",
|
|
50
|
+
"description": "Progressive forms, pending state and validation errors."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"slug": "getting-started",
|
|
54
|
+
"title": "Getting started",
|
|
55
|
+
"description": "Serve React Server Components from any JavaScript backend."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"slug": "images",
|
|
59
|
+
"title": "Images",
|
|
60
|
+
"description": "Responsive images with no optimizer to run — unpic for a CDN, imagetools for files in the repo."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"slug": "installation",
|
|
64
|
+
"title": "Installation",
|
|
65
|
+
"description": "From an empty directory to a streaming RSC app."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"slug": "introduction",
|
|
69
|
+
"title": "Introduction",
|
|
70
|
+
"description": "React Server Components as a Vite plugin, deployed wherever you like."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"slug": "mcp",
|
|
74
|
+
"title": "Working with an AI agent",
|
|
75
|
+
"description": "An MCP server that answers from your actual build, and the AGENTS.md written into every project."
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"slug": "metadata",
|
|
79
|
+
"title": "Page metadata",
|
|
80
|
+
"description": "Titles, descriptions and Open Graph tags, exported from the page."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"slug": "navigation",
|
|
84
|
+
"title": "Navigation",
|
|
85
|
+
"description": "What happens when someone clicks a link, and why state survives it."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"slug": "no-javascript",
|
|
89
|
+
"title": "A page with no JavaScript",
|
|
90
|
+
"description": "Rendering a route to HTML and stopping there."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"slug": "offline",
|
|
94
|
+
"title": "Offline",
|
|
95
|
+
"description": "Knowing when the server cannot be reached, and carrying on without it."
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"slug": "ppr",
|
|
99
|
+
"title": "Partial prerendering",
|
|
100
|
+
"description": "Storing what every visitor sees the same, and rendering the rest per request."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"slug": "pwa",
|
|
104
|
+
"title": "Progressive web apps",
|
|
105
|
+
"description": "Making the app installable, and adding push notifications and background sync."
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"slug": "queries",
|
|
109
|
+
"title": "Queries",
|
|
110
|
+
"description": "Reading from the server over GET, and letting TanStack Query or SWR own everything above it."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"slug": "quick-start",
|
|
114
|
+
"title": "Quick start",
|
|
115
|
+
"description": "A running app in one command, or added to a project you already have."
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"slug": "react-compiler",
|
|
119
|
+
"title": "React Compiler",
|
|
120
|
+
"description": "Enabling the compiler in the build."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"slug": "redirects",
|
|
124
|
+
"title": "Redirects",
|
|
125
|
+
"description": "Leaving a page from inside the render, and what that costs."
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"slug": "response-headers",
|
|
129
|
+
"title": "Headers and cookies",
|
|
130
|
+
"description": "Setting response headers and cookies during a render."
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"slug": "route-interception",
|
|
134
|
+
"title": "Route interception",
|
|
135
|
+
"description": "Opening a route as a modal over the page you were on."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"slug": "routing",
|
|
139
|
+
"title": "Routing",
|
|
140
|
+
"description": "File-based routes, layouts, loading states and parallel slots."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"slug": "sections",
|
|
144
|
+
"title": "Sections",
|
|
145
|
+
"description": "Refreshing one region of a page without re-rendering the rest."
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"slug": "server-actions",
|
|
149
|
+
"title": "Server actions",
|
|
150
|
+
"description": "Calling the server from a client component, as an ordinary function."
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"slug": "static-generation",
|
|
154
|
+
"title": "Static generation",
|
|
155
|
+
"description": "Rendering pages ahead of time, and exporting a site of files."
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"slug": "testing",
|
|
159
|
+
"title": "Testing",
|
|
160
|
+
"description": "Three tiers, and the one thing that still needs a browser."
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"slug": "third-party-scripts",
|
|
164
|
+
"title": "Third-party scripts",
|
|
165
|
+
"description": "Analytics, tag managers and widgets — and why there is no Script component."
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"slug": "typed-routes",
|
|
169
|
+
"title": "Typed routes",
|
|
170
|
+
"description": "Links that fail the typecheck instead of the browser."
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"slug": "url-validation",
|
|
174
|
+
"title": "Typed URLs",
|
|
175
|
+
"description": "Checking and typing params, query strings and request bodies with a schema you already write."
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"slug": "validation",
|
|
179
|
+
"title": "Validation",
|
|
180
|
+
"description": "Surfacing server-side validation errors in a form."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"slug": "view-transitions",
|
|
184
|
+
"title": "View transitions",
|
|
185
|
+
"description": "What React's ViewTransition animates in an app built with this, and what it does not."
|
|
186
|
+
}
|
|
187
|
+
]
|
|
@@ -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>
|