@rsc-kit/mcp 0.16.2 → 0.17.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/recipes.js +119 -4
- 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/coming-from-next.md +9 -4
- package/guides/emails.md +90 -0
- package/guides/errors.md +5 -1
- package/guides/images.md +10 -4
- package/guides/index.json +10 -0
- package/guides/installation.md +8 -9
- package/guides/mcp.md +1 -1
- package/guides/metadata.md +1 -1
- package/guides/offline.md +8 -0
- package/guides/routing.md +7 -1
- package/guides/seo-files.md +95 -0
- package/guides/typed-routes.md +9 -0
- package/guides/view-transitions.md +53 -7
- package/package.json +1 -1
package/guides/routing.md
CHANGED
|
@@ -301,7 +301,13 @@ a stored page, so `usePathname` just answers with the url being rendered.
|
|
|
301
301
|
|
|
302
302
|
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
|
-
throws rather than pretending it is empty.
|
|
304
|
+
throws rather than pretending it is empty. The dev server does the same, on
|
|
305
|
+
purpose: a page has one shape, and a boundary missing in dev is the one the
|
|
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.
|
|
305
311
|
|
|
306
312
|
Wrap it, and the throw becomes the fallback:
|
|
307
313
|
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
## How they are served
|
|
66
|
+
|
|
67
|
+
Each file becomes an api route, and that decides the rest. A `sitemap.ts`
|
|
68
|
+
that reads nothing per request — the database counts as nothing, the request
|
|
69
|
+
does not — is answered once at build and stored, like any frozen route, and
|
|
70
|
+
served from the file after. One that reads `cookies()` or awaits
|
|
71
|
+
`connection()` stays dynamic and runs per request. The build's table lists
|
|
72
|
+
them with the other routes and says which.
|
|
73
|
+
|
|
74
|
+
They run no middleware, on purpose: a guard on the root layout's directory
|
|
75
|
+
would otherwise answer a crawler's request for `robots.txt` with a 401.
|
|
76
|
+
|
|
77
|
+
The urls are typed like every other route, so `route('/sitemap.xml')` is a
|
|
78
|
+
link the build checks.
|
|
79
|
+
|
|
80
|
+
## Files as written
|
|
81
|
+
|
|
82
|
+
A hand-written file beside the root layout is served at the root as it is:
|
|
83
|
+
`robots.txt`, `sitemap.xml` (or `sitemap-posts.xml`), `llms.txt`,
|
|
84
|
+
`llms-full.txt`, and the others a site is asked for there — `humans.txt`,
|
|
85
|
+
`security.txt`, `ads.txt`. In development they are read from `app/`; a build
|
|
86
|
+
copies them beside the client output. A file and a function for the same url
|
|
87
|
+
is a build error naming both.
|
|
88
|
+
|
|
89
|
+
## Coming from Next
|
|
90
|
+
|
|
91
|
+
`app/robots.ts` and `app/sitemap.ts` carry across unchanged. `app/llms.txt`
|
|
92
|
+
in Next is usually a `route.ts`; here it is `llms.ts` with a shape, or a file
|
|
93
|
+
as written. Next's `generateSitemaps()` for a sitemap split across files is
|
|
94
|
+
not here: a `sitemap-posts.xml` written by hand, or a `route.ts` under
|
|
95
|
+
`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
|
|
@@ -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
|
|
package/package.json
CHANGED