@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/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.
@@ -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, behind a flag |
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
- Off by default, because it changes how every navigation commits:
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
- ```ts title="vite.config.ts"
68
- rscKit({ viewTransitions: true })
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
- A build-time constant rather than a runtime setting, so an app that does not
72
- ask for it does not carry the boundary at all. What it animates is the segment
73
- a navigation replaces; what a page does inside itself needs no flag.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsc-kit/mcp",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
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",