@rsc-kit/mcp 0.13.1 → 0.15.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.
Files changed (47) hide show
  1. package/dist/answers.d.ts +7 -0
  2. package/dist/answers.js +26 -0
  3. package/dist/answers.js.map +1 -1
  4. package/dist/bundleGuides.d.ts +22 -0
  5. package/dist/bundleGuides.js +130 -0
  6. package/dist/bundleGuides.js.map +1 -0
  7. package/dist/index.js +21 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/recipes.js +239 -0
  10. package/dist/recipes.js.map +1 -1
  11. package/dist/report.d.ts +11 -0
  12. package/dist/report.js +1 -1
  13. package/dist/report.js.map +1 -1
  14. package/guides/api-routes.md +168 -0
  15. package/guides/authorization.md +288 -0
  16. package/guides/caching.md +57 -0
  17. package/guides/connection.md +98 -0
  18. package/guides/edge-caching.md +159 -0
  19. package/guides/errors.md +109 -0
  20. package/guides/file-uploads.md +119 -0
  21. package/guides/fonts.md +117 -0
  22. package/guides/forms.md +528 -0
  23. package/guides/images.md +83 -0
  24. package/guides/index.json +162 -0
  25. package/guides/mcp.md +113 -0
  26. package/guides/metadata.md +289 -0
  27. package/guides/navigation.md +84 -0
  28. package/guides/no-javascript.md +39 -0
  29. package/guides/offline.md +215 -0
  30. package/guides/ppr.md +181 -0
  31. package/guides/pwa.md +260 -0
  32. package/guides/queries.md +340 -0
  33. package/guides/react-compiler.md +153 -0
  34. package/guides/redirects.md +143 -0
  35. package/guides/response-headers.md +66 -0
  36. package/guides/route-interception.md +206 -0
  37. package/guides/routing.md +458 -0
  38. package/guides/sections.md +74 -0
  39. package/guides/server-actions.md +444 -0
  40. package/guides/static-generation.md +347 -0
  41. package/guides/testing.md +158 -0
  42. package/guides/third-party-scripts.md +105 -0
  43. package/guides/typed-routes.md +139 -0
  44. package/guides/url-validation.md +143 -0
  45. package/guides/validation.md +175 -0
  46. package/guides/view-transitions.md +120 -0
  47. package/package.json +4 -3
