@alxia/react-router 0.12.0 → 0.12.1

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/README.md CHANGED
@@ -300,12 +300,17 @@ paint:
300
300
  ```tsx
301
301
  // app/root.tsx
302
302
  import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router';
303
- import { CspNonceMeta, InlineScript } from '@alxia/react-router/document';
303
+ import {
304
+ CspNonceMeta,
305
+ InlineScript,
306
+ useNonce,
307
+ } from '@alxia/react-router/document';
304
308
  import { themeBootScript } from '<your theme library>';
305
309
 
306
310
  const THEME_BOOT = themeBootScript();
307
311
 
308
312
  export function Layout({ children }: { children: React.ReactNode }) {
313
+ const nonce = useNonce();
309
314
  return (
310
315
  <html lang="en" suppressHydrationWarning>
311
316
  <head>
@@ -313,7 +318,7 @@ export function Layout({ children }: { children: React.ReactNode }) {
313
318
  <InlineScript code={THEME_BOOT} />
314
319
  <CspNonceMeta />
315
320
  <Meta />
316
- <Links />
321
+ <Links nonce={nonce} />
317
322
  </head>
318
323
  <body>
319
324
  {children}
@@ -329,10 +334,12 @@ export default function App() {
329
334
  }
330
335
  ```
331
336
 
332
- `THEME_BOOT` is your theme library's own boot script. With `/entry`, `<Links>`,
333
- `<Scripts>` and `<ScrollRestoration>` take the nonce from `<ServerRouter nonce>`,
334
- so their `nonce` props are optional; `useNonce()` stays for `InlineScript`,
335
- `CspNonceMeta` and any script or style the app inserts. An entry of your own
337
+ `THEME_BOOT` is your theme library's own boot script. With `/entry`,
338
+ `<Scripts>` and `<ScrollRestoration>` take the nonce from `<ServerRouter nonce>`
339
+ and need no prop. `<Links>` does not suppress the hydration warning for its
340
+ critical-CSS `<style nonce>` in development, so pass it `useNonce()`, as above;
341
+ `useNonce()` also serves `InlineScript`, `CspNonceMeta` and any script or style
342
+ the app inserts. An entry of your own
336
343
  wraps the router:
337
344
 
338
345
  ```tsx
@@ -64,11 +64,13 @@ export {
64
64
  It reads `nonceOf(loadContext)` by default; `createEntry({ nonce })`
65
65
  takes another source ([the entry](document.md#the-entry)).
66
66
 
67
- The root passes nothing: `<Links>`, `<Scripts>` and `<ScrollRestoration>`
68
- fall back to the nonce `<ServerRouter nonce>` put in React Router's
69
- framework context, so their `nonce` props are optional with this entry.
70
- `useNonce()` stays for what the app inserts itself: an `InlineScript`, a
71
- `CspNonceMeta`, any other script or style.
67
+ `<Scripts>` and `<ScrollRestoration>` fall back to the nonce `<ServerRouter
68
+ nonce>` put in React Router's framework context, so they need no `nonce` prop
69
+ with this entry. `<Links>` needs `<Links nonce={useNonce()} />`: it does not
70
+ suppress the hydration warning for its development critical-CSS `<style>`
71
+ ([why](document.md#what-react-router-renders)). `useNonce()` also serves what
72
+ the app inserts itself: an `InlineScript`, a `CspNonceMeta`, any other script
73
+ or style.
72
74
 
73
75
  ### An entry of your own
74
76
 
@@ -61,19 +61,23 @@ tests: reset modules between them, or render the server path with
61
61
 
62
62
  ## What React Router renders
63
63
 
64
- With `@alxia/react-router/entry`, the root passes nothing. `<Links>`,
65
- `<Scripts>` and `<ScrollRestoration>` fall back to the nonce `<ServerRouter
66
- nonce>` put in React Router's framework context, so their `nonce` props are
67
- optional:
64
+ With `@alxia/react-router/entry`, `<Scripts>` and `<ScrollRestoration>` need
65
+ no `nonce`: they fall back to the nonce `<ServerRouter nonce>` put in React
66
+ Router's framework context, and they suppress the hydration warning for the
67
+ attribute the browser hides. `<Links>` falls back to it on the server too, but
68
+ does not suppress the warning, so pass it `useNonce()`:
68
69
 
69
70
  ```tsx
70
71
  // app/root.tsx
