@rangojs/router 0.31.0 → 0.33.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/README.md +3 -2
- package/dist/bin/browser-7KIJ5HSV.js +15 -0
- package/dist/bin/chunk-3B7CKBRO.js +463 -0
- package/dist/bin/chunk-524VACCG.js +71 -0
- package/dist/bin/chunk-LD7UBHT4.js +196 -0
- package/dist/bin/chunk-TO6CRWLN.js +1429 -0
- package/dist/bin/chunk-TTXC2U3G.js +2477 -0
- package/dist/bin/client-urls-projection-DQLWPDAT.js +28 -0
- package/dist/bin/forward-user-plugins-URBZKJP4.js +62 -0
- package/dist/bin/rango.js +42 -10143
- package/dist/bin/runtime-EPUDEHO4.js +596 -0
- package/dist/bin/runtime-discovery-CZ2QHT4Q.js +352 -0
- package/dist/bin/state-ACD2MGKJ.js +16 -0
- package/dist/bin/temp-rsc-server-QRR6HD6T.js +3190 -0
- package/dist/types/bin/rango.d.ts +1 -0
- package/dist/types/browser/action-fence.d.ts +20 -0
- package/dist/types/browser/cookie-name.d.ts +51 -10
- package/dist/types/browser/history-state.d.ts +13 -2
- package/dist/types/browser/invalidate-client-cache.d.ts +8 -6
- package/dist/types/browser/navigation-bridge.d.ts +36 -1
- package/dist/types/browser/navigation-client.d.ts +19 -0
- package/dist/types/browser/navigation-store-handle.d.ts +37 -4
- package/dist/types/browser/navigation-store.d.ts +2 -2
- package/dist/types/browser/navigation-transaction.d.ts +0 -1
- package/dist/types/browser/partial-update.d.ts +13 -0
- package/dist/types/browser/prefetch/cache.d.ts +96 -8
- package/dist/types/browser/prefetch/fetch.d.ts +26 -2
- package/dist/types/browser/prefetch/queue.d.ts +3 -2
- package/dist/types/browser/rango-state.d.ts +34 -5
- package/dist/types/browser/react/Link.d.ts +19 -13
- package/dist/types/browser/react/location-state-shared.d.ts +2 -1
- package/dist/types/browser/types.d.ts +25 -0
- package/dist/types/build/generate-route-types.d.ts +1 -1
- package/dist/types/build/route-types/include-resolution.d.ts +9 -1
- package/dist/types/build/route-types/router-processing.d.ts +3 -1
- package/dist/types/cache/cache-key-utils.d.ts +12 -8
- package/dist/types/cache/cache-scope.d.ts +4 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +9 -5
- package/dist/types/cache/document-cache-policy.d.ts +9 -1
- package/dist/types/cache/memory-segment-store.d.ts +3 -2
- package/dist/types/cache/segment-codec.d.ts +2 -2
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/types.d.ts +12 -7
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -1
- package/dist/types/client-urls/types.d.ts +7 -5
- package/dist/types/prefetch-markers.d.ts +9 -0
- package/dist/types/prefetch-source-scope.d.ts +66 -0
- package/dist/types/route-definition/helpers-types.d.ts +8 -5
- package/dist/types/route-definition/redirect.d.ts +5 -0
- package/dist/types/router/intercept-resolution.d.ts +25 -11
- package/dist/types/router/match-context.d.ts +1 -0
- package/dist/types/router/match-handlers.d.ts +1 -1
- package/dist/types/router/middleware-types.d.ts +6 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +11 -3
- package/dist/types/router/router-context.d.ts +1 -1
- package/dist/types/router/router-options.d.ts +6 -3
- package/dist/types/router/segment-resolution/loader-cache.d.ts +8 -0
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +23 -7
- package/dist/types/router/segment-resolution/revalidation.d.ts +19 -0
- package/dist/types/router/source-reads.d.ts +53 -0
- package/dist/types/router/types.d.ts +1 -1
- package/dist/types/rsc/helpers.d.ts +8 -3
- package/dist/types/rsc/prefetch-render-error.d.ts +7 -2
- package/dist/types/rsc/request-stages.d.ts +4 -0
- package/dist/types/rsc/shell-serve.d.ts +6 -1
- package/dist/types/server/context.d.ts +23 -6
- package/dist/types/server/cookie-store.d.ts +15 -1
- package/dist/types/server/document-location-state.d.ts +59 -0
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +65 -12
- package/dist/types/testing/e2e/server.d.ts +16 -1
- package/dist/types/testing/render-route.d.ts +83 -2
- package/dist/types/testing/serve-shell-request.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +21 -4
- package/dist/types/types/loader-types.d.ts +7 -4
- package/dist/types/types/segments.d.ts +18 -1
- package/dist/types/urls/path-helper-types.d.ts +7 -5
- package/dist/types/vite/temp-rsc-server.d.ts +2 -2
- package/dist/types/vite/utils/forward-user-plugins.d.ts +32 -2
- package/dist/vite/index.js +451 -191
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +74 -0
- package/skills/app-structure/e-commerce.md +10 -9
- package/skills/app-structure/large-app.md +15 -2
- package/skills/caching/SKILL.md +10 -5
- package/skills/client-urls/SKILL.md +18 -11
- package/skills/comparison/references/framework-comparison.md +16 -10
- package/skills/document-cache/SKILL.md +13 -0
- package/skills/hooks/navigation.md +8 -8
- package/skills/hooks/state.md +132 -12
- package/skills/intercept/SKILL.md +33 -11
- package/skills/layout/SKILL.md +11 -0
- package/skills/links/SKILL.md +101 -4
- package/skills/loader/SKILL.md +49 -22
- package/skills/migrate-nextjs/SKILL.md +8 -2
- package/skills/migrate-react-router/data-and-actions.md +5 -0
- package/skills/ppr/SKILL.md +38 -5
- package/skills/prerender/SKILL.md +4 -1
- package/skills/route/SKILL.md +6 -6
- package/skills/router-setup/SKILL.md +2 -1
- package/skills/server-actions/SKILL.md +49 -8
- package/skills/testing/client-components.md +73 -32
- package/skills/testing/serve-shell-request.md +45 -20
- package/skills/testing/server-actions.md +8 -0
- package/skills/ux-architecture/SKILL.md +67 -15
- package/src/bin/rango.ts +39 -13
- package/src/browser/action-fence.ts +72 -0
- package/src/browser/cookie-name.ts +82 -14
- package/src/browser/history-state.ts +24 -2
- package/src/browser/invalidate-client-cache.ts +9 -7
- package/src/browser/navigation-bridge.ts +164 -32
- package/src/browser/navigation-client.ts +95 -20
- package/src/browser/navigation-store-handle.ts +67 -5
- package/src/browser/navigation-store.ts +86 -17
- package/src/browser/navigation-transaction.ts +0 -6
- package/src/browser/partial-update.ts +31 -12
- package/src/browser/prefetch/cache.ts +188 -11
- package/src/browser/prefetch/fetch.ts +211 -51
- package/src/browser/prefetch/queue.ts +3 -2
- package/src/browser/rango-state.ts +50 -8
- package/src/browser/react/Link.tsx +24 -16
- package/src/browser/react/location-state-shared.ts +8 -1
- package/src/browser/react/use-navigation.ts +1 -1
- package/src/browser/rsc-router.tsx +13 -3
- package/src/browser/server-action-bridge.ts +3 -3
- package/src/browser/types.ts +25 -0
- package/src/build/generate-route-types.ts +1 -0
- package/src/build/route-types/include-resolution.ts +40 -3
- package/src/build/route-types/router-processing.ts +170 -25
- package/src/build/route-types/scan-filter.ts +1 -0
- package/src/build/runtime-discovery.ts +46 -8
- package/src/cache/cache-key-utils.ts +12 -8
- package/src/cache/cache-runtime.ts +5 -1
- package/src/cache/cache-scope.ts +28 -7
- package/src/cache/cf/cf-cache-types.ts +9 -5
- package/src/cache/document-cache-policy.ts +37 -1
- package/src/cache/document-cache.ts +12 -0
- package/src/cache/memory-segment-store.ts +3 -2
- package/src/cache/segment-codec.ts +2 -2
- package/src/cache/shell-snapshot.ts +1 -1
- package/src/cache/types.ts +12 -7
- package/src/cache/vercel/vercel-cache-store.ts +2 -1
- package/src/client-urls/types.ts +7 -5
- package/src/prefetch-markers.ts +10 -0
- package/src/prefetch-source-scope.ts +137 -0
- package/src/route-definition/helpers-types.ts +8 -5
- package/src/route-definition/redirect.ts +5 -0
- package/src/router/intercept-resolution.ts +94 -50
- package/src/router/loader-resolution.ts +8 -6
- package/src/router/match-api.ts +33 -29
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +2 -1
- package/src/router/match-middleware/cache-lookup.ts +39 -14
- package/src/router/match-result.ts +45 -85
- package/src/router/middleware-types.ts +6 -0
- package/src/router/navigation-snapshot.ts +18 -3
- package/src/router/prefetch-cache-ttl.ts +15 -3
- package/src/router/revalidation.ts +63 -13
- package/src/router/router-context.ts +2 -1
- package/src/router/router-options.ts +6 -3
- package/src/router/segment-resolution/loader-cache.ts +21 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +38 -31
- package/src/router/segment-resolution/revalidation.ts +40 -12
- package/src/router/source-reads.ts +122 -0
- package/src/router/types.ts +2 -1
- package/src/router.ts +11 -12
- package/src/rsc/helpers.ts +20 -6
- package/src/rsc/prefetch-render-error.ts +7 -2
- package/src/rsc/progressive-enhancement.ts +8 -2
- package/src/rsc/request-stages.ts +87 -1
- package/src/rsc/rsc-rendering.ts +78 -32
- package/src/rsc/server-action.ts +2 -1
- package/src/rsc/shell-capture.ts +4 -5
- package/src/rsc/shell-serve.ts +12 -1
- package/src/segment-system.tsx +1 -1
- package/src/server/context.ts +37 -6
- package/src/server/cookie-store.ts +22 -1
- package/src/server/document-location-state.ts +334 -0
- package/src/server/handle-store.ts +2 -0
- package/src/server/request-context.ts +137 -33
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/e2e/fixture.ts +4 -12
- package/src/testing/e2e/server.ts +91 -4
- package/src/testing/render-route.tsx +309 -10
- package/src/testing/run-revalidate.ts +11 -1
- package/src/testing/serve-shell-request.ts +22 -3
- package/src/types/handler-context.ts +21 -4
- package/src/types/loader-types.ts +7 -4
- package/src/types/segments.ts +18 -1
- package/src/urls/path-helper-types.ts +7 -5
- package/src/vite/temp-rsc-server.ts +2 -2
- package/src/vite/utils/forward-user-plugins.ts +13 -5
|
@@ -8,25 +8,26 @@
|
|
|
8
8
|
|
|
9
9
|
### Options — `RenderRouteOptions`
|
|
10
10
|
|
|
11
|
-
| Field
|
|
12
|
-
|
|
|
13
|
-
| `request`
|
|
14
|
-
| `loaderData`
|
|
15
|
-
| `outletPending`
|
|
16
|
-
| `loaders`
|
|
17
|
-
| `params`
|
|
18
|
-
| `locationState`
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
11
|
+
| Field | Type | Meaning |
|
|
12
|
+
| ----------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
13
|
+
| `request` | `Request \| string` | Initial location. Only the URL is read (client render — headers/method ignored). Defaults to the leaf spec's static prefix or `"/"`. |
|
|
14
|
+
| `loaderData` | `Record<string, unknown>` | Loader data keyed by loader `$$id`. `useLoader(L)` reads `loaderData[L.$$id]`. |
|
|
15
|
+
| `outletPending` | `boolean` | Seed `useOutlet().pending` through each synthetic segment's production `OutletProvider`. Defaults to `false`; this is context seeding, not a simulated navigation/Suspense/action lifecycle. |
|
|
16
|
+
| `loaders` | `ReadonlyArray<readonly [LoaderDefinition<any>, unknown]>` | Seed by REFERENCE: `[loader, data]` pairs. Robust for real `createLoader()` handles whose `$$id` is empty in a bare test. Prefer over `loaderData`. |
|
|
17
|
+
| `params` | `Record<string, string>` | Explicit params, merged over (and overriding) params extracted from the `request` URL. |
|
|
18
|
+
| `locationState` | `ReadonlyArray<readonly [LocationStateDefinition<any, any>, unknown]>` | Seed `useLocationState(def)` by REFERENCE: `[def, value]` pairs; keys an unkeyed `def` (like `withLocationStateKey(def)`) and writes `value` to `history.state` under `def.__rsc_ls_key`. See [Location state](#location-state). |
|
|
19
|
+
| `documentLocationState` | `ReadonlyArray<readonly [LocationStateDefinition<any, any>, unknown]>` | The location state the document response carried (a handler, middleware or no-JS form action set it, or a document redirect's cookie brought it), as `[def, value]` pairs. Merged at start-up after `locationState` and the `clearOnReload` pass. See [State a document carried](#state-a-document-carried). |
|
|
20
|
+
| `handles` | `ReadonlyArray<readonly [Handle<any, any>, unknown[]]>` | Seed `useHandle(handle)` by REFERENCE: `[handle, pushedValues[]]`. Accumulated GLOBALLY (not segment-scoped). |
|
|
21
|
+
| `lateHandles` | `ReadonlyArray<readonly [Handle<any, any>, unknown[]]>` | Handle values that arrive AFTER hydration, as a late loader push does: `[handle, valuesAfterTheUpdate[]]` replaces that handle's `handles` seed once the root has hydrated (or mounted). See [Hydration](#hydration). |
|
|
22
|
+
| `handle` | `HandleDataSeed` | Advanced: raw wire format `{ [handleId]: { [segmentId]: pushedValues[] } }`. Prefer `handles`. Merged with it. |
|
|
23
|
+
| `routeMap` | `Record<string, string>` | Name -> pattern map (informational; client `useReverse` takes its map as an argument, so this is not consumed). |
|
|
24
|
+
| `basename` | `string` | `createRouter({ basename })` value. Wired into `NavigationProvider` so `useRouter().basename`, `<Link>` prefixing, `useMount`/`useHref` resolve against the mount. Normalized like `createRouter`. Defaults to root. |
|
|
25
|
+
| `mount` | `string` | `include()` mount prefix. Wraps the segment chain in a `MountContext` so `useMount()` returns the prefix. Normalized like a path prefix. Defaults to `"/"`. |
|
|
26
|
+
| `theme` | `ThemeConfig \| true` | Theme config (`createRouter({ theme })` shape) to wrap the tree in a `ThemeProvider`. Defaults to no provider. A component calling `useTheme()` REQUIRES one. |
|
|
27
|
+
| `nonce` | `string` | CSP nonce to seed via `NonceContext`, so a component calling `useNonce()` (e.g. an analytics/GTM head script) sees it — mirroring SSR. Defaults to `undefined` (the browser default). |
|
|
28
|
+
| `defaultPrefetch` | `PrefetchStrategy` | Router default for `<Link>` and eligible plain anchors. `data-prefetch="false"`/`"none"` opts out one anchor; ancestor `data-prefetch-scope="false"`/`"none"` hard-disables the subtree; `"true"` permits routed resource suffixes elsewhere. |
|
|
29
|
+
| `hydrate` | `boolean` | Render the same tree to HTML first (`renderToString`, no `window`/`document`), then hydrate that HTML instead of mounting fresh. The result gains `serverHtml`, `recoverableErrors` and `shellError`. See [Hydration](#hydration). |
|
|
30
|
+
| `rewrite` | `string` | The path middleware rewrote `request` to (`throw rewrite()`): the leaf is matched against it for params, while the location (`usePathname`, `useSearchParams`) stays `request`, as a rewritten document renders and hydrates. |
|
|
30
31
|
|
|
31
32
|
`RenderRouteSpec = { path, Component, layout?, loaderIds?, name?, transition? }` — one node of the route definition. The array is the layout chain root-to-leaf; the LAST entry is the leaf route (its pattern is matched against `request` to extract params; layout patterns are informational). `loaderIds` attaches seeded loaders to THIS node's segment; `layout` on the leaf wraps it; `name` is informational. `transition` is the `transition()` config this node declares (`{}` for a bare `transition()`), attached to its segment exactly as the DSL does — see [Held navigation](#held-navigation).
|
|
32
33
|
|
|
@@ -78,6 +79,21 @@ type RenderRouteResult = RenderResult & {
|
|
|
78
79
|
// re-render the current location, as router.refresh() does:
|
|
79
80
|
// a spec's when gets kind "revalidate" (`to` is `from`)
|
|
80
81
|
refresh(): Promise<void>;
|
|
82
|
+
// keep `url` as a prefetch made now carries it (the seeds then, `loaders`
|
|
83
|
+
// over them); a later navigate(url) without `loaders` commits that data
|
|
84
|
+
prefetch(
|
|
85
|
+
url: string,
|
|
86
|
+
options?: {
|
|
87
|
+
loaders?: ReadonlyArray<readonly [LoaderDefinition<any>, unknown]>;
|
|
88
|
+
},
|
|
89
|
+
): void;
|
|
90
|
+
// land a server action's response: `loaders` are the post-write values,
|
|
91
|
+
// the page on screen re-renders with them, and (without keepClientCache)
|
|
92
|
+
// every history entry and prefetch is marked stale
|
|
93
|
+
action(options?: {
|
|
94
|
+
loaders?: ReadonlyArray<readonly [LoaderDefinition<any>, unknown]>;
|
|
95
|
+
keepClientCache?: boolean;
|
|
96
|
+
}): Promise<void>;
|
|
81
97
|
pathname(): string;
|
|
82
98
|
params(): Record<string, string>;
|
|
83
99
|
store: NavigationStore; // advanced
|
|
@@ -265,6 +281,30 @@ it("drops them on a refresh and carries again on the next navigation", async ()
|
|
|
265
281
|
});
|
|
266
282
|
```
|
|
267
283
|
|
|
284
|
+
### State a document carried
|
|
285
|
+
|
|
286
|
+
A page load, a no-JS form post that renders a page, and the target of a document redirect carry the location state the server set for them (see `/hooks`, state.md, "State on a document response"). Start-up merges it into the entry after the `clearOnReload` pass, so a reader gets it once the root has hydrated, `clearOnReload` slots included. Seed it with `documentLocationState`, not `locationState`: `locationState` is the entry's stored state, which `hydrate: true` passes through the `clearOnReload` pass first.
|
|
287
|
+
|
|
288
|
+
| To test | Do |
|
|
289
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
290
|
+
| a flash a document carried, as the page hydrates | `renderRoute(routes, { hydrate: true, documentLocationState })` |
|
|
291
|
+
| a document's `clearOnReload` state replacing a stale entry | `hydrate: true` with the stale value in `locationState`, the new one in `documentLocationState` |
|
|
292
|
+
| what the server put in the document | `serveShellRequest(router, url).readLocationState()` (see [`./serve-shell-request.md`](./serve-shell-request.md)) |
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
it("shows the flash the declined-payment redirect carried", async () => {
|
|
296
|
+
const { serverHtml, getByRole } = await renderRoute(
|
|
297
|
+
[{ path: "/checkout", Component: FlashBanner }],
|
|
298
|
+
{
|
|
299
|
+
hydrate: true,
|
|
300
|
+
documentLocationState: [[FlashMessage, { text: "Payment declined" }]],
|
|
301
|
+
},
|
|
302
|
+
);
|
|
303
|
+
expect(serverHtml).not.toContain("Payment declined"); // useLocationState is undefined until hydration
|
|
304
|
+
expect(getByRole("alert")).toHaveTextContent("Payment declined");
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
268
308
|
## Hydration
|
|
269
309
|
|
|
270
310
|
A plain `renderRoute` mounts with `createRoot`, so a component never runs its hydration render: `useSyncExternalStore` skips `getServerSnapshot`, and nothing compares the first client render with server HTML. `hydrate: true` runs the document-load sequence instead. renderRoute renders the same element (same providers, seeds, and RTL `reactStrictMode`) to HTML with `react-dom/server`'s `renderToString`, puts that HTML in the container, and hydrates it through RTL (`render(ui, { hydrate: true, onRecoverableError })`), so `cleanup()` unmounts it like any other render. `serverHtml` is what the server pass produced; `recoverableErrors` holds the message of every error React recovered from, which is where a hydration mismatch arrives.
|
|
@@ -395,19 +435,20 @@ it("dims the held price while the next product streams", async () => {
|
|
|
395
435
|
});
|
|
396
436
|
```
|
|
397
437
|
|
|
398
|
-
| Pending state
|
|
399
|
-
|
|
|
400
|
-
| `useLoader().isLoading` on the reader a `transition` navigate() holds, pending seed
|
|
401
|
-
| `transition` navigate(), settled seed (a plain value)
|
|
402
|
-
| navigate() without `transition`, pending seed
|
|
403
|
-
| `transition: { when }` returning false, or `navigate(url, { transition: false })`
|
|
404
|
-
| Production's other hold lanes (same-structure search/filter nav, fully-prefetched, optimistic)
|
|
405
|
-
| `useNavigation().state`, `useLinkStatus().pending`
|
|
406
|
-
| `useAction().state`
|
|
407
|
-
| `useHandle()` on a held navigation, `navigate(url, { handles, loaders })`
|
|
408
|
-
| `useNavigation().isStreaming` past the commit, `navigate(url, { streamUntil })`
|
|
409
|
-
| A navigation started while React holds a page (a newer click, a `transition: false` commit)
|
|
410
|
-
| A back/forward restored from the history cache (`pop`)
|
|
438
|
+
| Pending state | Under `renderRoute` |
|
|
439
|
+
| ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
440
|
+
| `useLoader().isLoading` on the reader a `transition` navigate() holds, pending seed | Modeled: `true` on the OLD data until the promise settles, then `false` with the new data in the same commit. |
|
|
441
|
+
| `transition` navigate(), settled seed (a plain value) | `false`; the data swaps in the commit. |
|
|
442
|
+
| navigate() without `transition`, pending seed | Urgent commit (production's cold-navigation lane): the read suspends to the nearest `<Suspense>` fallback (with none, React keeps the previous tree). No flag. |
|
|
443
|
+
| `transition: { when }` returning false, or `navigate(url, { transition: false })` | Same as without `transition`: urgent, with production's decision (`kind: "push"`; `refresh()` is `"revalidate"`). `transition: false` calls no predicate. |
|
|
444
|
+
| Production's other hold lanes (same-structure search/filter nav, fully-prefetched, optimistic) | NOT modeled — without `transition` navigate() stays urgent. Assert those at e2e. |
|
|
445
|
+
| `useNavigation().state`, `useLinkStatus().pending` | Modeled: navigate() starts the navigation as a click does. `loading` from the call, `idle` in the commit, held with it; `pending` true for the destination's link until that commit, held with it too. |
|
|
446
|
+
| `useAction().state` | Stays `idle`: navigate() runs no action. e2e. |
|
|
447
|
+
| `useHandle()` on a held navigation, `navigate(url, { handles, loaders })` | Modeled: `handles` arrive on the response's handle stream, so readers keep the page being left's values while React holds the page and take the new ones with its commit. |
|
|
448
|
+
| `useNavigation().isStreaming` past the commit, `navigate(url, { streamUntil })` | Modeled: the stream opens a task after the call (`pendingUrl` clears) and ends when the promise settles or a newer navigation aborts it; settle it inside `await act(async () => ...)`. |
|
|
449
|
+
| A navigation started while React holds a page (a newer click, a `transition: false` commit) | `await router.navigate(...)` shows its page, with or without `act()`. A `<Link>` click goes inside `await act(async () => { ... })`: `fireEvent`'s synchronous `act()` drops React's retry of the held page. |
|
|
450
|
+
| A back/forward restored from the history cache (`pop`) | Modeled for the mounted entry and one a navigate() wrote: the page and its handle values come back, decided by `transition({ when })` as kind `"pop"`. A stale entry's revalidation and a restore that fetches: e2e. |
|
|
451
|
+
| A click on a prefetch made before an action, `prefetch(url)`, `action({ loaders })`, `navigate(url)` | Modeled: the click commits the prefetch's data at once; once it settled, the page revalidates in Back's lane with the post-write data (`useNavigation().state` stays `idle`, nothing remounts). `router.revalidations()` lists each revalidation request (its URL and held segment ids), decided by production's guards. `action({ keepClientCache: true })` keeps the prefetch fresh: no revalidation. The prefetch request itself: e2e. |
|
|
411
452
|
|
|
412
453
|
- Per-navigation seeds merge over the render-time `loaders`/`loaderData` for THAT navigation only; a later `navigate()` without `loaders` falls back to the render-time data.
|
|
413
454
|
- Pass settled data as a plain value, not `Promise.resolve(value)`: React has not observed a plain promise yet, so it counts as pending and renders one `stale` frame. (A Flight chunk that has already settled carries its status and is skipped, which a plain value mirrors.)
|
|
@@ -20,29 +20,30 @@ A `Prerender` route is served as in production: from the artifact `router.matchF
|
|
|
20
20
|
|
|
21
21
|
### Options — `ServeShellRequestOptions`
|
|
22
22
|
|
|
23
|
-
| Field | Type | Meaning
|
|
24
|
-
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
-
| `cacheStore` | `SegmentCacheStore` | The store for this request, in place of the store your `createRouter({ cache })` config returns. The rest of that config (`enabled`, `searchParams`) still applies. Omit it to use the router's own store.
|
|
26
|
-
| `env` | `TEnv` | Bindings, as `router.fetch(request, { env })` passes them. A function `cache` config receives them.
|
|
27
|
-
| `headers` | `HeadersInit` | Request headers (e.g. `cookie`). A document request defaults `accept` to `text/html`.
|
|
28
|
-
| `partial` | `true \| { from?: string; rewriteTarget?: string; segments?: string[]; prefetch?: boolean; fill?: boolean; interceptSource?: string; stale?: boolean; boundary?: string }` | Serve the navigation request the browser sends instead of a document GET: from `from` (default the site root) with `segments` mounted (default none). `prefetch: true` sends
|
|
29
|
-
| `action` | `{ ref: Function; id: string; args?: unknown[]; segments?: string[]; rewriteTarget?: string }` | Send the request a JS server action call makes from the page at `url`: a POST of `args` (encoded as the browser encodes them) with `?_rsc_action`, the `rsc-action` header, `X-RSC-Router-Client-Path` and `_rsc_segments` (the page's mounted segment ids, default none), plus the page's `rewriteTarget` as for `partial`. Registers `ref` under `id` (`<module id>#<export name>`). Not with `partial` or `form`.
|
|
23
|
+
| Field | Type | Meaning |
|
|
24
|
+
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `cacheStore` | `SegmentCacheStore` | The store for this request, in place of the store your `createRouter({ cache })` config returns. The rest of that config (`enabled`, `searchParams`) still applies. Omit it to use the router's own store. |
|
|
26
|
+
| `env` | `TEnv` | Bindings, as `router.fetch(request, { env })` passes them. A function `cache` config receives them. |
|
|
27
|
+
| `headers` | `HeadersInit` | Request headers (e.g. `cookie`). A document request defaults `accept` to `text/html`. |
|
|
28
|
+
| `partial` | `true \| { from?: string; rewriteTarget?: string; segments?: string[]; prefetch?: boolean; fill?: boolean; interceptSource?: string; stale?: boolean; boundary?: string }` | Serve the navigation request the browser sends instead of a document GET: from `from` (default the site root) with `segments` mounted (default none). `prefetch: true` sends the speculative kind a viewport or render `<Link>` prefetch sends (`X-Rango-Prefetch: 1`), so `prefetch: false` work is deferred; add `headers: { "X-Rango-Prefetch": "intent" }` to send a hover's or `prefetch()`'s intent prefetch instead, which defers nothing. `fill: true` sends the follow-up request (`_rsc_fill=1`) the browser makes after adopting a prefetch; pass the prefetch's delivered ids, without the deferred ones, as `segments`, and the same URL as `from`. `interceptSource` sends the intercept source (`X-RSC-Router-Intercept-Source`) the browser sends with the fill of an intercept, whose `from` is that source too. `rewriteTarget` sends what the page at `from` carried as its payload's `metadata.rewriteTarget` when middleware rewrote it (`_rsc_rt`), as the browser does, so the server diffs against the route that rendered that page. `stale: true` sends a stale request (`_rsc_stale`), as the browser sends it: every `revalidate()` sees `stale: true` (see `revalidate()`'s `stale`), segments take an action's revalidation defaults, and it renders live, as an action does: neither the route cache (`cache()`) nor the document cache serves or stores it, and a `ppr` route's `replayStatus` is `{ outcome: "BYPASS", reason: "stale" }`. `boundary` names the error segment of a thrown action's boundary the page at `from` shows (`_rsc_boundary`), as the background revalidation an earlier action owes that page sends it, with `stale: true` and no `segments`: the response renders every segment above the boundary and nothing at or below it, claims the boundary in `matched` and sends no segment for it. The server reads it only beside `stale`. |
|
|
29
|
+
| `action` | `{ ref: Function; id: string; args?: unknown[]; segments?: string[]; rewriteTarget?: string }` | Send the request a JS server action call makes from the page at `url`: a POST of `args` (encoded as the browser encodes them) with `?_rsc_action`, the `rsc-action` header, `X-RSC-Router-Client-Path` and `_rsc_segments` (the page's mounted segment ids, default none), plus the page's `rewriteTarget` as for `partial`. Registers `ref` under `id` (`<module id>#<export name>`). Not with `partial` or `form`. |
|
|
30
30
|
|
|
31
31
|
### Returns — `ServeShellRequestResult`
|
|
32
32
|
|
|
33
|
-
| Field
|
|
34
|
-
|
|
|
35
|
-
| `shellStatus`
|
|
36
|
-
| `replayStatus`
|
|
37
|
-
| `prelude`
|
|
38
|
-
| `flight`
|
|
39
|
-
| `key`
|
|
40
|
-
| `readEntry`
|
|
41
|
-
| `readHandles`
|
|
42
|
-
| `readDeferred`
|
|
43
|
-
| `readAction`
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
33
|
+
| Field | Type | Meaning |
|
|
34
|
+
| ------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `shellStatus` | `"HIT" \| "MISS" \| null` | `x-rango-shell`; null when the serve path did not consider the request (not a `ppr` route, a nonce'd request, a partial request). |
|
|
36
|
+
| `replayStatus` | `PprReplayStatus \| null` | `x-rango-ppr-replay` on a partial request: `{ outcome: "HIT", freshness }` or `{ outcome: "BYPASS", reason }`. |
|
|
37
|
+
| `prelude` | `string \| undefined` | The prelude a HIT served (the capture's Flight text). `undefined` unless `shellStatus` is `"HIT"`. |
|
|
38
|
+
| `flight` | `string \| undefined` | The Flight payload this request rendered: a HIT's tail, a document render, or a partial response. `undefined` when no Flight rendered (a redirect, a middleware response). |
|
|
39
|
+
| `key` | `string` | The shell key the serve path resolved for a document it read (MISS or HIT), request partition included. Otherwise (no `ppr`, a partial request) the URL's key without a partition. |
|
|
40
|
+
| `readEntry` | `() => Promise<ShellCacheEntry \| null>` | Reads the document entry under `key` from the request's store (a passive `getShell`). A read: on a store with a shell memo it warms the memo, so call it after the reads you count. |
|
|
41
|
+
| `readHandles` | `() => Promise<ShellRequestHandles \| undefined>` | Decodes the response's handle data as the browser reads it: `{ hydration, late }` (see "Handles and loader data"). `undefined` when no Flight rendered. |
|
|
42
|
+
| `readDeferred` | `() => Promise<string[] \| undefined>` | The ids of the segments the payload marks deferred (a prefetch skipped them; the fill renders them), in payload order. `[]` when nothing was deferred, `undefined` when no Flight rendered. |
|
|
43
|
+
| `readAction` | `() => Promise<ShellRequestAction \| undefined>` | For an `action` request, the call's answer as the browser reads it: `{ ok, result, revalidationError }` (see "A server action called from the page"). `undefined` when no Flight rendered or the payload carries no action result. |
|
|
44
|
+
| `readLocationState` | `() => Promise<Record<string, unknown> \| undefined>` | The payload's location state as the browser reads it: `{ [Def.__rsc_ls_key]: value }` (see "Location state on a document"). `undefined` when it carries none or no Flight rendered. |
|
|
45
|
+
| `response` | `Response` | Status and headers. Its body is already read. |
|
|
46
|
+
| `body` | `string` | The body text. |
|
|
46
47
|
|
|
47
48
|
### `resetShellTestState(): Promise<void>`
|
|
48
49
|
|
|
@@ -415,6 +416,30 @@ expect(await result.readPayloadFormState()).toEqual(result.formState);
|
|
|
415
416
|
- Request headers come from `headers`. No `Origin` is sent by default, which the origin guard allows.
|
|
416
417
|
- Not with `partial`. The hydration itself (`hydrateRoot` taking the state, the hook not resetting) is e2e.
|
|
417
418
|
|
|
419
|
+
## Location state on a document
|
|
420
|
+
|
|
421
|
+
A document carries the location state its handler, middleware or no-JS form action set, in its bootstrap payload; `readLocationState()` reads it as the browser does. A document request that redirects carries its state to the target in a one-shot cookie instead (`Set-Cookie`, `<stateCookieName>-ls`, `__Host-` on https): pass that cookie on the target's request, as the browser does when it follows the redirect, and read what the target's payload carries.
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
const redirected = await serveShellRequest(router, "/payment/callback");
|
|
425
|
+
const setCookie = redirected.response.headers
|
|
426
|
+
.getSetCookie()
|
|
427
|
+
.find((c) => /-ls=/.test(c.split(";")[0]!))!;
|
|
428
|
+
|
|
429
|
+
const target = await serveShellRequest(router, "/checkout", {
|
|
430
|
+
headers: { cookie: setCookie.split(";")[0]!, "sec-fetch-dest": "document" },
|
|
431
|
+
});
|
|
432
|
+
expect(await target.readLocationState()).toEqual({
|
|
433
|
+
[FlashMessage.__rsc_ls_key]: { text: "Payment declined" },
|
|
434
|
+
});
|
|
435
|
+
expect(target.response.headers.get("cache-control")).toBe("private, no-store");
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
- Only a GET document request of the target's pathname reads the cookie, and clears it (`Max-Age=0` on its response). Where the request sends `Sec-Fetch-Dest`, it must be `document`, `iframe` or `frame`: a prefetch or `fetch()` (`empty`) leaves the cookie to the navigation. Without the header the request counts as a navigation.
|
|
439
|
+
- A document that carries state, and the redirect carrying it on, leave `Cache-Control: private, no-store`; the document cache neither stores nor serves them. On a `ppr` route, state set before its handler ran, by middleware or a redirect's cookie, rides the live payload: the request is still a `MISS` that captures or a `HIT`, and `prelude` holds none of it. State its handler sets keeps the route from being stored as a shell (`readEntry()` is null).
|
|
440
|
+
- For a `form` request, the re-rendered document carries what the action set; a redirecting action carries it in the cookie, as above.
|
|
441
|
+
- What a reader shows is client-side: seed it with `renderRoute({ hydrate: true, documentLocationState })` (see [`./client-components.md`](./client-components.md)).
|
|
442
|
+
|
|
418
443
|
## A server action called from the page
|
|
419
444
|
|
|
420
445
|
`serveShellRequest(router, url, { action: { ref, id, args, segments } })` sends the request a JS server action call makes from the page at `url`, as `browser/server-action-bridge.ts` sends it: a POST of `args` with `?_rsc_action`, the `rsc-action` header, `X-RSC-Router-Client-Path` and `_rsc_segments`. `ref` is registered under `id` (`<module id>#<export name>`, any unique string of that shape in a test) so the handler loads it by id. The response is the action's Flight payload: its return value and the segments it re-rendered, `flight` as for any other request. Pass the segment ids the page holds as `segments`, so the request has segments to revalidate; without them the server holds none. Read them from the `metadata.matched` of a `partial` request's payload (`flight`), as in "Testing `prefetch: false`" above.
|
|
@@ -53,6 +53,14 @@
|
|
|
53
53
|
| `stateCookieName` | `string` | The resolved rango state cookie name this run seeded (default `rango-state_router_0`). Assert an `invalidateClientCache()` rotation against it without recomputing. |
|
|
54
54
|
| `locationState` | `Record<string, unknown>` | The flash set via `ctx.setLocationState()` / `redirect({ state })`, as the flat `{ key: value }` the client reads. |
|
|
55
55
|
|
|
56
|
+
`runInRequestContext` runs your function, not the router's action handling,
|
|
57
|
+
so its `response` carries no write bump: the router sets that around every
|
|
58
|
+
action it runs (the state cookie's `{version}:{timestamp}:{write}`, write part
|
|
59
|
+
bumped unless the action called `keepClientCache()`). To assert it, send the
|
|
60
|
+
action through the router with `serveShellRequest(router, url, { action })`
|
|
61
|
+
(or `{ form }` with `createActionForm` for a post before hydration) and read
|
|
62
|
+
the state `Set-Cookie` on `result.response`.
|
|
63
|
+
|
|
56
64
|
Low-level building blocks (all from `@rangojs/router/testing`):
|
|
57
65
|
|
|
58
66
|
- `createTestRequestContext(opts)` builds the context WITHOUT entering it and returns `{ ctx, request, url, variables, stateCookieName }`. Its `ctx.reverse` accepts any name from your `routeMap`.
|
|
@@ -249,6 +249,13 @@ them right:
|
|
|
249
249
|
server").
|
|
250
250
|
- A prefetch runs the predicates too, so a loader that holds costs nothing in
|
|
251
251
|
the prefetch of a tab switch.
|
|
252
|
+
- A predicate that reads `currentParams` or `fromRouteName` and keeps its
|
|
253
|
+
segment shares that prefetch only between the pages with equal values of
|
|
254
|
+
what it read; one that reads `currentUrl` makes it page-only (2.6).
|
|
255
|
+
`bySlug` keeps only on an unchanged slug, so a product's tabs share their
|
|
256
|
+
prefetches; `bySearch` reads the URL, so the cost is the links between one
|
|
257
|
+
query's tabs. A layout rule comparing a param most pages share (a locale,
|
|
258
|
+
an account) groups by that param and keeps the sharing.
|
|
252
259
|
- Unit-test them with `runRevalidate` (`/testing`).
|
|
253
260
|
|
|
254
261
|
### 2.5 loading() or an inline Suspense
|
|
@@ -279,8 +286,14 @@ A prefetch is a navigation sent early: the server renders the destination's
|
|
|
279
286
|
handlers and loaders for it, from the page you are on (`/links`, "What a
|
|
280
287
|
prefetch renders"). A click that adopts a complete prefetch commits with no
|
|
281
288
|
request. Entries live 300 s by default (`prefetchCacheTTL`) in the router's
|
|
282
|
-
memory
|
|
283
|
-
|
|
289
|
+
memory. An action marks them stale unless it calls `keepClientCache()`: a
|
|
290
|
+
click on one shows it at once, then revalidates the page in the background
|
|
291
|
+
(`/server-actions`, "Client Cache After an Action"). The browser HTTP cache
|
|
292
|
+
keeps a prefetch for as long only when nothing in it is live: a document cache
|
|
293
|
+
HIT, or an answer whose every sent segment came from a `cache()` record or a
|
|
294
|
+
Prerender entry and that runs no loader
|
|
295
|
+
(`Cache-Control: private, max-age=<prefetchCacheTTL>`). Any other prefetch
|
|
296
|
+
carries no `Cache-Control` of the router's. A prefetch whose page failed
|
|
284
297
|
is never used: a 500 or 404, and a 200 whose render reported an error (a
|
|
285
298
|
loader that threw, a `loading()` handler that fell back to its
|
|
286
299
|
`errorBoundary()`, an async component that threw). The click sends its own
|
|
@@ -301,11 +314,16 @@ The rule: **prefetch broadly, and flag what is expensive.**
|
|
|
301
314
|
already on screen whose `revalidate()` re-runs it for the destination is
|
|
302
315
|
prefetched with it, so the click does not wait on a fill for data the page
|
|
303
316
|
is already showing.
|
|
304
|
-
-
|
|
305
|
-
|
|
317
|
+
- `prefetch: false` applies to speculative prefetches: viewport and render.
|
|
318
|
+
A hover, or `useRouter().prefetch()`, is intent to click and runs the
|
|
319
|
+
loader, so its click needs no fill. A hover over a link a viewport prefetch
|
|
320
|
+
already answered sends nothing, and that click still fills.
|
|
321
|
+
- The trade-off of a deferring prefetch, measured on a `clientUrls()` tab in
|
|
322
|
+
a consumer app (dev, a loaded machine), before a hover's prefetch ran
|
|
323
|
+
flagged loaders: with the tab's loader flagged, a hover then a click 150 ms
|
|
306
324
|
later showed the content in 843 to 918 ms; with the tab prefetched whole, in
|
|
307
325
|
77 to 157 ms. The flagged click pays the fill request and then React's
|
|
308
|
-
300 ms on the tab's fallback.
|
|
326
|
+
300 ms on the tab's fallback. A viewport-prefetched link still pays it.
|
|
309
327
|
- A click that adopts a prefetch with a flagged loader shows the page at once
|
|
310
328
|
and sends one fill request for the flagged loaders. Read a flagged loader
|
|
311
329
|
under its own `<Suspense>`: with no boundary, React keeps the page being
|
|
@@ -313,9 +331,33 @@ The rule: **prefetch broadly, and flag what is expensive.**
|
|
|
313
331
|
- On a server route, `loading(fallback, { prefetch: false })` keeps the whole
|
|
314
332
|
entry behind that fallback out of the prefetch (`/route`, "`loading()`
|
|
315
333
|
options"). A group's `loading()` takes no options.
|
|
316
|
-
-
|
|
317
|
-
|
|
318
|
-
|
|
334
|
+
- One prefetch of a page serves every page that links to it, so a paginated list
|
|
335
|
+
or a grid does not prefetch each link again after every navigation. The server
|
|
336
|
+
makes an answer page-only (`x-rsc-prefetch-scope: source`) when sharing it
|
|
337
|
+
would be wrong: an intercept targets the route or the page sending it, the
|
|
338
|
+
answer defers a `prefetch: false` unit the page does not show yet, a
|
|
339
|
+
`revalidate()` on the target read `currentUrl` and kept its segment, a rule
|
|
340
|
+
on the route (or inside its `path()`) read the page being left and kept the
|
|
341
|
+
segment through the route's default, or a kept answer sends no segment at
|
|
342
|
+
all. Only a click from that exact URL, query
|
|
343
|
+
and hash included, uses a page-only answer, so every page prefetches it
|
|
344
|
+
again (`/links`, "Shared prefetches and `prefetchKey`").
|
|
345
|
+
- A `revalidate()` that read `currentParams` or `fromRouteName` and kept its
|
|
346
|
+
segment, or a prerendered target reached from a page with the same route
|
|
347
|
+
params, groups the answer (`x-rsc-prefetch-scope: params; <label>`): the
|
|
348
|
+
pages with equal values of what was read share it. A layout rule comparing
|
|
349
|
+
`currentParams.locale` shares each prefetch between the pages of one locale.
|
|
350
|
+
Spreading `currentParams` reads every param; read the ones the rule needs by
|
|
351
|
+
name. A page's own block (a category hero) still goes in its route or in a
|
|
352
|
+
layout inside its `path()`, and links that follow a mount param come from
|
|
353
|
+
`useReverse`; neither needs a rule (`/app-structure`, "Prefetches are
|
|
354
|
+
shared").
|
|
355
|
+
- `prefetchKey=":source"` on a Link, or
|
|
356
|
+
`router.prefetch(url, { key: ":source" })`, makes that link's prefetches
|
|
357
|
+
page-only. Use it when the target's answer depends on the page through
|
|
358
|
+
something the server does not track: `context` in a `revalidate()`, or a
|
|
359
|
+
`Referer` read. It costs the same requests, so set it per link, not
|
|
360
|
+
app-wide.
|
|
319
361
|
|
|
320
362
|
### 2.7 transition() and view transitions
|
|
321
363
|
|
|
@@ -372,16 +414,16 @@ The rule: **prefetch broadly, and flag what is expensive.**
|
|
|
372
414
|
the error to the nearest error boundary, which replaces the page and its
|
|
373
415
|
form (`/client-urls`, "A loader that fails on its rerun").
|
|
374
416
|
- `keepClientCache()` in an action that changed nothing keeps the prefetches
|
|
375
|
-
and the history cache.
|
|
417
|
+
and the history cache fresh: no revalidation after a click or on Back.
|
|
376
418
|
- `useOptimistic` gives feedback before the action answers.
|
|
377
419
|
|
|
378
420
|
### 2.10 Updates you did not make: three channels
|
|
379
421
|
|
|
380
|
-
| The change | The tool
|
|
381
|
-
| -------------------------------------------------- |
|
|
382
|
-
| Your own mutation | A server action plus `revalidate()` rules (`isAction(ref)`, `actionResult`). The server re-runs what the action touched in the same response; nothing else to call.
|
|
383
|
-
| Out of band: a push, polling, a REST call, a login | `useLoader(L).load()` (also `refetch`): a plain `load()` of a route-registered loader updates every reader of it. `refreshGroup` on reads plus `useRefreshLoaders()` refreshes several loaders at once. Both need a fetchable loader, `createLoader(fn, true)`; a plain one answers 403. `router.refresh()` re-renders the current route's server data and keeps client state. `invalidateClientCache()` marks the history cache stale
|
|
384
|
-
| Shared server caches (other users' data) | Tags: `cache({ tags })` or `cacheTag()`, then `updateTag()` in the action (awaitable, the action's response reads fresh) or `revalidateTag()` from a webhook. `revalidate()` is not cache invalidation (`/cache-guide`).
|
|
422
|
+
| The change | The tool |
|
|
423
|
+
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
424
|
+
| Your own mutation | A server action plus `revalidate()` rules (`isAction(ref)`, `actionResult`). The server re-runs what the action touched in the same response; nothing else to call. |
|
|
425
|
+
| Out of band: a push, polling, a REST call, a login | `useLoader(L).load()` (also `refetch`): a plain `load()` of a route-registered loader updates every reader of it. `refreshGroup` on reads plus `useRefreshLoaders()` refreshes several loaders at once. Both need a fetchable loader, `createLoader(fn, true)`; a plain one answers 403. `router.refresh()` re-renders the current route's server data and keeps client state. `invalidateClientCache()` marks the history cache stale, drops prefetches and rotates the state when the change makes them wrong. |
|
|
426
|
+
| Shared server caches (other users' data) | Tags: `cache({ tags })` or `cacheTag()`, then `updateTag()` in the action (awaitable, the action's response reads fresh) or `revalidateTag()` from a webhook. `revalidate()` is not cache invalidation (`/cache-guide`). |
|
|
385
427
|
|
|
386
428
|
### 2.11 Handles: titles and breadcrumbs
|
|
387
429
|
|
|
@@ -445,7 +487,7 @@ ctx.reverse(".product", { slug: "b", locale: "de" }); // switch language
|
|
|
445
487
|
take unchecked path strings: prefer the typed `useReverse` map.
|
|
446
488
|
- `useReverse` with another module's map, or `useReverse(NamedRoutes)` under
|
|
447
489
|
a mount, gives a wrong URL with no error. Build cross-module links on the
|
|
448
|
-
server and pass them as props or loader data
|
|
490
|
+
server and pass them as props or loader data.
|
|
449
491
|
- A server layout stays held across navigations, its own `:projectId`
|
|
450
492
|
changing included: partial rendering by design. Links it rendered keep the
|
|
451
493
|
old params. Put param-dependent nav links in a client component with
|
|
@@ -926,6 +968,16 @@ Each entry: what the user sees, why, the fix.
|
|
|
926
968
|
15. **An effect keyed on a location-state object.** In a group the object is
|
|
927
969
|
the one the navigation passed until the commit, then the history entry's
|
|
928
970
|
copy: the effect runs twice. Fix: key it on a primitive field.
|
|
971
|
+
16. **A page's own block in a shared layout, kept by a rule that reads the
|
|
972
|
+
page being left** (a category hero in a template over the product pages,
|
|
973
|
+
re-rendered when `currentParams.category` changes). Nothing looks wrong,
|
|
974
|
+
but every prefetch under that layout is shared only between the pages of
|
|
975
|
+
one category (or with the same params, off the category routes): the
|
|
976
|
+
other pages prefetch their links again and the server renders them
|
|
977
|
+
again. Fix: put
|
|
978
|
+
the block in its route, or in a layout inside its `path()`; it
|
|
979
|
+
re-renders on its own params with no rule (`/app-structure`, "Prefetches
|
|
980
|
+
are shared").
|
|
929
981
|
|
|
930
982
|
## 5. Known gaps
|
|
931
983
|
|