@rsc-kit/mcp 0.16.3 → 0.18.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 +14 -1
- package/dist/answers.js +76 -34
- package/dist/answers.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/recipes.js +434 -14
- package/dist/recipes.js.map +1 -1
- package/dist/report.d.ts +12 -0
- package/dist/report.js +17 -15
- package/dist/report.js.map +1 -1
- package/guides/api-routes.md +106 -6
- package/guides/authorization.md +39 -0
- package/guides/backend-answered-pages.md +163 -0
- package/guides/coming-from-next.md +12 -4
- package/guides/deployment.md +129 -0
- package/guides/domains.md +129 -0
- package/guides/emails.md +90 -0
- package/guides/errors.md +16 -2
- package/guides/go.md +194 -0
- package/guides/images.md +10 -4
- package/guides/index.json +50 -0
- package/guides/installation.md +31 -13
- package/guides/instrumentation.md +91 -0
- package/guides/introduction.md +4 -0
- package/guides/laravel.md +406 -0
- package/guides/mcp.md +1 -1
- package/guides/metadata.md +1 -1
- package/guides/offline.md +8 -0
- package/guides/queries.md +138 -8
- package/guides/quick-start.md +31 -0
- package/guides/redirects.md +14 -0
- package/guides/response-headers.md +22 -0
- package/guides/routing.md +5 -1
- package/guides/seo-files.md +140 -0
- package/guides/typed-routes.md +26 -0
- package/guides/view-transitions.md +53 -7
- package/guides/where-it-runs.md +155 -0
- package/guides/your-own-backend.md +238 -0
- package/package.json +2 -2
|
@@ -87,3 +87,25 @@ jar.set('name', 'value', {
|
|
|
87
87
|
|
|
88
88
|
Names are validated as cookie tokens and `sameSite` / `expires` are checked, so
|
|
89
89
|
a typo is an error rather than a header the browser quietly ignores.
|
|
90
|
+
|
|
91
|
+
## What a response says about itself
|
|
92
|
+
|
|
93
|
+
Two things, and they are not the same kind:
|
|
94
|
+
|
|
95
|
+
**How it was served**, always: `X-RSC-Kit: stored`, `rendered` or `shell` on
|
|
96
|
+
every response — a page from a file the build wrote, a page rendered for this
|
|
97
|
+
visitor, or a stored shell with its holes rendered now. It is the header a
|
|
98
|
+
developer reads in the Network tab when a page is slower than expected, the
|
|
99
|
+
way `X-Nextjs-Cache` is, and a CDN rule or a health check can key on it. It
|
|
100
|
+
names no product, so there is nothing to strip.
|
|
101
|
+
|
|
102
|
+
**What built it**, by default: `X-Powered-By: rsc-kit` on every response and
|
|
103
|
+
`<meta name="generator" content="rsc-kit">` in every document — what
|
|
104
|
+
BuiltWith and Wappalyzer read. The name only, never the version: a version in
|
|
105
|
+
every response is what a vulnerability scanner filters on. A policy that
|
|
106
|
+
strips every framework identifier turns both off, which matters for the tag,
|
|
107
|
+
since a proxy can strip a header but not a line inside the HTML:
|
|
108
|
+
|
|
109
|
+
```ts title="vite.config.ts"
|
|
110
|
+
rscKit({ identify: false })
|
|
111
|
+
```
|
package/guides/routing.md
CHANGED
|
@@ -303,7 +303,11 @@ A **query string** is not — the same route is asked for with `?q=shoes` and
|
|
|
303
303
|
`?q=hats` — so there is no honest answer at build time, and `useSearchParams`
|
|
304
304
|
throws rather than pretending it is empty. The dev server does the same, on
|
|
305
305
|
purpose: a page has one shape, and a boundary missing in dev is the one the
|
|
306
|
-
build will refuse.
|
|
306
|
+
build will refuse. Under a `<Suspense>` you wrote, nothing is said. With
|
|
307
|
+
nothing closer than a `loading.tsx`, dev prints one line and shows it at the
|
|
308
|
+
bottom of the page: the whole segment is that fallback until the query
|
|
309
|
+
arrives, and a boundary around the component that reads keeps the rest
|
|
310
|
+
painted.
|
|
307
311
|
|
|
308
312
|
Wrap it, and the throw becomes the fallback:
|
|
309
313
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# robots, sitemap and llms.txt
|
|
2
|
+
|
|
3
|
+
> The files a site describes itself with, from a file beside the root layout — written the way Next writes them, stored at build when they can be.
|
|
4
|
+
|
|
5
|
+
A crawler asks for `/robots.txt` and `/sitemap.xml` before it reads a page,
|
|
6
|
+
and a model asks for `/llms.txt`. Each comes from a file beside the root
|
|
7
|
+
layout, named for what it answers:
|
|
8
|
+
|
|
9
|
+
| file | answers | returns |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| `src/app/robots.ts` | `/robots.txt` | `MetadataRoute.Robots` |
|
|
12
|
+
| `src/app/sitemap.ts` | `/sitemap.xml` | `MetadataRoute.Sitemap` |
|
|
13
|
+
| `src/app/llms.ts` | `/llms.txt` | `MetadataRoute.Llms` |
|
|
14
|
+
| `src/app/llms-full.ts` | `/llms-full.txt` | a string |
|
|
15
|
+
|
|
16
|
+
The same names and shapes as Next, so a port copies them across unchanged.
|
|
17
|
+
|
|
18
|
+
```ts title="src/app/robots.ts"
|
|
19
|
+
import type { MetadataRoute } from '@rsc-kit/core/metadata';
|
|
20
|
+
|
|
21
|
+
export default function robots(): MetadataRoute.Robots {
|
|
22
|
+
return {
|
|
23
|
+
rules: [
|
|
24
|
+
{ userAgent: '*', allow: '/', disallow: ['/api/', '/studio'] },
|
|
25
|
+
{ userAgent: 'GPTBot', disallow: '/' },
|
|
26
|
+
],
|
|
27
|
+
sitemap: '/sitemap.xml',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```ts title="src/app/sitemap.ts"
|
|
33
|
+
import type { MetadataRoute } from '@rsc-kit/core/metadata';
|
|
34
|
+
import { db } from '@/lib/db';
|
|
35
|
+
|
|
36
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
37
|
+
const posts = await db.post.findMany({ select: { slug: true, updatedAt: true } });
|
|
38
|
+
|
|
39
|
+
return [
|
|
40
|
+
{ url: '/', changeFrequency: 'weekly', priority: 1 },
|
|
41
|
+
{ url: '/pricing' },
|
|
42
|
+
...posts.map((post) => ({ url: `/blog/${post.slug}`, lastModified: post.updatedAt })),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```ts title="src/app/llms.ts"
|
|
48
|
+
import type { MetadataRoute } from '@rsc-kit/core/metadata';
|
|
49
|
+
|
|
50
|
+
export default function llms(): MetadataRoute.Llms {
|
|
51
|
+
return {
|
|
52
|
+
title: 'Remorva',
|
|
53
|
+
summary: 'Photo restoration: damage repaired and colour restored, in about a minute.',
|
|
54
|
+
sections: [
|
|
55
|
+
{ title: 'Pages', links: [{ title: 'Pricing', url: '/pricing', description: 'Per restoration, no subscription' }] },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A relative url in any of them is made absolute with the root layout's
|
|
62
|
+
`metadataBase`; without one, a relative url is a build error that says so.
|
|
63
|
+
Any of the three may return a string instead, served as written.
|
|
64
|
+
|
|
65
|
+
## A sitemap you do not write
|
|
66
|
+
|
|
67
|
+
With no `sitemap.ts`, the build writes `/sitemap.xml` itself, from what it
|
|
68
|
+
already knows: every page it stored and every url `generateStaticParams`
|
|
69
|
+
listed, each with the build as `lastModified`. Left out: anything under a
|
|
70
|
+
`middleware.ts` (a guard means not for everyone), a page the build could not
|
|
71
|
+
render, and the not-found page. The build's table says so:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
○ /sitemap.xml
|
|
75
|
+
written by the build: 5 urls; a sitemap.ts beside the root layout replaces it
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
It needs the root layout's `metadataBase` for the host; without one the line
|
|
79
|
+
says it was not written. A `sitemap.ts` replaces it entirely — the moment you
|
|
80
|
+
want a page the build cannot see (a post per database row without
|
|
81
|
+
`generateStaticParams`) or a `changeFrequency`, write one.
|
|
82
|
+
|
|
83
|
+
## How fresh
|
|
84
|
+
|
|
85
|
+
Three choices, and the function decides — the same rule every route follows:
|
|
86
|
+
|
|
87
|
+
| you write | served | fresh |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| nothing | the build's own sitemap, stored | every deploy |
|
|
90
|
+
| a `sitemap.ts` that reads the database | stored at build | every deploy |
|
|
91
|
+
| a `sitemap.ts` that awaits `connection()` | rendered per request | every crawl |
|
|
92
|
+
|
|
93
|
+
```ts title="src/app/sitemap.ts"
|
|
94
|
+
import { connection } from '@rsc-kit/core/request';
|
|
95
|
+
|
|
96
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
97
|
+
await connection(); // per request — the same mark a page uses
|
|
98
|
+
|
|
99
|
+
const posts = await db.post.findMany({ select: { slug: true, updatedAt: true } });
|
|
100
|
+
|
|
101
|
+
return posts.map((post) => ({ url: `/blog/${post.slug}`, lastModified: post.updatedAt }));
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Without the `connection()` line the same function runs once at build and the
|
|
106
|
+
answer is stored; the build's table shows `○` for a stored one and `ƒ` for
|
|
107
|
+
one that runs per request. Reading the database at build is fine — it is the
|
|
108
|
+
request that makes a route dynamic, not the data.
|
|
109
|
+
|
|
110
|
+
## How they are served
|
|
111
|
+
|
|
112
|
+
Each file becomes an api route, and that decides the rest. A `sitemap.ts`
|
|
113
|
+
that reads nothing per request — the database counts as nothing, the request
|
|
114
|
+
does not — is answered once at build and stored, like any frozen route, and
|
|
115
|
+
served from the file after. One that reads `cookies()` or awaits
|
|
116
|
+
`connection()` stays dynamic and runs per request. The build's table lists
|
|
117
|
+
them with the other routes and says which.
|
|
118
|
+
|
|
119
|
+
They run no middleware, on purpose: a guard on the root layout's directory
|
|
120
|
+
would otherwise answer a crawler's request for `robots.txt` with a 401.
|
|
121
|
+
|
|
122
|
+
The urls are typed like every other route, so `route('/sitemap.xml')` is a
|
|
123
|
+
link the build checks.
|
|
124
|
+
|
|
125
|
+
## Files as written
|
|
126
|
+
|
|
127
|
+
A hand-written file beside the root layout is served at the root as it is:
|
|
128
|
+
`robots.txt`, `sitemap.xml` (or `sitemap-posts.xml`), `llms.txt`,
|
|
129
|
+
`llms-full.txt`, and the others a site is asked for there — `humans.txt`,
|
|
130
|
+
`security.txt`, `ads.txt`. In development they are read from `app/`; a build
|
|
131
|
+
copies them beside the client output. A file and a function for the same url
|
|
132
|
+
is a build error naming both.
|
|
133
|
+
|
|
134
|
+
## Coming from Next
|
|
135
|
+
|
|
136
|
+
`app/robots.ts` and `app/sitemap.ts` carry across unchanged. `app/llms.txt`
|
|
137
|
+
in Next is usually a `route.ts`; here it is `llms.ts` with a shape, or a file
|
|
138
|
+
as written. Next's `generateSitemaps()` for a sitemap split across files is
|
|
139
|
+
not here: a `sitemap-posts.xml` written by hand, or a `route.ts` under
|
|
140
|
+
`app/sitemap/`, covers it.
|
package/guides/typed-routes.md
CHANGED
|
@@ -94,6 +94,15 @@ const nav = [
|
|
|
94
94
|
Without `satisfies`, TypeScript infers `string` for `href` and you lose the
|
|
95
95
|
check.
|
|
96
96
|
|
|
97
|
+
## The build checks
|
|
98
|
+
|
|
99
|
+
`vite build` runs the project's typecheck before it bundles, and stops on an
|
|
100
|
+
error — so a link to a route that does not exist is a failed build, not a 404
|
|
101
|
+
found after deploying. It is the same `tsc --noEmit` the `check` script runs,
|
|
102
|
+
on the same tsconfig, and it costs about a second. `rscKit({ typecheck: false })`
|
|
103
|
+
turns it off; a project with no `tsconfig.json`, or no `typescript`
|
|
104
|
+
installed, is skipped without being asked.
|
|
105
|
+
|
|
97
106
|
## If you never run the generator
|
|
98
107
|
|
|
99
108
|
`.rsc-kit/rsc-routes.d.ts` is written by the build. Without it — or with a
|
|
@@ -137,3 +146,20 @@ For end-to-end types without a fetch at all, a [server action or
|
|
|
137
146
|
query](/guides/queries/) is already typed across the boundary: the return type
|
|
138
147
|
is the function's, because it is the same function.
|
|
139
148
|
:::
|
|
149
|
+
|
|
150
|
+
## Regions
|
|
151
|
+
|
|
152
|
+
`revalidate()` in an action and `refresh()` in the browser take a region by
|
|
153
|
+
name, and the names are typed to the ones the build found: every
|
|
154
|
+
`section('orders', …)` and every `@slot` directory, plus `'page'` and
|
|
155
|
+
`'all'`. A typo, or a section renamed since, stops compiling instead of being
|
|
156
|
+
refused by the renderer at runtime:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
revalidate('orders') // a section the build found
|
|
160
|
+
revalidate('order') // does not compile
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
A name computed at runtime is cast, as an href is: `revalidate(name as
|
|
164
|
+
RevalidateTarget)`. A name that reaches the renderer unknown anyway is still
|
|
165
|
+
refused with the names it does know.
|
|
@@ -17,7 +17,7 @@ badly.
|
|
|
17
17
|
| a server action's result, put into state | yes |
|
|
18
18
|
| `<Form>` — errors, success, optimistic updates | yes |
|
|
19
19
|
| a streamed `<Suspense>` boundary arriving | yes, React does this on its own |
|
|
20
|
-
| navigating to another page | yes
|
|
20
|
+
| navigating to another page | yes — the update carries a transition type |
|
|
21
21
|
|
|
22
22
|
## What works
|
|
23
23
|
|
|
@@ -62,15 +62,61 @@ throughout, so everything it drives animates the same way.
|
|
|
62
62
|
|
|
63
63
|
## Navigating between pages
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
A navigation replaces the segment inside `startTransition`, so a
|
|
66
|
+
`<ViewTransition>` around it animates like any other. What you cannot tell
|
|
67
|
+
from outside is a navigation from the first commit after hydration — the
|
|
68
|
+
boundary taking over the server-rendered page, which animated would be the
|
|
69
|
+
page fading into itself on every load. So a navigation's update carries the
|
|
70
|
+
transition type `rsc-navigation`, and that first commit does not. Key the
|
|
71
|
+
boundary on it:
|
|
66
72
|
|
|
67
|
-
```
|
|
68
|
-
|
|
73
|
+
```tsx title="src/app/layout.tsx"
|
|
74
|
+
import { ViewTransition } from 'react';
|
|
75
|
+
|
|
76
|
+
export default function RootLayout({ children }) {
|
|
77
|
+
return (
|
|
78
|
+
<html lang="en">
|
|
79
|
+
<body className="min-h-full flex flex-col">
|
|
80
|
+
<ViewTransition default={{ 'rsc-navigation': 'page', default: 'none' }}>
|
|
81
|
+
<div className="flex flex-1 flex-col">{children}</div>
|
|
82
|
+
</ViewTransition>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
That is a server component, and it can be: `ViewTransition` is a built-in like
|
|
90
|
+
`Suspense`, which the payload carries as a symbol. No `'use client'` file is
|
|
91
|
+
needed for it.
|
|
92
|
+
|
|
93
|
+
`default: 'none'` is what makes a first load or a reload never animate. The
|
|
94
|
+
`<div>` matters (here it also carries the body's flex layout, so a `<main>`
|
|
95
|
+
inside still fills the height): React names each top-level element under a boundary and
|
|
96
|
+
animates every one on its own, so a page of five sections becomes five groups,
|
|
97
|
+
each morphing from where it was to where it is now. One wrapper element is one
|
|
98
|
+
snapshot pair — the old page and the new one dissolving into each other, the
|
|
99
|
+
way Inertia's does. A navbar inside the pair cross-fades into itself, which is
|
|
100
|
+
invisible; put the boundary around `<main>` in a nested layout instead when
|
|
101
|
+
you want the navbar left out of it entirely. Leave the wrapper out when you
|
|
102
|
+
want the per-element movement instead.
|
|
103
|
+
|
|
104
|
+
### Shaping the animation
|
|
105
|
+
|
|
106
|
+
The pair carries the class you chose, so your CSS shapes it there. The
|
|
107
|
+
browser's default is a 250 ms cross-fade:
|
|
108
|
+
|
|
109
|
+
```css title="src/app/styles.css"
|
|
110
|
+
/* Shorter, or none at all — the instant swap Inertia does by default. */
|
|
111
|
+
::view-transition-old(.page),
|
|
112
|
+
::view-transition-new(.page) {
|
|
113
|
+
animation-duration: 120ms;
|
|
114
|
+
}
|
|
69
115
|
```
|
|
70
116
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
117
|
+
`animation: none` on both is the instant swap, with the transition still in
|
|
118
|
+
place for the shared elements you name yourself (`<ViewTransition name="hero">`
|
|
119
|
+
still morphs).
|
|
74
120
|
|
|
75
121
|
## Coming back to a page you were just on
|
|
76
122
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Where it runs
|
|
2
|
+
|
|
3
|
+
> A host is a Nitro preset, not a server you write.
|
|
4
|
+
|
|
5
|
+
There used to be a page here for each runtime, and a server file generated to
|
|
6
|
+
match. There is no server file now, and no choice about it either:
|
|
7
|
+
[Nitro](https://nitro.build) builds the server around the route tree, and
|
|
8
|
+
choosing where an app runs is choosing a preset.
|
|
9
|
+
|
|
10
|
+
```ts title="vite.config.ts"
|
|
11
|
+
import { defineConfig } from 'vite';
|
|
12
|
+
import { nitro } from 'nitro/vite';
|
|
13
|
+
import react from '@vitejs/plugin-react';
|
|
14
|
+
import { rscKit } from '@rsc-kit/core/vite';
|
|
15
|
+
|
|
16
|
+
export default defineConfig({
|
|
17
|
+
plugins: [
|
|
18
|
+
nitro({ preset: 'bun', serveStatic: 'inline' }),
|
|
19
|
+
rscKit(),
|
|
20
|
+
react(),
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`bun create rsc-kit@latest my-app` writes that for you. Changing where it deploys is
|
|
26
|
+
changing the one string.
|
|
27
|
+
|
|
28
|
+
## The presets
|
|
29
|
+
|
|
30
|
+
`rsc-kit` asks about three, because those are the ones it can check for you.
|
|
31
|
+
Nitro carries [many more](https://nitro.build/deploy) and they need nothing from
|
|
32
|
+
this package — a preset is a string, not an integration.
|
|
33
|
+
|
|
34
|
+
| `--host` | preset | what you get |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `bun` | `bun` | `.output/server/index.mjs`, and `bun run compile` for a single binary |
|
|
37
|
+
| `node` | `node` | `.output/server/index.mjs`, run with `node` |
|
|
38
|
+
| `worker` | `cloudflare_module` | a Worker, plus the `wrangler.json` and `_headers` Nitro generates |
|
|
39
|
+
|
|
40
|
+
Everything else — Vercel, Netlify, Azure, Deno, AWS Amplify — is the same
|
|
41
|
+
change:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
nitro({ preset: 'vercel', serveStatic: 'inline' })
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Running it
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run dev # vite is the renderer; nothing is prebuilt
|
|
51
|
+
npm run build # writes .output/
|
|
52
|
+
npm run start # runs .output/server/index.mjs
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
A Worker has no `start`, because it is deployed rather than started:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run preview # wrangler dev, on workerd
|
|
59
|
+
npm run deploy # nitro deploy --prebuilt
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Compiling to a single binary
|
|
63
|
+
|
|
64
|
+
Bun only, and the whole application ends up inside one file — engine, route
|
|
65
|
+
tree and assets:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run compile # builds, then bun build --compile
|
|
69
|
+
./dist/app
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
It builds first on purpose. Compiling whatever `.output` happens to hold means
|
|
73
|
+
a binary one version behind the source with nothing to say so — and on a
|
|
74
|
+
project that has never been built, an `ENOENT` naming a path the app did not
|
|
75
|
+
write.
|
|
76
|
+
|
|
77
|
+
<Aside type="note" title="A Worker serves the frozen pages from a module">
|
|
78
|
+
A Worker has no filesystem, so the directory the build writes is not there
|
|
79
|
+
at request time. The build also writes the same pages as one module beside
|
|
80
|
+
the bundle, `rsc-static-inline.mjs`, which wrangler uploads with the rest
|
|
81
|
+
and the server imports when the directory is missing. Nothing to configure;
|
|
82
|
+
it is why `○` on a Worker means the stored page and not a live render that
|
|
83
|
+
happens to be the same. It counts toward the Worker's script size, which is
|
|
84
|
+
worth knowing on a site with hundreds of frozen pages.
|
|
85
|
+
</Aside>
|
|
86
|
+
|
|
87
|
+
<Aside type="note" title="Frozen pages stay outside the binary">
|
|
88
|
+
The build freezes pages into `.output/server/rsc-static` and the server reads
|
|
89
|
+
them from there. A compiled binary has no filesystem to read — the directory
|
|
90
|
+
is not embedded — so it renders those pages live instead. Everything still
|
|
91
|
+
answers; what you lose is the stored render, not the page.
|
|
92
|
+
</Aside>
|
|
93
|
+
|
|
94
|
+
<Aside type="caution" title="serveStatic: 'inline' is what makes this work">
|
|
95
|
+
Without it the binary compiles, starts, serves pages, and 404s every asset.
|
|
96
|
+
Inside a compiled binary the static path resolves into Bun's virtual
|
|
97
|
+
filesystem — where the files on disk are not — and the failure is
|
|
98
|
+
`ENOENT: /$bunfs/public/assets/…` with the pages themselves looking fine.
|
|
99
|
+
|
|
100
|
+
The generated config sets it. If you write your own, set it too.
|
|
101
|
+
</Aside>
|
|
102
|
+
|
|
103
|
+
## Offline
|
|
104
|
+
|
|
105
|
+
`rscKit({ offline: true })` writes a service worker into `.output/public`
|
|
106
|
+
alongside the assets, so a page someone has visited survives a reload with no
|
|
107
|
+
network. Off by default, and covered in [Offline](/guides/offline#surviving-without-one).
|
|
108
|
+
|
|
109
|
+
## Why `nitro` is pinned
|
|
110
|
+
|
|
111
|
+
The generated `package.json` pins an exact version rather than a range:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
"nitro": "3.0.260903-beta"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Nitro's own `latest` tag is a dated prerelease, and it sorts **above** the plain
|
|
118
|
+
`3.0.0` on npm. So `^3.0.0` resolves to the older release, which builds without
|
|
119
|
+
complaint and then answers 404 to every route. TanStack Start pins a dated beta
|
|
120
|
+
for the same reason.
|
|
121
|
+
|
|
122
|
+
## Assets are Nitro's
|
|
123
|
+
|
|
124
|
+
The build writes browser assets to `.output/public`, and Nitro serves them from
|
|
125
|
+
its own root. There is no `assetsDir` or `assetsUrl` to set: both were removed,
|
|
126
|
+
and `rscKit()` refuses a config that still passes them rather than reading a
|
|
127
|
+
prefix and ignoring it.
|
|
128
|
+
|
|
129
|
+
The alternative was the silent version — the markup asks for the app's prefix,
|
|
130
|
+
Nitro answers at its own, and the page arrives unstyled and never hydrates with
|
|
131
|
+
nothing logged anywhere.
|
|
132
|
+
|
|
133
|
+
Assets live in `.output/public` and are served by the same process that serves
|
|
134
|
+
your pages. If you want nginx or a CDN serving them instead, point it at
|
|
135
|
+
`.output/public` — that directory is the deployment.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
Next: [Deploying →](/hosts/deployment)
|
|
140
|
+
|
|
141
|
+
## Which entry point is whose
|
|
142
|
+
|
|
143
|
+
`@rsc-kit/core/host` is the engine's front door for a **host adapter** — the
|
|
144
|
+
handler, and beside it the few functions an adapter needs when it embeds the
|
|
145
|
+
engine. Several of those are re-exports of app-facing modules, so an editor's
|
|
146
|
+
auto-import may offer `redirect` or `revalidate` from `host`. They are the
|
|
147
|
+
same functions; an app imports them from their own entry points:
|
|
148
|
+
|
|
149
|
+
| in an app | not |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| `@rsc-kit/core/redirect` | `@rsc-kit/core/host` |
|
|
152
|
+
| `@rsc-kit/core/revalidate` | `@rsc-kit/core/host` |
|
|
153
|
+
|
|
154
|
+
Only an adapter imports `host`. The re-exports there are marked internal, so
|
|
155
|
+
they sort below the app entry in a completion list.
|