72
+ import { useNonce } from '@alxia/react-router/document';
73
+
71
74
  export function Layout({ children }: { children: React.ReactNode }) {
75
+ const nonce = useNonce();
72
76
  return (
73
77
  <html lang="en">
74
78
  <head>
75
79
  <meta charSet="utf-8" />
76
- <Links />
80
+ <Links nonce={nonce} />
77
81
  </head>
78
82
  <body>
79
83
  {children}
@@ -85,6 +89,13 @@ export function Layout({ children }: { children: React.ReactNode }) {
85
89
  }
86
90
  ```
87
91
 
92
+ Why `<Links>` needs it: in development, React Router renders the critical CSS
93
+ as a `<style nonce>`. The server writes the nonce on it, the browser hides the
94
+ attribute and React Router's client knows none, so without the prop the client
95
+ renders the `<style>` with no nonce and React reports a mismatch
96
+ ([Troubleshooting](../troubleshooting.md#a-tree-hydrated-but-some-attributes-of-the-server-rendered-html-didnt-match-the-client-properties-on-a-style-with-a-nonce)).
97
+ `useNonce()` returns the same value on both sides.
98
+
88
99
  Keep `useNonce()` for what React Router does not render: an `InlineScript`,
89
100
  a `CspNonceMeta`, and any script or style the app inserts itself.
90
101
 
@@ -28,6 +28,7 @@ a loader, a message React Router or the browser prints, or an error from
28
28
  - [`Refused to execute inline script because it violates the following Content Security Policy directive: "default-src 'none'"`](#refused-to-execute-inline-script-because-it-violates-the-following-content-security-policy-directive-default-src-none)
29
29
  - [`Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-…'"`](#refused-to-execute-inline-script-because-it-violates-the-following-content-security-policy-directive-script-src-self-nonce-)
30
30
  - [`A tree hydrated but some attributes of the server rendered HTML didn't match the client properties`](#a-tree-hydrated-but-some-attributes-of-the-server-rendered-html-didnt-match-the-client-properties)
31
+ - [`A tree hydrated but some attributes of the server rendered HTML didn't match the client properties` on a `<style>` with a `nonce`](#a-tree-hydrated-but-some-attributes-of-the-server-rendered-html-didnt-match-the-client-properties-on-a-style-with-a-nonce)
31
32
  - [`useNonce()` is undefined on the server](#usenonce-is-undefined-on-the-server)
32
33
  - [`TypeError: InlineScript(): the code contains "</script" or "<!--", which ends or confuses the script element; escape it, such as "<\/script" inside a string`](#typeerror-inlinescript-the-code-contains-script-or----which-ends-or-confuses-the-script-element-escape-it-such-as-script-inside-a-string)
33
34
  - [`TypeError: createPageLanguage(): the fallback … is not one of the languages; got ….`](#typeerror-createpagelanguage-the-fallback--is-not-one-of-the-languages-got-)
@@ -603,6 +604,40 @@ A warning on another element is a real mismatch: a `nonce` the browser hid
603
604
  is read by `useNonce()` from the document, so never pass it from loader
604
605
  data ([guide](guide/document.md#the-theme-boot-script)).
605
606
 
607
+ ### `A tree hydrated but some attributes of the server rendered HTML didn't match the client properties` on a `<style>` with a `nonce`
608
+
609
+ **When:** in development (`react-router dev`) with a CSP nonce, the console
610
+ warns that a tree hydrated but some attributes did not match, and the diff
611
+ shows `nonce="…"` on the `<style data-react-router-critical-css>` in `<head>`.
612
+
613
+ **Why:** React Router renders the critical CSS as a `<style nonce>`. The
614
+ server writes the nonce from `<ServerRouter nonce>`, the browser hides the
615
+ attribute, and `<Links />` has no nonce to render on the client. Unlike
616
+ `<Scripts>` and `<ScrollRestoration>`, `<Links>` does not suppress that
617
+ warning.
618
+
619
+ **Fix:** pass it the nonce `useNonce()` reads, on both sides:
620
+
621
+ ```tsx
622
+ // app/root.tsx
623
+ import { useNonce } from '@alxia/react-router/document';
624
+
625
+ export function Layout({ children }: { children: React.ReactNode }) {
626
+ const nonce = useNonce();
627
+ return (
628
+ <html lang="en">
629
+ <head>
630
+ <Links nonce={nonce} />
631
+ </head>
632
+ <body>{children}</body>
633
+ </html>
634
+ );
635
+ }
636
+ ```
637
+
638
+ `<Scripts>` and `<ScrollRestoration>` need nothing
639
+ ([guide](guide/document.md#what-react-router-renders)).
640
+
606
641
  ### `useNonce()` is undefined on the server
607
642
 
608
643
  **When:** the page's inline scripts or `<CspNonceMeta />` render with no
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alxia/react-router",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "description": "alxia as the server of a React Router framework app: server rendering behind the app's hooks, loaders reading its typed context, the client's assets served; the code with no UI an installable app shares (`/pwa`), its hooks (`/pwa/react`), `noCache` for the worker's script (`/pwa/server`), the service worker on Workbox (`/pwa/worker`) and its build (`/pwa/build`), an API's refusals worded for the page (`/errors`), the page's CSP nonce and inline boot scripts (`/document`) and a streaming `entry.server` (`/entry`)",
5
5
  "license": "MIT",
6
6
  "type": "module",