@rangojs/router 0.17.0 → 0.19.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/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/event-controller.d.ts +3 -3
- package/dist/types/browser/history-state.d.ts +18 -0
- package/dist/types/browser/navigation-transaction.d.ts +20 -2
- package/dist/types/browser/partial-update.d.ts +16 -1
- package/dist/types/browser/prefetch/cache.d.ts +6 -6
- package/dist/types/browser/react/Link.d.ts +19 -9
- package/dist/types/browser/react/context.d.ts +2 -2
- package/dist/types/browser/react/location-state-shared.d.ts +95 -56
- package/dist/types/browser/react/location-state.d.ts +1 -1
- package/dist/types/browser/transition-when.d.ts +80 -0
- package/dist/types/browser/types.d.ts +104 -9
- package/dist/types/build/generate-manifest.d.ts +8 -0
- package/dist/types/cache/background-task.d.ts +24 -2
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/cache-key-utils.d.ts +48 -2
- package/dist/types/cache/cache-policy.d.ts +18 -1
- package/dist/types/cache/cache-runtime.d.ts +7 -0
- package/dist/types/cache/cache-scope.d.ts +192 -26
- package/dist/types/cache/cache-tag.d.ts +116 -11
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +224 -72
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +19 -0
- package/dist/types/cache/document-cache.d.ts +10 -0
- package/dist/types/cache/handle-snapshot.d.ts +41 -8
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/invalidation-order.d.ts +23 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/request-tag-mask.d.ts +54 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +141 -105
- package/dist/types/cache/tag-invalidation.d.ts +54 -3
- package/dist/types/cache/types.d.ts +267 -96
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +123 -18
- package/dist/types/client-urls/navigation.d.ts +28 -2
- package/dist/types/client-urls/server-projection.d.ts +5 -2
- package/dist/types/client-urls/types.d.ts +5 -5
- package/dist/types/client.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/route-definition/helpers-types.d.ts +11 -11
- package/dist/types/route-name.d.ts +7 -0
- package/dist/types/router/error-handling.d.ts +1 -1
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/intercept-resolution.d.ts +2 -0
- package/dist/types/router/match-context.d.ts +5 -0
- package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/router-options.d.ts +4 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +58 -12
- package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +33 -48
- package/dist/types/router/segment-resolution/mask-nested.d.ts +73 -24
- package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
- package/dist/types/router/transition-when.d.ts +24 -10
- package/dist/types/rsc/attach-transition-when.d.ts +18 -0
- package/dist/types/rsc/full-payload.d.ts +21 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/response-cache-serve.d.ts +15 -12
- package/dist/types/rsc/shell-build-manifest.d.ts +12 -3
- package/dist/types/rsc/shell-capture-constants.d.ts +116 -12
- package/dist/types/rsc/shell-capture.d.ts +202 -74
- package/dist/types/rsc/shell-serve.d.ts +144 -50
- package/dist/types/rsc/types.d.ts +9 -0
- package/dist/types/segment-system.d.ts +7 -0
- package/dist/types/server/context.d.ts +155 -33
- package/dist/types/server/cookie-store.d.ts +33 -0
- package/dist/types/server/handle-store.d.ts +52 -1
- package/dist/types/server/request-context.d.ts +210 -63
- package/dist/types/server/request-headers.d.ts +44 -0
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/index.d.ts +3 -2
- package/dist/types/testing/internal/action-id.d.ts +7 -0
- package/dist/types/testing/location-state-key.d.ts +24 -0
- package/dist/types/testing/render-route.d.ts +17 -1
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/run-transition-when.d.ts +44 -69
- package/dist/types/testing/serve-shell-request.d.ts +133 -0
- package/dist/types/testing/shell-status.d.ts +44 -19
- package/dist/types/transition-when-ref.d.ts +79 -0
- package/dist/types/types/cache-types.d.ts +15 -3
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/segments.d.ts +86 -71
- package/dist/types/urls/path-helper-types.d.ts +3 -4
- package/dist/types/urls/pattern-types.d.ts +21 -18
- package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +1005 -105
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +175 -76
- package/skills/caching/SKILL.md +361 -33
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +12 -7
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +52 -37
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +32 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/SKILL.md +3 -1
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/navigation.md +23 -2
- package/skills/hooks/state.md +101 -7
- package/skills/intercept/SKILL.md +29 -12
- package/skills/loader/SKILL.md +197 -44
- package/skills/middleware/SKILL.md +34 -9
- package/skills/migrate-nextjs/SKILL.md +68 -37
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +65 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +715 -371
- package/skills/prerender/SKILL.md +30 -17
- package/skills/rango/SKILL.md +72 -34
- package/skills/route/SKILL.md +14 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +49 -43
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/client-components.md +45 -3
- package/skills/testing/loader.md +19 -1
- package/skills/testing/middleware.md +1 -0
- package/skills/testing/predicates.md +49 -49
- package/skills/testing/render-handler.md +1 -0
- package/skills/testing/serve-shell-request.md +202 -0
- package/skills/testing/server-actions.md +9 -3
- package/skills/theme/SKILL.md +56 -4
- package/skills/typesafety/env-and-bindings.md +18 -2
- package/skills/use-cache/SKILL.md +91 -23
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +150 -29
- package/src/__augment-tests__/augmented.check.ts +23 -0
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/event-controller.ts +4 -4
- package/src/browser/history-state.ts +75 -1
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +77 -18
- package/src/browser/navigation-client.ts +4 -3
- package/src/browser/navigation-store.ts +43 -1
- package/src/browser/navigation-transaction.ts +31 -11
- package/src/browser/partial-update.ts +146 -35
- package/src/browser/prefetch/cache.ts +6 -6
- package/src/browser/prefetch/fetch.ts +2 -2
- package/src/browser/react/Link.tsx +38 -33
- package/src/browser/react/NavigationProvider.tsx +2 -2
- package/src/browser/react/context.ts +2 -2
- package/src/browser/react/location-state-shared.ts +241 -49
- package/src/browser/react/location-state.ts +5 -2
- package/src/browser/rsc-router.tsx +9 -0
- package/src/browser/server-action-bridge.ts +84 -15
- package/src/browser/transition-when.ts +205 -0
- package/src/browser/types.ts +124 -10
- package/src/build/generate-manifest.ts +77 -1
- package/src/cache/background-task.ts +55 -2
- package/src/cache/cache-exec-scope.ts +48 -1
- package/src/cache/cache-key-utils.ts +62 -2
- package/src/cache/cache-policy.ts +31 -4
- package/src/cache/cache-runtime.ts +209 -129
- package/src/cache/cache-scope.ts +573 -69
- package/src/cache/cache-tag.ts +475 -19
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +1094 -376
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +55 -40
- package/src/cache/document-cache.ts +53 -7
- package/src/cache/handle-snapshot.ts +53 -9
- package/src/cache/index.ts +2 -0
- package/src/cache/invalidation-order.ts +84 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/request-tag-mask.ts +147 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +249 -277
- package/src/cache/tag-invalidation.ts +109 -18
- package/src/cache/types.ts +274 -102
- package/src/cache/vercel/vercel-cache-store.ts +561 -74
- package/src/client-urls/client-root.tsx +8 -4
- package/src/client-urls/client-urls.ts +8 -6
- package/src/client-urls/navigation.ts +55 -4
- package/src/client-urls/server-projection.ts +31 -10
- package/src/client-urls/types.ts +12 -5
- package/src/client.tsx +2 -0
- package/src/host/cookie-handler.ts +2 -1
- package/src/index.rsc.ts +3 -0
- package/src/index.ts +3 -0
- package/src/loader-redirect.tsx +7 -3
- package/src/prerender/build-shell-capture.ts +173 -110
- package/src/route-definition/dsl-helpers.ts +6 -3
- package/src/route-definition/helpers-types.ts +11 -11
- package/src/route-name.ts +12 -0
- package/src/router/content-negotiation.ts +4 -1
- package/src/router/error-handling.ts +5 -2
- package/src/router/handler-context.ts +42 -5
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +30 -8
- package/src/router/loader-resolution.ts +89 -33
- package/src/router/logging.ts +5 -3
- package/src/router/match-api.ts +89 -42
- package/src/router/match-context.ts +5 -0
- package/src/router/match-handlers.ts +14 -29
- package/src/router/match-middleware/background-revalidation.ts +3 -5
- package/src/router/match-middleware/cache-lookup.ts +52 -19
- package/src/router/match-middleware/cache-store.ts +36 -10
- package/src/router/match-result.ts +1 -0
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +20 -4
- package/src/router/navigation-snapshot.ts +6 -5
- package/src/router/prerender-match.ts +10 -0
- package/src/router/request-classification.ts +7 -4
- package/src/router/router-options.ts +4 -1
- package/src/router/segment-resolution/fresh.ts +18 -13
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +366 -70
- package/src/router/segment-resolution/loader-mask.ts +3 -4
- package/src/router/segment-resolution/loader-snapshot.ts +215 -126
- package/src/router/segment-resolution/mask-nested.ts +161 -42
- package/src/router/segment-resolution/revalidation.ts +27 -13
- package/src/router/segment-resolution/view-transition-default.ts +6 -21
- package/src/router/telemetry.ts +6 -3
- package/src/router/transition-when.ts +34 -52
- package/src/router.ts +2 -1
- package/src/rsc/attach-transition-when.ts +47 -0
- package/src/rsc/full-payload.ts +47 -3
- package/src/rsc/handler.ts +24 -10
- package/src/rsc/loader-fetch.ts +2 -1
- package/src/rsc/manifest-init.ts +4 -0
- package/src/rsc/origin-guard.ts +8 -5
- package/src/rsc/progressive-enhancement.ts +10 -32
- package/src/rsc/response-cache-serve.ts +69 -78
- package/src/rsc/response-route-handler.ts +2 -2
- package/src/rsc/rsc-rendering.ts +712 -338
- package/src/rsc/server-action.ts +8 -20
- package/src/rsc/shell-build-manifest.ts +49 -29
- package/src/rsc/shell-capture-constants.ts +182 -12
- package/src/rsc/shell-capture.ts +1126 -612
- package/src/rsc/shell-serve.ts +331 -85
- package/src/rsc/ssr-setup.ts +3 -2
- package/src/rsc/types.ts +9 -0
- package/src/segment-system.tsx +40 -7
- package/src/server/context.ts +328 -72
- package/src/server/cookie-store.ts +184 -133
- package/src/server/handle-store.ts +245 -27
- package/src/server/request-context.ts +364 -100
- package/src/server/request-headers.ts +134 -0
- package/src/server.ts +4 -0
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/dispatch.ts +4 -7
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/internal/action-id.ts +25 -0
- package/src/testing/internal/context.ts +1 -1
- package/src/testing/location-state-key.ts +45 -0
- package/src/testing/render-handler.ts +1 -1
- package/src/testing/render-route.tsx +109 -16
- package/src/testing/run-client-revalidate.ts +3 -19
- package/src/testing/run-loader.ts +67 -5
- package/src/testing/run-transition-when.ts +109 -172
- package/src/testing/serve-shell-request.ts +381 -0
- package/src/testing/shell-status.ts +73 -47
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/transition-when-ref.ts +220 -0
- package/src/types/cache-types.ts +15 -3
- package/src/types/handler-context.ts +14 -1
- package/src/types/index.ts +3 -0
- package/src/types/segments.ts +94 -91
- package/src/urls/path-helper-types.ts +3 -4
- package/src/urls/pattern-types.ts +21 -18
- package/src/vite/discovery/discover-routers.ts +3 -0
- package/src/vite/discovery/shell-prerender-phase.ts +9 -2
- package/src/vite/discovery/virtual-module-codegen.ts +16 -6
- package/src/vite/plugins/transition-when-hoist.ts +1064 -0
- package/src/vite/rango.ts +2 -0
- package/src/vite/router-discovery.ts +89 -9
- package/dist/types/rsc/transition-gate.d.ts +0 -23
- package/src/rsc/transition-gate.ts +0 -86
|
@@ -373,15 +373,17 @@ old-deployment request pinning.
|
|
|
373
373
|
The `@rangojs/router/testing/*` entry points give you `runLoader`,
|
|
374
374
|
`runMiddleware`, `runInRequestContext` (server actions), `dispatch` (base
|
|
375
375
|
`./testing`), `renderHandler` and real Flight rendering (`renderServerTree`,
|
|
376
|
-
`findClientBoundaries`, `findElements`)
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
and
|
|
376
|
+
`findClientBoundaries`, `findElements`) plus a real PPR shell capture and HIT
|
|
377
|
+
(`serveShellRequest`, `resetShellTestState`) from `./testing/flight`,
|
|
378
|
+
`renderRoute` from `./testing/dom`, a vitest preset from `./testing/vitest`, and
|
|
379
|
+
a Playwright e2e harness with dev/prod parity helpers (`parityDescribe`,
|
|
380
|
+
`expectParity`) from `./testing/e2e`. You can unit-test a loader, a middleware,
|
|
381
|
+
an action, or an RSC handler in isolation. Next.js and Waku ship no official
|
|
382
|
+
primitives for testing server components/handlers; TanStack documents testing
|
|
383
|
+
patterns (build your own harness from `createRouter`/`createMemoryHistory`) but
|
|
384
|
+
ships no testing package, and nothing at the RSC-handler level. See the
|
|
385
|
+
[testing skill](../../testing/SKILL.md) and
|
|
386
|
+
[testing.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/testing.md).
|
|
385
387
|
|
|
386
388
|
### Bundle discipline
|
|
387
389
|
|
|
@@ -487,7 +489,7 @@ layout(<ShopLayout />, () => [
|
|
|
487
489
|
"@modal",
|
|
488
490
|
"product.detail",
|
|
489
491
|
ProductQuickView,
|
|
490
|
-
{ when: ({ from }) => from.pathname === "/products" },
|
|
492
|
+
{ when: ({ from }) => from.url.pathname === "/products" },
|
|
491
493
|
() => [
|
|
492
494
|
loader(ProductLoader),
|
|
493
495
|
loading(<QuickViewSkeleton />),
|
|
@@ -506,8 +508,10 @@ middleware, layout, loaders, loading state, and loader-level caching and
|
|
|
506
508
|
`intercept()`) can choose by navigation source.
|
|
507
509
|
|
|
508
510
|
This is integrated with the rest of the runtime rather than implemented as URL
|
|
509
|
-
masking plus local component state.
|
|
510
|
-
|
|
511
|
+
masking plus local component state. A route an intercept targets gets
|
|
512
|
+
source-scoped prefetch entries whether or not the intercept applied, so neither a
|
|
513
|
+
modal Flight payload nor a full page prefetched from another page is reused where
|
|
514
|
+
the intercept decides differently. A prerendered target
|
|
511
515
|
can store a separate intercept variant while its loaders still run live at
|
|
512
516
|
request time. An action can revalidate the open intercept without remounting its
|
|
513
517
|
subtree or forcing the preserved background to render. See the
|
|
@@ -534,10 +538,13 @@ There are two distinct things named "revalidate", and the split is deliberate:
|
|
|
534
538
|
tabs, and pagination. See
|
|
535
539
|
[shallow-navigation.md](https://github.com/rangojs/rango/blob/main/packages/rangojs-router/docs/design/shallow-navigation.md).
|
|
536
540
|
|
|
537
|
-
Both are separate from `revalidateTag()`/`updateTag()`, which
|
|
538
|
-
cache entries
|
|
539
|
-
`
|
|
540
|
-
|
|
541
|
+
Both are separate from `revalidateTag()`/`updateTag()`, which evict tagged
|
|
542
|
+
cache entries; the request that calls either reads its own writes.
|
|
543
|
+
`updateTag()` also awaits the durable write; `revalidateTag()` schedules it in
|
|
544
|
+
the background. The mutating user's next requests skip the stores' per-isolate
|
|
545
|
+
PPR shell memos via a fresh-reads cookie, and other users converge within the
|
|
546
|
+
memo window and the platform's own propagation. Neither API selects a client
|
|
547
|
+
segment to render. Next.js offers `router.refresh()` (and
|
|
541
548
|
`refresh()` from `next/cache`) plus path/tag cache invalidation, but no per-segment
|
|
542
549
|
render predicate and no typed action discrimination — and its invalidations clear
|
|
543
550
|
the whole client cache rather than selecting a segment. TanStack's `shouldReload`/`router.invalidate()` is the nearest
|
|
@@ -573,7 +580,8 @@ the bounded cache uses a configurable TTL. `Save-Data`,
|
|
|
573
580
|
Correctness is part of the prefetch contract. Keys include Rango state, destination
|
|
574
581
|
URL, and the mounted segment set used to compute the diff. Server actions rotate
|
|
575
582
|
state and abort stale speculative work; a generation check prevents a late result
|
|
576
|
-
from repopulating an invalidated cache.
|
|
583
|
+
from repopulating an invalidated cache. Routes an intercept targets are automatically
|
|
584
|
+
source-scoped, matched or not,
|
|
577
585
|
and malformed, cross-origin, foreign-router, redirect, and reload responses are
|
|
578
586
|
dropped rather than warmed. `useLinkStatus()` exposes `{ pending }` for the owning
|
|
579
587
|
link.
|
|
@@ -620,18 +628,22 @@ this resource-aware policy layer.
|
|
|
620
628
|
|
|
621
629
|
`createLoader(fn)` / `loader()` define **live-by-default** data units: they are
|
|
622
630
|
excluded from an enclosing segment cache and resolve on every request unless the
|
|
623
|
-
loader itself explicitly opts into `cache()`.
|
|
624
|
-
`headers()`, request context, and `env` because
|
|
625
|
-
cached shell
|
|
626
|
-
|
|
627
|
-
route
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
631
|
+
loader itself explicitly opts into `cache()`. A loader without `ssr: false` may
|
|
632
|
+
safely read `cookies()`, `headers()`, request context, and `env` because its
|
|
633
|
+
execution is outside the cached shell; a loader with its own `cache()` must put
|
|
634
|
+
those reads in its `key()`, or its miss fails and stores nothing. On a `ppr`
|
|
635
|
+
route an `ssr: false` loader bakes into the shell, so an identity read there
|
|
636
|
+
refuses the capture. Loaders run in parallel, stream independently under
|
|
637
|
+
`loading()` boundaries, compose server-side via `ctx.use(OtherLoader)`, and
|
|
638
|
+
carry route-level authority: they can throw `notFound()`/`redirect()`, WRITE
|
|
639
|
+
handles (`ctx.use(Meta)({ title })` — data-derived page metadata pushed from the
|
|
640
|
+
data's producer), read handle data after the render settles (`ctx.get(handle)`
|
|
641
|
+
behind `await ctx.rendered()`), and opt into `loader(Def, { ssr: false })` so a
|
|
642
|
+
document render awaits them before first flush (deterministic SSR'd data, meta,
|
|
643
|
+
and 404 status). Reads happen through `useLoader` in a client component
|
|
644
|
+
(including its SSR pass) or `useFetchLoader` for standalone client fetches.
|
|
645
|
+
"Fetchable" loaders are callable endpoints with their own middleware and
|
|
646
|
+
GET/POST/PUT/PATCH/DELETE bodies.
|
|
635
647
|
|
|
636
648
|
One sharp edge is worth stating because the distinction matters: a cached handler
|
|
637
649
|
can call `await ctx.use(Loader)`, but if it renders that result inline, it bakes the
|
|
@@ -681,7 +693,7 @@ a fully prerendered route. Caching loader data is a separate, explicit opt-in on
|
|
|
681
693
|
that loader.
|
|
682
694
|
|
|
683
695
|
The mechanism is concrete: a prerendered route serves its baked Flight shell from
|
|
684
|
-
the store, then
|
|
696
|
+
the store, then the router resolves the loaders through their own
|
|
685
697
|
freshness policy at request time and merges their segments into that replayed
|
|
686
698
|
shell. With the default policy you get a cached/prerendered shell plus live data,
|
|
687
699
|
automatically, with no per-route static-vs-dynamic decision. The loader is the
|
|
@@ -692,7 +704,8 @@ designated live data slot in a cache-first RSC tree.** Several properties follow
|
|
|
692
704
|
that no loader-before-render has:
|
|
693
705
|
|
|
694
706
|
- it is the **cache-safety escape hatch** — the only place request-coupled reads
|
|
695
|
-
are allowed inside a cache scope, because it is the part guaranteed to re-run
|
|
707
|
+
are allowed inside a cache scope, because it is the part guaranteed to re-run
|
|
708
|
+
(a loader without `ssr: false` and without its own unkeyed `cache()`);
|
|
696
709
|
- it is **standalone and composable**, not route-coupled — one `createLoader()`
|
|
697
710
|
read by many segments, composed via `ctx.use`, or exposed as a fetchable endpoint;
|
|
698
711
|
- it **streams as a hole, not a gate** — concurrent, Suspense-resolved under
|
|
@@ -744,7 +757,7 @@ from two hosts, route scopes, param sets, or query variants therefore does not
|
|
|
744
757
|
collapse into one entry just because both calls received a `ctx` object.
|
|
745
758
|
|
|
746
759
|
The same boundary guards operations whose meaning cannot survive a hit. Direct
|
|
747
|
-
`cookies()`/`headers()` reads and request/response mutations such as `ctx.set()`,
|
|
760
|
+
`cookies()`/`headers()`/`ctx.request.headers` reads and request/response mutations such as `ctx.set()`,
|
|
748
761
|
`ctx.header()`, status, theme, and location-state writes throw inside
|
|
749
762
|
`"use cache"`. If a cached function pushes typed handles through
|
|
750
763
|
`ctx.use(Breadcrumbs)` or `ctx.use(Meta)`, Rango captures those pushes on a miss and
|
|
@@ -773,11 +786,13 @@ safe rule remains simple: read request-specific context at the live point of use
|
|
|
773
786
|
normally a loader, rather than deriving it outside and carrying it into a cached
|
|
774
787
|
shell.
|
|
775
788
|
|
|
776
|
-
This is where the taint and loader designs meet. Loaders are the sanctioned
|
|
777
|
-
holes: they run outside the enclosing segment cache and may read the
|
|
778
|
-
request,
|
|
779
|
-
|
|
780
|
-
|
|
789
|
+
This is where the taint and loader designs meet. Loaders are the sanctioned
|
|
790
|
+
dynamic holes: they run outside the enclosing segment cache and may read the
|
|
791
|
+
current request (except, on a `ppr` route, an `ssr: false` loader or a loader a
|
|
792
|
+
handler awaits, which the capture runs and refuses on such a read), while the
|
|
793
|
+
shared shell remains protected. Next.js also isolates runtime APIs from ordinary
|
|
794
|
+
`"use cache"`, and React's optional taint APIs protect values at the
|
|
795
|
+
server-to-client serialization boundary. Those are useful but different
|
|
781
796
|
contracts; they do not provide Rango's route-aware tainted-argument keying,
|
|
782
797
|
non-cacheable typed context variables, handle capture/replay, and loader escape
|
|
783
798
|
path as one system. TanStack Start and Waku leave this boundary primarily to
|
|
@@ -15,7 +15,7 @@ storefront homepage (shell HIT, production build, M-series laptop): a single
|
|
|
15
15
|
was dead for ~11s. Fast paint, frozen page.
|
|
16
16
|
|
|
17
17
|
The obvious fix — wrap the heavy subtree in `<Suspense>` so it hydrates later —
|
|
18
|
-
|
|
18
|
+
can trade the paint away: when that boundary postpones under shell capture, the
|
|
19
19
|
frozen prelude ships an empty `<main>`. This recipe gets both: the full body
|
|
20
20
|
HTML in the prelude AND its hydration off the initial task, released on first
|
|
21
21
|
idle at retry-lane priority.
|
|
@@ -28,7 +28,9 @@ idle at retry-lane priority.
|
|
|
28
28
|
| interactive (menu click works) | ~3.5s | ~1.6s | ~1.6s |
|
|
29
29
|
|
|
30
30
|
Measure your own page before and after — the win depends on how much of the
|
|
31
|
-
hydration cost lives under the boundary (see "Verifying and measuring").
|
|
31
|
+
hydration cost lives under the boundary (see "Verifying and measuring"). These
|
|
32
|
+
numbers were measured before PPR captures baked handler output; see the next
|
|
33
|
+
section for what that changed.
|
|
32
34
|
|
|
33
35
|
## The recipe
|
|
34
36
|
|
|
@@ -102,23 +104,39 @@ inside hydrates after first idle.
|
|
|
102
104
|
|
|
103
105
|
## Why `fallback={children}` is load-bearing
|
|
104
106
|
|
|
105
|
-
This is not a style choice
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
This is not a style choice. The recipe was built when the shell capture
|
|
108
|
+
postponed any big `<Suspense>` subtree: the capture froze on Flight byte-quiet
|
|
109
|
+
(`FLIGHT_QUIET_HOPS` in `src/rsc/shell-capture.ts`) while the handler output
|
|
110
|
+
was still rendering, so a boundary over the page body became a hole no matter
|
|
111
|
+
where it sat. Verified both ways at the time: wrapping the client island from
|
|
112
|
+
outside AND placing the boundary inside the island both baked
|
|
113
|
+
`<!--$?--><template id="B:…">` into `<main>`, i.e. an empty body in the frozen
|
|
114
|
+
prelude. With the content as the fallback, the postpone became the delivery
|
|
115
|
+
mechanism: the shell baked the fallback, and the fallback was the body.
|
|
116
|
+
|
|
117
|
+
PPR captures now wait for the whole handler layer before freezing anything:
|
|
118
|
+
the capture records the page's segments first (async server components
|
|
119
|
+
rendered, every promise in the handler output settled) and renders its Flight
|
|
120
|
+
payload from that record (`settleCaptureRecord` in `src/rsc/shell-capture.ts`).
|
|
121
|
+
A boundary whose content is only handler output is therefore expected to bake
|
|
122
|
+
its content as a completed boundary instead of postponing. What still
|
|
123
|
+
postpones at capture is a boundary over live data: a loader without
|
|
124
|
+
`ssr: false` read with `useLoader` under `loading()` or an inline
|
|
125
|
+
`<Suspense>`, or a promise nested in an `ssr: false` loader's value. Run the
|
|
126
|
+
prelude check in "Verifying and measuring" on your page rather than assuming
|
|
127
|
+
either shape.
|
|
128
|
+
|
|
129
|
+
`fallback={children}` stays the safe choice either way:
|
|
130
|
+
|
|
131
|
+
- If the boundary still postpones (live data inside it), the fallback is what
|
|
132
|
+
the prelude shows, so it must be the content.
|
|
133
|
+
- The sync-update trap below: a client render of the boundary shows the
|
|
134
|
+
fallback. With `fallback={children}` that is a visual no-op; with
|
|
135
|
+
`fallback={null}` it blanks the page.
|
|
136
|
+
|
|
137
|
+
The hydration deferral does not depend on which shape you get: `HydrationGate`
|
|
138
|
+
suspends during hydration, and React keeps the server DOM of the boundary in
|
|
139
|
+
place until the gate releases.
|
|
122
140
|
|
|
123
141
|
## Why the client gate is free
|
|
124
142
|
|
|
@@ -164,13 +182,14 @@ release on interaction instead (see Variations).
|
|
|
164
182
|
|
|
165
183
|
## Known cost: the body rides twice (measure it, don't guess)
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
baked fallback in the prelude, once as the
|
|
169
|
-
re-renders and re-ships it; there is no
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
185
|
+
When the boundary postpones at capture, a shell HIT carries the gated
|
|
186
|
+
subtree's HTML twice — once as the baked fallback in the prelude, once as the
|
|
187
|
+
resumed hole content (the resume re-renders and re-ships it; there is no
|
|
188
|
+
bake-through). Homepage measurement (postponing boundary): 234KB → 302KB
|
|
189
|
+
gzipped (+68KB, +29%; raw +1.05MB). It is post-paint bandwidth, not
|
|
190
|
+
render-blocking — the visible prelude streams first — but it is real bytes on
|
|
191
|
+
every document GET. Weigh it per page; on a small body the recipe may not pay
|
|
192
|
+
for itself. A boundary that bakes its content ships it once.
|
|
174
193
|
|
|
175
194
|
## Verifying and measuring
|
|
176
195
|
|
|
@@ -230,7 +249,8 @@ this exact space. The recipe survives that future; a primitive might not.
|
|
|
230
249
|
|
|
231
250
|
## Related
|
|
232
251
|
|
|
233
|
-
- `/ppr` — the shell/hole mechanics this recipe rides on
|
|
234
|
-
|
|
235
|
-
- `src/rsc/shell-capture.ts` — the
|
|
236
|
-
(`FLIGHT_QUIET_HOPS`) that
|
|
252
|
+
- `/ppr` — the shell/hole mechanics this recipe rides on: what bakes into the
|
|
253
|
+
shell, and which boundaries stay holes
|
|
254
|
+
- `src/rsc/shell-capture.ts` — the record-first capture (`settleCaptureRecord`)
|
|
255
|
+
and the byte-quiet window (`FLIGHT_QUIET_HOPS`) that decide what the prelude
|
|
256
|
+
holds
|
|
@@ -56,8 +56,11 @@ There are two shell producers:
|
|
|
56
56
|
Both producers feed the same in-function serve path. Build-time shells are
|
|
57
57
|
content-hashed modules in the server bundle, not CDN-served HTML files.
|
|
58
58
|
|
|
59
|
-
`ppr.ttl
|
|
60
|
-
|
|
59
|
+
`ppr.ttl` and `ppr.swr` set the shell entry's lifetime, capped by the route
|
|
60
|
+
`cache()` entry it was captured from (the shorter wins). Its tags are
|
|
61
|
+
`ppr.tags` plus the tags its content recorded. A route `cache({ key })` or a
|
|
62
|
+
store `keyGenerator` stores one shell per partition value. None of this emits
|
|
63
|
+
HTTP `Cache-Control` or configures a platform CDN.
|
|
61
64
|
|
|
62
65
|
## Platform deployment shapes
|
|
63
66
|
|
|
@@ -205,7 +205,10 @@ the variant suffixes are still appended.
|
|
|
205
205
|
- Responses whose `Cache-Control` also contains `private`, `no-store`, or an
|
|
206
206
|
unqualified `no-cache` (those win over `s-maxage` in a shared cache)
|
|
207
207
|
- Responses carrying `Set-Cookie` (a per-client value must not be replayed to
|
|
208
|
-
everyone)
|
|
208
|
+
everyone). That includes a response whose request called
|
|
209
|
+
`updateTag()`/`revalidateTag()` on a store that keeps per-isolate memos
|
|
210
|
+
(`CFCacheStore`, `VercelCacheStore`): it carries the fresh-reads cookie
|
|
211
|
+
(`rango-state-fresh` by default), so it is not stored.
|
|
209
212
|
- Non-200 responses
|
|
210
213
|
- A 200 whose render reported an error: a component that threw after the
|
|
211
214
|
response started streaming, such as an async server component whose fetch
|
|
@@ -216,6 +219,30 @@ the variant suffixes are still appended.
|
|
|
216
219
|
error during the render. The current request still gets the errored render,
|
|
217
220
|
the next request renders fresh, and on a stale refresh the stale entry keeps
|
|
218
221
|
serving.
|
|
222
|
+
- A page rendered with a visitor's stored theme other than `defaultTheme`.
|
|
223
|
+
That happens only when `Cache-Control` was set after the render (see
|
|
224
|
+
"Theme" below).
|
|
225
|
+
|
|
226
|
+
## Theme
|
|
227
|
+
|
|
228
|
+
With `theme` enabled, the page payload carries the theme `useTheme()` starts
|
|
229
|
+
at (`initialTheme`). A page whose response opted in to this cache before
|
|
230
|
+
`next()` starts it at the no-cookie default (`defaultTheme`), whoever rendered
|
|
231
|
+
it (#978), so a visitor with no stored theme never inherits the theme of the
|
|
232
|
+
visitor who warmed the entry. A visitor with a stored theme sees theirs after
|
|
233
|
+
hydration, and the `<html>` class is right before paint either way (the theme
|
|
234
|
+
script reads the cookie). Any other render, a 404 included, starts at the
|
|
235
|
+
visitor's own theme.
|
|
236
|
+
|
|
237
|
+
The render decides from the `Cache-Control` the handler or middleware set
|
|
238
|
+
before `await next()`. A header set after `next()` (or in `onResponse`) arrives
|
|
239
|
+
once the page is rendered, so the cache stores that response only when it was
|
|
240
|
+
rendered with the default theme. Set `Cache-Control` in the handler or before
|
|
241
|
+
`next()` so every visitor's miss can fill the entry.
|
|
242
|
+
|
|
243
|
+
A handler that reads `ctx.theme` on a stored route is not guarded: the stored
|
|
244
|
+
document shows that visitor's theme to everyone. Read the theme with
|
|
245
|
+
`useTheme()` instead (see `/theme`).
|
|
219
246
|
|
|
220
247
|
## Invalidation
|
|
221
248
|
|
|
@@ -224,7 +251,10 @@ with the request's tag set, so `updateTag()`/`revalidateTag()` evict them:
|
|
|
224
251
|
|
|
225
252
|
- a render-callable `cacheTag("...")` in any server component on the page;
|
|
226
253
|
- tags of `cache()` segments, cached loaders, and `"use cache"` functions the
|
|
227
|
-
render read.
|
|
254
|
+
render read. A `cache()` segment HIT contributes the tags its content
|
|
255
|
+
recorded when it was written (render-callable calls and `"use cache"` reads
|
|
256
|
+
inside it), so a document stored over the HIT stays evictable by them. A
|
|
257
|
+
cached loader's HIT likewise contributes the tags its body recorded.
|
|
228
258
|
|
|
229
259
|
Tags are snapshotted after the response body has fully streamed, so tags from
|
|
230
260
|
Suspense-streamed content are included. A `Prerender()` route's build-time
|
|
@@ -184,6 +184,8 @@ parallel({
|
|
|
184
184
|
});
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
+
On a `ppr` route, a slot's `loading()` over handler output does not make a hole. A slot handler that awaits `ctx.use(CartLoader)` is handler output: it runs at capture, its value is baked into the shared shell, and every HIT replays it without running the handler. A `cookies()`/`headers()` read in that loader refuses the capture, so the route stays on MISS. For a per-request slot, read the loader with `useLoader()` in a `"use client"` component under the slot's `loading()` (see `/ppr` → "The layout-with-loaders playbook").
|
|
188
|
+
|
|
187
189
|
### Two scopes for explicit `use` at the mount site: shared (broadcast) and slot-local
|
|
188
190
|
|
|
189
191
|
`parallel()` accepts an explicit `use()` callback that **broadcasts** to every slot in the call ([dsl-helpers.ts](../../src/route-definition/dsl-helpers.ts)). That's the right behavior for the items the parallel allow-list permits and that accumulate (`loader`, `revalidate`, `errorBoundary`, `notFoundBoundary`, `transition`) — every slot gets them. (Note: `middleware` is not allowed inside `parallel()`; see the allowed-types table above.)
|
package/skills/hooks/SKILL.md
CHANGED
|
@@ -66,7 +66,9 @@ the one for your case.
|
|
|
66
66
|
Rango-specific hook for tracking actions called outside a
|
|
67
67
|
`<form action={...}>` flow.
|
|
68
68
|
- [`./state.md`](./state.md) — `useLocationState` (persistent + flash state,
|
|
69
|
-
`.read()`/`.write()`/`.delete()`
|
|
69
|
+
`.read()`/`.read(location)`/`.write()`/`.delete()`, typed `state` on `router.push()` /
|
|
70
|
+
`router.replace()`, serializability errors that name the failing field),
|
|
71
|
+
`invalidateClientCache()`.
|
|
70
72
|
- [`./outlets.md`](./outlets.md) — `Outlet`, `ParallelOutlet`, `useOutlet`.
|
|
71
73
|
- [`./urls.md`](./urls.md) — `useParams`, `usePathname`, `useSearchParams`,
|
|
72
74
|
`useHref`, `useMount`, `useReverse`.
|
|
@@ -34,7 +34,9 @@ Writers: handlers AND loader bodies — `ctx.use(Handle)` returns the push
|
|
|
34
34
|
function in each (middleware has no handle APIs). Loader pushes follow a delivery race (settle before
|
|
35
35
|
the handler barrier → in the SSR'd document; later → applied client-side), and
|
|
36
36
|
`loader(Def, { ssr: false })` makes them deterministic in the
|
|
37
|
-
document.
|
|
37
|
+
document. On a `ppr` shell HIT, a loader without `ssr: false` never ran during
|
|
38
|
+
the capture, so its pushes always apply client-side; with `ssr: false` they
|
|
39
|
+
bake into the shell. See `/loader` → "Writing Handles from Loaders". `useHandle`
|
|
38
40
|
re-renders on every handle update, late loader pushes included.
|
|
39
41
|
|
|
40
42
|
Handles can be passed as props from server to client components:
|
|
@@ -67,8 +67,10 @@ function NavigationControls() {
|
|
|
67
67
|
| `forward()` | `history.forward()`. |
|
|
68
68
|
|
|
69
69
|
`push` / `replace` options: `scroll` (`false` keeps the current scroll
|
|
70
|
-
position), `revalidate` (default `true`, see below),
|
|
71
|
-
|
|
70
|
+
position), `revalidate` (default `true`, see below), `transition` (`false`
|
|
71
|
+
skips the route's `transition()` for this navigation, see below), and `state`
|
|
72
|
+
(location state: `[MyState(value)]` for typed entries, or plain
|
|
73
|
+
structured-clone-safe data; see [`./state.md`](./state.md)).
|
|
72
74
|
|
|
73
75
|
Target resolution: a path starting with `/` is app-absolute and gets the
|
|
74
76
|
router `basename` prefixed. A relative path (`"cart"`, `"./cart"`) resolves
|
|
@@ -95,6 +97,25 @@ If the pathname changes, `revalidate: false` is silently ignored and a full navi
|
|
|
95
97
|
|
|
96
98
|
Plain `<a>` tags can opt in via `data-revalidate="false"`.
|
|
97
99
|
|
|
100
|
+
#### Skipping the transition
|
|
101
|
+
|
|
102
|
+
Pass `transition: false` to present one navigation without the route's
|
|
103
|
+
`transition()`: the commit is urgent (the `loading()` skeleton streams) and no
|
|
104
|
+
view transition runs, the same result as a `transition({ when })` predicate
|
|
105
|
+
returning `false`. No predicate is called. Back/forward, actions and
|
|
106
|
+
revalidation are unaffected.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
router.push("/photos/2", { transition: false });
|
|
110
|
+
|
|
111
|
+
<Link to="/photos/2" transition={false}>
|
|
112
|
+
Next
|
|
113
|
+
</Link>;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Plain `<a>` tags can opt in via `data-transition="false"`. See
|
|
117
|
+
`/view-transitions` → "Conditional transitions".
|
|
118
|
+
|
|
98
119
|
### useSegments()
|
|
99
120
|
|
|
100
121
|
Access current URL path and matched route segments:
|
package/skills/hooks/state.md
CHANGED
|
@@ -12,6 +12,9 @@ import { useLocationState, createLocationState } from "@rangojs/router/client";
|
|
|
12
12
|
|
|
13
13
|
// Define typed state (all export patterns supported)
|
|
14
14
|
// Keys are auto-injected by the Vite plugin -- no manual key needed.
|
|
15
|
+
// In a unit test (no plugin), key it with withLocationStateKey(ProductState,
|
|
16
|
+
// "ProductState") from @rangojs/router/testing: outside production, an
|
|
17
|
+
// unkeyed definition throws on first use.
|
|
15
18
|
export const ProductState = createLocationState<{
|
|
16
19
|
name: string;
|
|
17
20
|
price: number;
|
|
@@ -81,8 +84,87 @@ definition:
|
|
|
81
84
|
const state = useLocationState<{ from?: string }>(); // { from?: string } | undefined
|
|
82
85
|
```
|
|
83
86
|
|
|
87
|
+
### State on router.push() / router.replace()
|
|
88
|
+
|
|
84
89
|
The same `state` option exists on `router.push()` / `router.replace()` (see
|
|
85
|
-
[`./navigation.md`](./navigation.md))
|
|
90
|
+
[`./navigation.md`](./navigation.md)), with the same type as `<Link state>`
|
|
91
|
+
(minus the click-time getter): `HistoryState`, which is
|
|
92
|
+
`readonly LocationStateEntry[]` (typed entries) or `PlainHistoryState` (plain,
|
|
93
|
+
structured-clone-safe data). Both types are exported from
|
|
94
|
+
`@rangojs/router/client`.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
"use client";
|
|
98
|
+
import { useLocationState, useRouter } from "@rangojs/router/client";
|
|
99
|
+
import { GridState } from "./state"; // createLocationState<{ count: number }>()
|
|
100
|
+
|
|
101
|
+
function LoadMore() {
|
|
102
|
+
const router = useRouter();
|
|
103
|
+
const grid = useLocationState(GridState);
|
|
104
|
+
const count = grid?.count ?? 20;
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
onClick={() =>
|
|
108
|
+
router.replace("?page=2", { state: [GridState({ count: count + 20 })] })
|
|
109
|
+
}
|
|
110
|
+
>
|
|
111
|
+
Load more
|
|
112
|
+
</button>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Typed entries always go in an array, with nothing else in it. Three mistakes
|
|
118
|
+
are compile errors:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
router.push(url, { state: GridState({ count: 3 }) }); // entry without the array
|
|
122
|
+
router.push(url, { state: [GridState] }); // definition, not called
|
|
123
|
+
router.push(url, { state: [GridState({ count: 3 }), { from: "list" }] }); // mixed
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Without the array, the entry's own `__rsc_ls_*` fields would land on
|
|
127
|
+
`history.state` and `useLocationState(GridState)` would read `undefined`; an
|
|
128
|
+
uncalled definition throws `DataCloneError` in `history.pushState`; in a mixed
|
|
129
|
+
array only the first element picks the format, so the rest are lost. For
|
|
130
|
+
untyped (JS) callers, development builds throw an error for each that names
|
|
131
|
+
the definition's key (or the offending index) and the fix.
|
|
132
|
+
|
|
133
|
+
Plain state is checked at the top level only. It rejects functions, symbols,
|
|
134
|
+
`Promise`, `WeakMap`, `WeakSet`, and values typed `unknown` (narrow an
|
|
135
|
+
`unknown` value, or annotate it as `PlainHistoryState`). Array, `Map`, and
|
|
136
|
+
`Set` elements are checked the same way, but plain object members are not.
|
|
137
|
+
These still compile and still throw `DataCloneError` at `history.pushState`:
|
|
138
|
+
|
|
139
|
+
- a function, symbol, or React element inside a plain object
|
|
140
|
+
(`{ onClose: () => {} }`);
|
|
141
|
+
- a top-level `ReactElement` or DOM node (`HTMLElement`), which look like
|
|
142
|
+
plain objects to the type.
|
|
143
|
+
|
|
144
|
+
Store plain data and rebuild the rest on arrival, or use a typed
|
|
145
|
+
`createLocationState<T>()` definition, whose `T` is checked all the way down.
|
|
146
|
+
|
|
147
|
+
### When a type fails the serializability check
|
|
148
|
+
|
|
149
|
+
`createLocationState<T>()` rejects values that cannot survive
|
|
150
|
+
`history.state`'s structured clone: functions, class constructors, symbols,
|
|
151
|
+
React/RSC content, and fields typed `unknown`. The compile error names the
|
|
152
|
+
failing field and the reason:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Argument of type '{ items: never[]; cursor: string; }' is not assignable to
|
|
156
|
+
parameter of type '... & LocationStateUnsafe<"`unknown` cannot be verified as
|
|
157
|
+
serializable; give it a concrete type", "items[].info.values">'.
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The path uses `.` for fields, `[string]` / `[number]` for index signatures
|
|
161
|
+
(`Record<string, T>`), `[]` for array and `Set` elements, `[0]` for tuple
|
|
162
|
+
elements, `<key>` / `<value>` for `Map` keys and values, and `<root>` when the
|
|
163
|
+
value itself is unsafe. A field typed `any` is never reported. When several fields fail, the error lists one `LocationStateUnsafe` per
|
|
164
|
+
path. An object's own unsafe fields are reported before anything nested
|
|
165
|
+
deeper, so a class instance or DOM node stops at its methods
|
|
166
|
+
(`row.el.click`); fix those and the next compile names the rest. The walk
|
|
167
|
+
stops 8 levels deep, reporting the deepest path it reached.
|
|
86
168
|
|
|
87
169
|
### Flash State (read-once)
|
|
88
170
|
|
|
@@ -160,6 +242,15 @@ const flash = FlashMessage.read();
|
|
|
160
242
|
const product = ProductState.read();
|
|
161
243
|
```
|
|
162
244
|
|
|
245
|
+
`.read(location)` reads this definition's slot from any `{ state }` snapshot
|
|
246
|
+
instead of the current entry, such as the `from` / `to` of a
|
|
247
|
+
`transition({ when })` context. It works without `window` and never clears
|
|
248
|
+
flash state:
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
transition({ when: ({ to }) => ProductState.read(to) !== undefined });
|
|
252
|
+
```
|
|
253
|
+
|
|
163
254
|
> **Hydration:** `.read()` returns `undefined` on the server but may return
|
|
164
255
|
> a real value on the first client render (history state survives reload).
|
|
165
256
|
> Do not call `.read()` directly during the initial render of a component;
|
|
@@ -191,11 +282,12 @@ const current = ProductState.read();
|
|
|
191
282
|
ProductState.delete();
|
|
192
283
|
```
|
|
193
284
|
|
|
194
|
-
| Method
|
|
195
|
-
|
|
|
196
|
-
| `.read()`
|
|
197
|
-
| `.
|
|
198
|
-
| `.
|
|
285
|
+
| Method | Updates `history.state` | Fires `useLocationState` rerender | SSR behavior |
|
|
286
|
+
| ----------------- | ----------------------- | --------------------------------- | ------------------- |
|
|
287
|
+
| `.read()` | no | n/a (returns snapshot) | returns `undefined` |
|
|
288
|
+
| `.read(location)` | no | n/a (reads the given snapshot) | reads the snapshot |
|
|
289
|
+
| `.write()` | yes (replace this slot) | no | throws |
|
|
290
|
+
| `.delete()` | yes (remove this slot) | no | throws |
|
|
199
291
|
|
|
200
292
|
## Cache Control
|
|
201
293
|
|
|
@@ -214,7 +306,9 @@ it is selected by export conditions:
|
|
|
214
306
|
a rotated state `Set-Cookie` for the responding client, which marks its
|
|
215
307
|
caches stale when it next reads them. Idempotent within a request; throws
|
|
216
308
|
inside a `cache()` / `"use cache"` boundary; a no-op with a dev warning
|
|
217
|
-
outside a request.
|
|
309
|
+
outside a request. On a `ppr` route a shell HIT runs no handler, so a call
|
|
310
|
+
from handler code writes the cookie only on the renders that run it (a
|
|
311
|
+
MISS); call it from middleware, which runs on every request, HITs included.
|
|
218
312
|
|
|
219
313
|
Server actions already invalidate automatically; to suppress that for a no-op
|
|
220
314
|
action, call `keepClientCache()` inside it (see `/server-actions` → "Client
|