@@ -0,0 +1,109 @@
1
+ # Errors and 404s
2
+
3
+ > What a visitor sees when a page throws, or asks for a url nothing answers.
4
+
5
+ Two files, both optional, both following the same rule as `loading.tsx`: the
6
+ nearest one wins.
7
+
8
+ ## When a page throws
9
+
10
+ Put an `error.tsx` in the directory you want to cover:
11
+
12
+ ```tsx title="src/app/orders/error.tsx"
13
+ 'use client'
14
+
15
+ import type { RouteErrorProps } from '@rsc-kit/core/RouteErrorBoundary'
16
+
17
+ export default function OrdersError({ error, reset }: RouteErrorProps) {
18
+ return (
19
+ <section>
20
+ <h1>That did not work</h1>
21
+ <p>{error.message}</p>
22
+ <button type="button" onClick={reset}>Try again</button>
23
+ </section>
24
+ )
25
+ }
26
+ ```
27
+
28
+ Anything below it that throws renders this instead. The layouts above stay on
29
+ screen — only the segment that failed is replaced.
30
+
31
+ `reset()` renders the segment again, for a failure that might not happen twice.
32
+ Navigating away clears it on its own.
33
+
34
+ :::caution[It has to be a client component]
35
+ `'use client'` at the top, or the build refuses it. Catching a render error is
36
+ a class component's job, it runs in the browser, and `reset` is a callback the
37
+ browser calls — none of which a server component can do.
38
+ :::
39
+
40
+ ### What `error.message` says in production
41
+
42
+ React replaces it with a generic sentence and puts a hash on `error.digest`.
43
+ That is React, not this package: the real message could name a table, a query or
44
+ a path, and none of that should reach a browser.
45
+
46
+ Log the digest where you log the error, and the two line up.
47
+
48
+ ### It does not catch everything
49
+
50
+ - **Errors in the layout above it.** The boundary sits inside that layout, so a
51
+ layout that throws needs an `error.tsx` a directory up.
52
+ - **The build.** A page that throws every time it renders fails the build
53
+ rather than shipping a stored error page. The boundary is for a request that
54
+ goes wrong, not a page that is broken.
55
+
56
+ ## When nothing answers the url
57
+
58
+ `src/app/not-found.tsx` is rendered for any url no route matches:
59
+
60
+ ```tsx title="src/app/not-found.tsx"
61
+ import Link from '@rsc-kit/core/Link'
62
+
63
+ export default function NotFound() {
64
+ return (
65
+ <main>
66
+ <h1>No such page</h1>
67
+ <p>Nothing answers that url. <Link href="/">Go home</Link>.</p>
68
+ </main>
69
+ )
70
+ }
71
+ ```
72
+
73
+ It renders through your root layout like any other page, and is served with a
74
+ real **404** — a page that says "not found" under a `200` is a page search
75
+ engines index.
76
+
77
+ An ordinary server component, so it can be `async` and read whatever it likes.
78
+ Without one, an unmatched url gets a plain `Not found` string.
79
+
80
+ ### When the page has to decide
81
+
82
+ A url can match a route and still name nothing — `/posts/42` is a real route
83
+ and there may be no post 42. Only the page can know that, so the page says so:
84
+
85
+ ```tsx title="src/app/posts/[slug]/page.tsx"
86
+ import { notFound } from '@rsc-kit/core/not-found'
87
+
88
+ export default async function PostPage({ params }) {
89
+ const post = await findPost((await params).slug)
90
+
91
+ if (!post) notFound()
92
+
93
+ return <article>{post.title}</article>
94
+ }
95
+ ```
96
+
97
+ Same page, same **404**. It throws, so nothing after it runs — and if you wrap
98
+ the call in a `try`/`catch`, rethrow what you do not recognise, or a missing
99
+ page becomes a blank region.
100
+
101
+ :::caution[Where you call it decides the status]
102
+ Called above every Suspense boundary — at the top of a page, in a layout —
103
+ nothing has been written yet, so the response is a real 404.
104
+
105
+ Called deeper, inside a boundary, the shell has already gone out with the
106
+ status line on it. The boundary shows its fallback instead and the status stays
107
+ `200`. If the status matters — and for anything a crawler or a cache sees, it
108
+ does — do the lookup above the boundary.
109
+ :::
@@ -0,0 +1,119 @@
1
+ # File uploads
2
+
3
+ > Sending files through a server action without encoding them.
4
+
5
+ A `File` is a value a server action can take. React's Flight format carries it
6
+ as binary, so there is nothing to encode on the way out and nothing to decode
7
+ on the way in.
8
+
9
+ ## One file
10
+
11
+ ```tsx title="src/components/AvatarUpload.tsx"
12
+ "use client";
13
+
14
+ import { useState } from 'react';
15
+ import { uploadAvatar } from '../actions';
16
+
17
+ export function AvatarUpload() {
18
+ const [url, setUrl] = useState<string | null>(null);
19
+
20
+ async function submit(formData: FormData) {
21
+ const file = formData.get('avatar') as File;
22
+
23
+ if (!file || file.size === 0) return;
24
+
25
+ setUrl(await uploadAvatar(file));
26
+ }
27
+
28
+ return (
29
+ <form action={submit}>
30
+ <input type="file" name="avatar" accept="image/*" />
31
+ <button type="submit">Upload</button>
32
+ {url && <img src={url} alt="" />}
33
+ </form>
34
+ );
35
+ }
36
+ ```
37
+
38
+ The action receives the `File` itself — name, type, size and all:
39
+
40
+ ```ts title="src/actions.ts"
41
+ 'use server'
42
+
43
+ import { writeFile } from 'node:fs/promises';
44
+
45
+ export async function uploadAvatar(file: File): Promise<string> {
46
+ const bytes = new Uint8Array(await file.arrayBuffer());
47
+
48
+ await writeFile(`./public/avatars/${file.name}`, bytes);
49
+
50
+ return `/avatars/${file.name}`;
51
+ }
52
+ ```
53
+
54
+ Nothing about that is upload-specific: it is the same call, the same
55
+ serialisation and the same round trip as [any other server
56
+ action](/guides/server-actions).
57
+
58
+ ## The whole form at once
59
+
60
+ An action can take the `FormData` instead of individual arguments, which is
61
+ usually simpler when the form mixes files and fields:
62
+
63
+ ```tsx
64
+ "use client";
65
+
66
+ import { createPost } from '../actions';
67
+
68
+ export function NewPost() {
69
+ return (
70
+ <form action={createPost}>
71
+ <input name="title" />
72
+ <textarea name="body" />
73
+ <input type="file" name="cover" accept="image/*" />
74
+ <button type="submit">Create</button>
75
+ </form>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ```ts title="src/actions.ts"
81
+ 'use server'
82
+
83
+ export async function createPost(formData: FormData) {
84
+ const title = formData.get('title') as string;
85
+ const cover = formData.get('cover') as File;
86
+
87
+ // …
88
+ }
89
+ ```
90
+
91
+ ## Several files
92
+
93
+ `formData.getAll` gives every file from a `multiple` input, and an array of
94
+ `File` serialises as readily as one:
95
+
96
+ ```tsx
97
+ const files = formData.getAll('photos') as File[];
98
+
99
+ await uploadPhotos(files);
100
+ ```
101
+
102
+ ```ts title="src/actions.ts"
103
+ 'use server'
104
+
105
+ export async function uploadPhotos(files: File[]) {
106
+ for (const file of files) {
107
+ // …
108
+ }
109
+ }
110
+ ```
111
+
112
+ ## Size
113
+
114
+ The whole body is buffered before the action runs — it is one `POST`, not a
115
+ stream — so an upload occupies memory for as long as it takes. Two consequences
116
+ worth planning for:
117
+
118
+ - Keep any limit your host imposes as low as the app actually needs.
119
+ - For genuinely large files, do not send them through an action at all. Have the action mint a pre-signed URL and let the browser upload straight to storage; the bytes never touch your server.
@@ -0,0 +1,117 @@
1
+ # Fonts
2
+
3
+ > Self-hosted fonts from npm, and what next/font was doing for you.
4
+
5
+ Install the font, import its CSS, name it in a variable. Vite does the rest.
6
+
7
+ ```sh
8
+ bun add @fontsource-variable/fraunces @fontsource-variable/geist
9
+ ```
10
+
11
+ ```css title="src/app/styles.css"
12
+ @import '@fontsource-variable/fraunces/full.css';
13
+ @import '@fontsource-variable/fraunces/full-italic.css';
14
+ @import '@fontsource-variable/geist';
15
+
16
+ :root {
17
+ --font-display: 'Fraunces Variable', ui-serif, Georgia, Cambria, 'Times New Roman', serif;
18
+ --font-sans: 'Geist Variable', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
19
+ 'Helvetica Neue', Arial, sans-serif,
20
+ 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
21
+ }
22
+ ```
23
+
24
+ Put your font in *front* of a full stack rather than in place of one. Two things
25
+ the rest of the stack does that a bare `sans-serif` does not:
26
+
27
+ - **Emoji.** Geist has no emoji glyphs, so an emoji falls through to whatever is
28
+ named after it. With nothing there, some Linux and older Android setups draw a
29
+ monochrome glyph or a box. The four emoji fonts at the end are consulted only
30
+ for characters the main font cannot draw, so they cost nothing.
31
+ - **A closer fallback.** During the `swap` window the browser shows the next
32
+ font. `-apple-system` and `Segoe UI` are nearer Geist in width and x-height
33
+ than Helvetica or Arial, so the moment of swap is a smaller jolt.
34
+
35
+ Those are Tailwind's own defaults, and shadcn's generated `--font-sans:
36
+ 'Geist Variable', sans-serif` drops both. Keep the stack.
37
+
38
+ [Fontsource](https://fontsource.org) packages every Google font — and many
39
+ others — as woff2 files with the `@font-face` rules already written. Vite
40
+ hashes the files and serves them beside your other assets, so **nothing is
41
+ fetched from Google at runtime**, and nothing is downloaded at build time
42
+ either. The files are in `node_modules`.
43
+
44
+ ## Porting from `next/font`
45
+
46
+ ```ts
47
+ // before
48
+ const fraunces = Fraunces({
49
+ variable: '--font-display',
50
+ subsets: ['latin'],
51
+ style: ['normal', 'italic'],
52
+ axes: ['SOFT', 'opsz'],
53
+ display: 'swap',
54
+ })
55
+ ```
56
+
57
+ Every option there is something Fontsource already did:
58
+
59
+ | `next/font` | Fontsource |
60
+ | --- | --- |
61
+ | `variable: '--font-display'` | the `:root` rule above — you write the variable, once |
62
+ | `subsets: ['latin']` | every subset ships, each with a `unicode-range`; the browser fetches only the ones the page uses |
63
+ | `style: ['normal', 'italic']` | `full.css` and `full-italic.css` |
64
+ | `axes: ['SOFT', 'opsz']` | `full.css` — every axis of the variable font; `standard.css` is weight alone |
65
+ | `display: 'swap'` | already in every rule |
66
+ | `className={fraunces.variable}` | nothing — the variable is on `:root` |
67
+
68
+ So the layout loses its font imports and the `className` gains nothing:
69
+
70
+ ```tsx title="src/app/layout.tsx"
71
+ import './styles.css'
72
+
73
+ export default function RootLayout({ children }) {
74
+ return (
75
+ <html lang="en" className="h-full antialiased dark font-sans">
76
+
77
+ </html>
78
+ )
79
+ }
80
+ ```
81
+
82
+ The subsetting is worth a second look, because it is *better* than what you
83
+ had. `next/font` asks you to name subsets at build time and ships those.
84
+ Fontsource ships all of them, each behind a `unicode-range`, and the browser
85
+ downloads only the ones a page's text actually needs — a Latin-only page
86
+ fetches one file whatever else is installed.
87
+
88
+ ## Preloading
89
+
90
+ The one thing `next/font` did that needs a line here. It added a `<link
91
+ rel="preload">` for each font so the browser finds it before the stylesheet
92
+ does. Without one, the font is discovered when the CSS is parsed, and the text
93
+ shows in the fallback face for a moment longer.
94
+
95
+ Import the file for its url, and React hoists the link:
96
+
97
+ ```tsx title="src/app/layout.tsx"
98
+ import fraunces from '@fontsource-variable/fraunces/files/fraunces-latin-full-normal.woff2?url'
99
+
100
+ export default function RootLayout({ children }) {
101
+ return (
102
+ <html>
103
+ <head>
104
+ <link rel="preload" href={fraunces} as="font" type="font/woff2" crossOrigin="anonymous" />
105
+ </head>
106
+
107
+ ```
108
+
109
+ `?url` is Vite's — it hands back the hashed path the build will serve. Preload
110
+ the one file the first paint needs, usually the Latin regular; preloading all
111
+ of them defeats the subsetting.
112
+
113
+ ## A font that is not on npm
114
+
115
+ A file you own goes in `public/` and gets its own `@font-face` in your CSS, the
116
+ same as anywhere. Or in `src/` and imported with `?url` as above, which hashes
117
+ it.