@rangojs/router 0.20.0 → 0.22.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/dist/bin/rango.js +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +29 -4
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/build-shell-capture.d.ts +8 -6
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +12 -6
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/build-shell-capture.ts +11 -9
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
package/skills/loader/SKILL.md
CHANGED
|
@@ -612,6 +612,10 @@ included. A handler's `ctx.use(Loader)` read never caches by itself (see
|
|
|
612
612
|
body, gets the binding's value: one value per loader per request, a HIT
|
|
613
613
|
included. A reader that runs before the binding starts (a layout's loader
|
|
614
614
|
reading a route's cached loader) gets the value of its own run, as before.
|
|
615
|
+
This is by design: that loader runs live while the binding serves its entry,
|
|
616
|
+
so its data and its handle push can come from different runs on that request.
|
|
617
|
+
To keep them together, declare the cached loader first or read it from the
|
|
618
|
+
handler.
|
|
615
619
|
|
|
616
620
|
The loader's data is cached independently from the route's segment cache,
|
|
617
621
|
using the same `SegmentCacheStore` (app-level or per-loader override).
|
|
@@ -648,11 +652,21 @@ rejects) skips the whole entry, as above. Entries written
|
|
|
648
652
|
before handles were recorded replay none until they are rewritten (expiry or
|
|
649
653
|
SWR revalidation).
|
|
650
654
|
|
|
655
|
+
On a `ppr` route the shell keeps a copy of an `ssr: false` loader's settled
|
|
656
|
+
pushes too. The two never disagree on the page: where a replay of the shell
|
|
657
|
+
serves the loader's data from the shell, the shell's copy is the one shown
|
|
658
|
+
and the entry only adds the deferred push the shell could not keep; where it
|
|
659
|
+
does not (the loader runs or reads its entry), the entry's pushes take the
|
|
660
|
+
shell copy's place, none if the entry recorded none. See `/ppr` → "On a shell
|
|
661
|
+
HIT".
|
|
662
|
+
|
|
651
663
|
### Cache Key
|
|
652
664
|
|
|
653
|
-
The default cache key is
|
|
654
|
-
|
|
655
|
-
|
|
665
|
+
The default cache key is
|
|
666
|
+
`loader:{loaderId}:{routerId}@{host}{pathname}:{sortedParams}`: the host keeps
|
|
667
|
+
multi-tenant hosts from sharing entries, and the router id keeps two routers
|
|
668
|
+
that mount the loader on one store apart (see `/host-router`, "Shared cache
|
|
669
|
+
store"). This can be customized at two levels:
|
|
656
670
|
|
|
657
671
|
```typescript
|
|
658
672
|
import { cookies } from "@rangojs/router";
|
|
@@ -676,7 +690,7 @@ Resolution priority (same order as route-level `cache()`):
|
|
|
676
690
|
`loader:{loaderId}:key:` plus the result's URI encoding
|
|
677
691
|
2. `store.keyGenerator(ctx, defaultKey)` — store-level modification, stored
|
|
678
692
|
as returned
|
|
679
|
-
3. Default key — `loader:{loaderId}:{host}{pathname}:{sortedParams}`
|
|
693
|
+
3. Default key — `loader:{loaderId}:{routerId}@{host}{pathname}:{sortedParams}`
|
|
680
694
|
|
|
681
695
|
Because the `key()` result is namespaced by the loader id, no value it
|
|
682
696
|
returns, request input included, can name another loader's entry or a
|
|
@@ -993,7 +1007,13 @@ on document loads (`metadata.handlesLate`) or progressively on navigations.
|
|
|
993
1007
|
A push before your slow fetch usually beats the barrier; a push derived from
|
|
994
1008
|
the fetched data usually does not. When it MUST be in the document, use
|
|
995
1009
|
`ssr: false` below. A loader with its own `cache()` keeps its pushes on a hit —
|
|
996
|
-
they are replayed from the entry (see "Opting a Loader into Caching").
|
|
1010
|
+
they are replayed from the entry (see "Opting a Loader into Caching"). On a
|
|
1011
|
+
`ppr` route the race does not apply to a document served from its shell:
|
|
1012
|
+
there every push a loader makes on that request applies post-hydration (see
|
|
1013
|
+
`/ppr`, "Handles on a shell HIT"). A `useHandle` reader inside this loader's
|
|
1014
|
+
`loading()` or `<Suspense>` hydrates with the data its HTML was rendered
|
|
1015
|
+
from and shows a later push right after, so a push after an `await` causes
|
|
1016
|
+
no hydration mismatch there.
|
|
997
1017
|
|
|
998
1018
|
Under `cache()`, loader pushes are not stored with the cached segments: the
|
|
999
1019
|
loader re-runs on every hit and its live push is the only copy. (`ppr` keeps
|
|
@@ -426,6 +426,8 @@ export const requireAuthMiddleware: Middleware = async (ctx, next) => {
|
|
|
426
426
|
|
|
427
427
|
Read the flash on the target page with `useLocationState(FlashMessage)`. The `{ flash: true }` option makes it auto-clear after first render. See `/hooks`.
|
|
428
428
|
|
|
429
|
+
A redirect is followed on a link click, `router.push()`, and Back/Forward to an entry the client has to fetch. On Back/Forward the redirect replaces the history entry it was triggered from: Back from the target lands on the entry before the redirecting one, not on the redirecting one again. The client follows at most 20 redirects in one navigation; a loop (two pages redirecting to each other) stops there with the error boundary and a `[rango] Server redirect loop` console error.
|
|
430
|
+
|
|
429
431
|
## Authentication Middleware
|
|
430
432
|
|
|
431
433
|
```typescript
|
|
@@ -416,13 +416,82 @@ default) or serves an open-ended param space. See `/prerender` for full API.
|
|
|
416
416
|
|
|
417
417
|
Next.js route segment config maps onto Rango's explicit primitives:
|
|
418
418
|
|
|
419
|
-
| Next.js segment config | Rango
|
|
420
|
-
| --------------------------------------------------- |
|
|
421
|
-
| `dynamic = "force-static"` + `generateStaticParams` | `Static()` / `Prerender()` (see `/prerender`)
|
|
422
|
-
| `revalidate = 60` (ISR) | `cache({ ttl: 60, swr: ... })` on the route (see `/caching`) |
|
|
423
|
-
| `dynamic = "force-dynamic"` | the default — routes are dynamic unless you cache them
|
|
424
|
-
| `dynamicParams = true` | `Passthrough()` (above)
|
|
425
|
-
| `experimental_ppr = true` | the `ppr` path option (below, and `/ppr`)
|
|
419
|
+
| Next.js segment config | Rango |
|
|
420
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
421
|
+
| `dynamic = "force-static"` + `generateStaticParams` | `Static()` / `Prerender()` (see `/prerender`) |
|
|
422
|
+
| `revalidate = 60` (ISR) | `cache({ ttl: 60, swr: ... })` on the route (see `/caching`), or on-demand prerender with `onDemand: { ttl: 60 }` (below) |
|
|
423
|
+
| `dynamic = "force-dynamic"` | the default — routes are dynamic unless you cache them |
|
|
424
|
+
| `dynamicParams = true` | `Passthrough()` (above) |
|
|
425
|
+
| `experimental_ppr = true` | the `ppr` path option (below, and `/ppr`) |
|
|
426
|
+
|
|
427
|
+
### On-demand ISR → `Prerender(..., { onDemand })` + `router.prerender()`
|
|
428
|
+
|
|
429
|
+
A Next.js page with `generateStaticParams` refreshed by `revalidatePath()` or
|
|
430
|
+
`revalidateTag()` from a CMS webhook maps to an on-demand prerender route. The
|
|
431
|
+
route opts in, a store holds refreshed payloads, and the webhook re-renders the
|
|
432
|
+
page instead of invalidating it:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// Next.js:
|
|
436
|
+
export const revalidate = 3600;
|
|
437
|
+
export async function generateStaticParams() {
|
|
438
|
+
return (await getFeatured()).map((p) => ({ id: p.id }));
|
|
439
|
+
}
|
|
440
|
+
// app/api/revalidate/route.ts
|
|
441
|
+
revalidatePath(`/products/${id}`);
|
|
442
|
+
revalidateTag(`product:${id}`);
|
|
443
|
+
|
|
444
|
+
// Rango:
|
|
445
|
+
export const ProductPage = Prerender<{ id: string }>(
|
|
446
|
+
async () => (await getFeatured()).map((p) => ({ id: p.id })),
|
|
447
|
+
async (ctx) => <Product data={await getProduct(ctx.params.id)} />,
|
|
448
|
+
{ onDemand: { ttl: 3600, tags: ({ params }) => [`product:${params.id}`] } },
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
// router: createRouter({ prerender: { store, ttl, onRevalidate? } })
|
|
452
|
+
// webhook (route handler, cron or queue):
|
|
453
|
+
const prerender = router.prerender({ env, ctx });
|
|
454
|
+
await prerender(`/products/${id}`); // revalidatePath
|
|
455
|
+
await prerender.markStale([`product:${id}`]); // revalidateTag
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
| Next.js | Rango |
|
|
459
|
+
| ------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
460
|
+
| `revalidatePath(path)` on an ISR page | `await router.prerender({ env, ctx })(path)`: renders now, serves the new payload next request |
|
|
461
|
+
| `revalidateTag(tag)` on ISR pages | `router.prerender({ env, ctx }).markStale([tag])`: marks only; a stale hit schedules `onRevalidate` |
|
|
462
|
+
| `revalidate = N` (time-based ISR) | `onDemand: { ttl: N }` + `onRevalidate`; `ttl` is soft, entries never expire |
|
|
463
|
+
| `dynamicParams = false` | plain on-demand `Prerender`: an unknown param 404s until something refreshes it |
|
|
464
|
+
| `dynamicParams = true` | wrap it in `Passthrough()`: unknown params render live until refreshed |
|
|
465
|
+
|
|
466
|
+
A page whose item was deleted. In the Pages Router, `getStaticProps` returns
|
|
467
|
+
`{ notFound: true }` on a revalidation: "With `notFound: true`, the page will
|
|
468
|
+
return a `404` even if there was a successfully generated page before. This is
|
|
469
|
+
meant to support use cases like user-generated content getting removed by its
|
|
470
|
+
author. Note, `notFound` follows the same `revalidate` behavior described
|
|
471
|
+
here." In Rango a `notFound()` anywhere in the on-demand render (the route
|
|
472
|
+
handler, a layout or slot handler, any server component) does the same on the
|
|
473
|
+
next `prerender(path)`: the result is `{ ok: true, status: "removed" }`, the
|
|
474
|
+
page answers 404 instead of the page or its build-time entry, and the removal
|
|
475
|
+
is rechecked per the route's `ttl`, as Next's is per `revalidate`. When the
|
|
476
|
+
data source may be failing, throw anything else: that keeps the page.
|
|
477
|
+
`prerender.remove(path)` removes without rendering, for a webhook that already
|
|
478
|
+
knows the item is gone, and is permanent until a `prerender(path)` renders the
|
|
479
|
+
page again. See `/prerender` → "Remove a page".
|
|
480
|
+
|
|
481
|
+
Three differences to plan for:
|
|
482
|
+
|
|
483
|
+
- Prerender tags are their own namespace. `updateTag()` / `revalidateTag()`
|
|
484
|
+
from `@rangojs/router` purge the runtime cache and never reach on-demand
|
|
485
|
+
entries; `markStale()` never reaches the runtime cache.
|
|
486
|
+
- A refresh is requestless: `cookies()` / `headers()` in the page make it
|
|
487
|
+
return `skipped-personalized`, and `ctx.env` is the live env you pass.
|
|
488
|
+
- `markStale()` does not re-render: without `onRevalidate` or a cron calling
|
|
489
|
+
`prerender(target, { onlyIfStale: true })` (the runner from
|
|
490
|
+
`router.prerender({ env, ctx })`), a marked page keeps
|
|
491
|
+
serving its old payload.
|
|
492
|
+
|
|
493
|
+
See `/prerender` → "On-demand refresh (ISR)" for the store options, the serve
|
|
494
|
+
table and the rules.
|
|
426
495
|
|
|
427
496
|
### Partial prerendering → the `ppr` path option
|
|
428
497
|
|
|
@@ -552,7 +621,9 @@ cookie (`rango-state-fresh`); other users, and other locations on
|
|
|
552
621
|
propagates (`/caching` → "The fresh-reads cookie").
|
|
553
622
|
Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`, `VercelCacheStore`)
|
|
554
623
|
index by tag. Next's
|
|
555
|
-
`revalidatePath` has no path-based equivalent — tag the
|
|
624
|
+
`revalidatePath` has no path-based equivalent for runtime-cached data — tag the
|
|
625
|
+
relevant entries instead. For a prerendered ISR page it is
|
|
626
|
+
`router.prerender({ env, ctx })(path)` (see "On-demand ISR" above).
|
|
556
627
|
|
|
557
628
|
**2. Partial-render selection (which segments re-run after an action).** This is
|
|
558
629
|
NOT cache invalidation — it is `revalidate()`, controlling which segments
|
|
@@ -594,7 +665,9 @@ When migrating:
|
|
|
594
665
|
|
|
595
666
|
- `revalidateTag(tag)` → `await updateTag(tag)` (in a server action) or
|
|
596
667
|
`revalidateTag(tag)` (in a route handler / webhook). Effectively 1:1.
|
|
597
|
-
- `revalidatePath(path)`
|
|
668
|
+
- `revalidatePath(path)` on an ISR page → `router.prerender({ env, ctx })(path)`
|
|
669
|
+
(on-demand prerender, §3); on runtime-cached data, no path-based
|
|
670
|
+
equivalent; tag the entries on that
|
|
598
671
|
route (`cache({ tags })` / `cacheTag(...)`) and invalidate by tag.
|
|
599
672
|
- To also force specific segments to re-render after the action (independent of
|
|
600
673
|
cache busting), attach a `revalidate()` rule at those segment boundaries.
|
package/skills/ppr/SKILL.md
CHANGED
|
@@ -53,7 +53,8 @@ implements the shell family (`getShell`/`putShell`): `MemorySegmentCacheStore`
|
|
|
53
53
|
`VercelCacheStore` (Vercel Runtime Cache). A ppr route on a store without the
|
|
54
54
|
family renders normally (no shell) with a once-per-key warning. A custom store
|
|
55
55
|
returns each `ShellCacheEntry` field as `putShell` received it, including
|
|
56
|
-
`buildVersion` and `snapshot` (both required;
|
|
56
|
+
`buildVersion` (the router's document version) and `snapshot` (both required;
|
|
57
|
+
the snapshot can be empty).
|
|
57
58
|
|
|
58
59
|
```typescript
|
|
59
60
|
import { createRouter, urls } from "@rangojs/router";
|
|
@@ -169,7 +170,7 @@ The partition follows nested scopes (issue #970): a `ppr` route under a
|
|
|
169
170
|
inner boundary has none, and by both composed (outermost first, joined by
|
|
170
171
|
`|`) when it has its own. Each `key()` result is namespaced as `key:` plus
|
|
171
172
|
its URI encoding (issue #975), exactly as in the record key, so the shell of
|
|
172
|
-
`key: () => "tier:gold"` is `<host><path>:shell|key%3Atier%253Agold`. The
|
|
173
|
+
`key: () => "tier:gold"` is `<router>@<host><path>:shell|key%3Atier%253Agold`. The
|
|
173
174
|
route's record adds the inner boundary's own default key when it has no
|
|
174
175
|
`key`; the shell partition does not, since the shell key already carries the
|
|
175
176
|
URL (a store `keyGenerator`'s result is kept when it adds anything, an
|
|
@@ -219,10 +220,10 @@ does not render from: otherwise the first variant captured is served for every
|
|
|
219
220
|
value of it.
|
|
220
221
|
|
|
221
222
|
In tests, `serveShellRequest` reports the key the serve path resolved
|
|
222
|
-
(`result.key`, partition included); `shellCacheKey(url, searchParams,
|
|
223
|
+
(`result.key`, partition included); `shellCacheKey(router, url, searchParams,
|
|
223
224
|
partition)` builds the same key from the `key()` result, namespacing it as
|
|
224
225
|
production does. Under nested keyed scopes, pass the `key()` results as an
|
|
225
|
-
array, outermost first (`shellCacheKey(url, undefined, ["tier:gold",
|
|
226
|
+
array, outermost first (`shellCacheKey(router, url, undefined, ["tier:gold",
|
|
226
227
|
"v:a"])`); for a store `keyGenerator` partition pass
|
|
227
228
|
`{ keys, generated }` (`/testing`, `cache-prerender.md`).
|
|
228
229
|
|
|
@@ -250,7 +251,9 @@ under `cache()` and under the shell alike.
|
|
|
250
251
|
|
|
251
252
|
Invalidation crosses layers: `updateTag()`/`revalidateTag()` reach segment,
|
|
252
253
|
shell, loader, and item entries in the same store, and shell entries
|
|
253
|
-
additionally self-invalidate on `React.version` change
|
|
254
|
+
additionally self-invalidate on `React.version` change and when the router's
|
|
255
|
+
document version changes (server code, SSR output, client asset names, `base`
|
|
256
|
+
or Prerender payloads changed), not on every deploy.
|
|
254
257
|
|
|
255
258
|
## The serve pipeline: commit after ALL middleware
|
|
256
259
|
|
|
@@ -261,7 +264,7 @@ On a document GET to a ppr route the router runs:
|
|
|
261
264
|
DSL `middleware()`; both are guards, and the COMMIT POINT is after all of
|
|
262
265
|
them: any rejection/redirect/401 wins before a single shell byte, on MISS
|
|
263
266
|
and on a warmed HIT alike;
|
|
264
|
-
3. **shell lookup** — `getShell(key)` on the app store (key =
|
|
267
|
+
3. **shell lookup** — `getShell(key)` on the app store (key = router id +
|
|
265
268
|
host+pathname+sorted search). `CFCacheStore` stores the entry prelude-first
|
|
266
269
|
and the serve path reads only its head and prelude here, with the tag-marker
|
|
267
270
|
read running alongside; the capture snapshot is read off the commit path.
|
|
@@ -383,10 +386,12 @@ encountered by the shell capture.
|
|
|
383
386
|
Two more decisions are made before any shell-store read, so probes and
|
|
384
387
|
prerendered routes never spend passive `getShell` I/O:
|
|
385
388
|
|
|
386
|
-
- A partial request carrying neither `X-RSC-Router-Client-Path` nor
|
|
387
|
-
(a curl probe, a synthetic monitor) can never produce
|
|
388
|
-
reports `BYPASS; reason=no-navigation-context`.
|
|
389
|
-
|
|
389
|
+
- A partial request carrying neither `X-RSC-Router-Client-Path` nor a
|
|
390
|
+
same-origin `Referer` (a curl probe, a synthetic monitor) can never produce
|
|
391
|
+
a partial match; it reports `BYPASS; reason=no-navigation-context`. A
|
|
392
|
+
`Referer` on another origin counts as absent: only its pathname would be
|
|
393
|
+
matched, as if it were a route of this app. Alert on replay hit-rate with
|
|
394
|
+
this in mind — such probes are not cache misses.
|
|
390
395
|
- A `Prerender()` route's partial is served from the build-time prerender
|
|
391
396
|
store inside matching (a better-than-HIT outcome); it reports
|
|
392
397
|
`BYPASS; reason=prerender-store`. Its captures never record the page's
|
|
@@ -436,7 +441,7 @@ The bounded bypass tokens, grouped by when they are decided:
|
|
|
436
441
|
| Token | Decided | Meaning |
|
|
437
442
|
| ----------------------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
438
443
|
| `method`, `dynamic`, `nonce`, `store-unavailable`, `passive-read-unsupported` | pre-read | request/route/store ineligible for replay |
|
|
439
|
-
| `no-navigation-context` | pre-read | no `X-RSC-Router-Client-Path
|
|
444
|
+
| `no-navigation-context` | pre-read | no `X-RSC-Router-Client-Path` and no same-origin `Referer`; a partial match is impossible |
|
|
440
445
|
| `prerender-store` | pre-read or match | `Prerender()` route served by its baked artifact (pre-read probe of the normal variant; reclassified post-match when the store actually served, either variant) |
|
|
441
446
|
| `intercept` | match | the navigation resolved to an intercept — replay is never armed for intercepts (they keep their normal cache path); no heal capture |
|
|
442
447
|
| `cache-disabled` | pre-read or match | `cache(false)` (pre-read, static) or `condition()` false (decided at the lookup); consumer opt-out is absolute |
|
|
@@ -556,6 +561,19 @@ curl -s -D - -o /dev/null https://app.example.com/products/1 | grep -i x-rango-s
|
|
|
556
561
|
writes, and marker rejection, with `cf-ray`/colo and read timings. The flag is baked at
|
|
557
562
|
build time; setting only a Worker runtime variable is too late.
|
|
558
563
|
|
|
564
|
+
### Capturing a shell before the first visitor
|
|
565
|
+
|
|
566
|
+
A shell is captured by the first document MISS for its key, so the first
|
|
567
|
+
visitor after a deploy or an `updateTag()` renders without one. To capture it
|
|
568
|
+
ahead of them, warm the URL: `router.prerender({ env, ctx })(url)` renders it
|
|
569
|
+
through the handler, schedules the capture and waits for it, so the next
|
|
570
|
+
document request is a HIT. A warm replaces a stored shell in place (the old
|
|
571
|
+
one serves until the new one is written) and reports the capture's outcome
|
|
572
|
+
(`result.caches.shell`, with `refusal` when the capture refused). It is not
|
|
573
|
+
subject to the per-key in-flight dedup or the refused-capture backoff, which
|
|
574
|
+
exist to stop a herd of visitors repeating work; every correctness guard on
|
|
575
|
+
this page still decides. See `/prerender` → "Warm any route before traffic".
|
|
576
|
+
|
|
559
577
|
### Unit / integration testing (public primitives)
|
|
560
578
|
|
|
561
579
|
Import from `@rangojs/router/testing` (Vitest) or `@rangojs/router/testing/e2e`
|
|
@@ -566,17 +584,18 @@ Import from `@rangojs/router/testing` (Vitest) or `@rangojs/router/testing/e2e`
|
|
|
566
584
|
| `assertShellStatus(res, "HIT" \| "MISS")` | Document Response from a real RSC serve / e2e `page.request` |
|
|
567
585
|
| `assertPprReplayStatus(res, expected)` | Partial response fresh/stale replay or bounded bypass |
|
|
568
586
|
| `parsePprReplayStatus(res)` | Read structured replay/bypass status or null |
|
|
569
|
-
| `shellCacheKey(url)`
|
|
587
|
+
| `shellCacheKey(router, url)` | Production store key for `store.getShell` / custom stores |
|
|
570
588
|
| `MemorySegmentCacheStore` + `getShell`/`putShell` | Custom store contract / tag eviction (no faked HIT) |
|
|
571
589
|
| `serveShellRequest(router, url, opts)` | A real MISS → capture → HIT (react-server Vitest) |
|
|
572
590
|
|
|
573
591
|
```ts
|
|
574
592
|
import { MemorySegmentCacheStore } from "@rangojs/router/cache";
|
|
575
593
|
import { assertShellStatus, shellCacheKey } from "@rangojs/router/testing";
|
|
594
|
+
import { router } from "./router";
|
|
576
595
|
|
|
577
596
|
// Unit: store + key (after a real putShell / capture flush in e2e)
|
|
578
597
|
const store = new MemorySegmentCacheStore();
|
|
579
|
-
const key = shellCacheKey("http://localhost/products/1");
|
|
598
|
+
const key = shellCacheKey(router, "http://localhost/products/1");
|
|
580
599
|
// ... after production putShell ...
|
|
581
600
|
expect(await store.getShell(key)).not.toBeNull();
|
|
582
601
|
|
|
@@ -783,6 +802,9 @@ identifiers — served to anonymous visitors.)
|
|
|
783
802
|
- `ctx.use(H)(promise)` from a handler — a TOP-LEVEL pushed promise is awaited
|
|
784
803
|
at capture and BAKED into the shell (bounded by `ppr.captureTimeout`, 15s by
|
|
785
804
|
default).
|
|
805
|
+
- A promise pushed from a LOADER is never in the shell and the capture does not
|
|
806
|
+
wait for it: however slow it is (or if it never settles), the shell is still
|
|
807
|
+
stored, and the HIT's own loader run delivers the value after hydration.
|
|
786
808
|
- `ctx.use(H)({ x: promise })` from a HANDLER — also baked. The capture waits
|
|
787
809
|
for every promise nested in a handler's push, at any depth of plain objects,
|
|
788
810
|
arrays and JSX props, before it records the handles every HIT replays. Keep
|
|
@@ -896,31 +918,109 @@ value once it refreshes, while the baked container keeps the capture's. If a
|
|
|
896
918
|
hole needs a stable value, give it its own cache (`"use cache"` with a
|
|
897
919
|
profile, or a loader `cache()`); the shell never provides one.
|
|
898
920
|
|
|
899
|
-
**Handle pushes from a bake-lane loader appear once
|
|
900
|
-
|
|
921
|
+
**Handle pushes from a bake-lane loader appear once, and they match its
|
|
922
|
+
data.** A loader's title, meta or breadcrumb describes the data next to it,
|
|
923
|
+
so both come from the same run on every way a shell can be replayed: a
|
|
924
|
+
document HIT, a client navigation, a prefetch. The capture records the
|
|
925
|
+
settled, thenable-free handle pushes of every loader body it runs (the
|
|
901
926
|
prelude rendered them): the `ssr: false` loader's own, those of the loaders
|
|
902
|
-
it awaits with `ctx.use()`, and those its own `cache()` entry replays.
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
927
|
+
it awaits with `ctx.use()`, and those its own `cache()` entry replays. What
|
|
928
|
+
a replay does with a loader's recorded pushes follows what it does with that
|
|
929
|
+
loader's data:
|
|
930
|
+
|
|
931
|
+
| The replay serves the loader's data from | Its settled pushes are |
|
|
932
|
+
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
933
|
+
| the shell (its pinned container) | the shell's: restored with the handler layer, and they stand. A loader that does run on the replay (a hole-carrying one) reads the store, and the settled pushes that run makes are dropped |
|
|
934
|
+
| a run on this request (a live-lane loader, or one the shell has no pin for) | that run's: they replace the shell's copies in place; a run that throws or makes no push shows none |
|
|
935
|
+
| the loader's own `cache()` entry | that entry's: the pushes it recorded, none if it recorded none |
|
|
936
|
+
|
|
937
|
+
So any handle, deduping or not, shows each value once. A pinned loader's
|
|
938
|
+
pushes are the values the prelude rendered, also for the pushes a `"use
|
|
939
|
+
cache"` hit inside its body replays. A loader the route also registers on
|
|
940
|
+
the live lane is a hole: its live run replaces its restored pushes, and those
|
|
941
|
+
of the loaders it awaits, so they show its live value, even when a bake-lane
|
|
942
|
+
loader running on the replay awaits it. On a document HIT the page hydrates
|
|
943
|
+
with the shell's copy and the hole's value takes its place after hydration
|
|
944
|
+
("Handles on a shell HIT" below). An
|
|
945
|
+
`ssr: false` loader is served from the shell only while the entry holds its
|
|
946
|
+
pin: a snapshot captured by a navigation alone carries none, and a shell over
|
|
947
|
+
`ppr.maxSnapshotBytes` drops them. There the loader runs fresh, and its
|
|
948
|
+
pushes are that run's, like its data.
|
|
949
|
+
|
|
950
|
+
A `Prerender` + `ppr` page follows the same table. Its handler layer comes
|
|
951
|
+
from the prerendered payload, which never holds a loader push (loaders do not
|
|
952
|
+
run at build), so the shell entry records the loader pushes its HTML rendered
|
|
953
|
+
on its own, and a HIT restores them as above (#1057).
|
|
954
|
+
|
|
955
|
+
A dependency the route does not register, which an `ssr: false` loader
|
|
956
|
+
awaited at capture outside any live loader, is on neither lane: its settled
|
|
957
|
+
pushes stay as the prelude rendered them, even where a live loader also
|
|
958
|
+
awaits it, while its data is fresh. (They follow the run instead once the
|
|
959
|
+
entry has lost a pin, because the loader that awaits it then runs.) To keep
|
|
960
|
+
its pushes live, declare it as its own `loader()` on the route. A push the
|
|
961
|
+
capture cannot record (a deferred push, or one holding a promise) marks every
|
|
962
|
+
loader record of the page to run: those bodies then still run on each replay,
|
|
963
|
+
in the background, and that push reaches the page (from the loader's own
|
|
964
|
+
`cache()` entry when it hits) with the run's value, on a document HIT and on
|
|
965
|
+
a client navigation alike. On a document HIT it arrives after hydration.
|
|
966
|
+
|
|
967
|
+
**Handles on a shell HIT.** A promise pushed from a loader is live and is
|
|
968
|
+
never in a shell. To have a value in the shell's HTML, push it settled
|
|
969
|
+
(await it) from the `ssr: false` loader, or push it from the handler.
|
|
970
|
+
|
|
971
|
+
Three things follow, in this order of scope:
|
|
972
|
+
|
|
973
|
+
1. **What is in a shell's HTML** changes only for promise pushes of
|
|
974
|
+
`ssr: false` loaders and of the loaders they await: `push(promise)`, or a
|
|
975
|
+
pushed value that holds a promise. Those run at capture, and the shell
|
|
976
|
+
renders no element for such a push. A loader without `ssr: false` never
|
|
977
|
+
ran at capture, so nothing of it was ever in the shell.
|
|
978
|
+
2. **On a shell HIT every loader push made during the request arrives after
|
|
979
|
+
hydration**, a live-lane loader's included. The document hydrates with
|
|
980
|
+
exactly the handle data the shell's HTML was rendered from (the handlers'
|
|
981
|
+
pushes and the settled pushes of the loaders that ran at capture), and
|
|
982
|
+
the request's own pushes follow as a normal `useHandle` update.
|
|
983
|
+
3. **`useHandle` on every document, `ppr` or not:** a reader that React is
|
|
984
|
+
hydrating reads the handle data its HTML was rendered from, wherever its
|
|
985
|
+
boundary hydrates, and takes the newer data right after. So a reader
|
|
986
|
+
inside the `loading()` or `<Suspense>` of a loader that pushes after an
|
|
987
|
+
`await` hydrates clean.
|
|
988
|
+
|
|
989
|
+
| On the HIT's request | In the HIT's HTML | The client |
|
|
990
|
+
| --------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------- |
|
|
991
|
+
| a handler's push, a pinned `ssr: false` loader's settled push | yes (the shell has it) | hydrates with it |
|
|
992
|
+
| a deferred push (`push(promise)`), or a push holding a promise, by a loader | no | gets it right after hydration |
|
|
993
|
+
| a live-lane loader's push | no | gets it right after hydration |
|
|
994
|
+
| the run of an `ssr: false` loader the shell has no pin for | the capture's push | hydrates with the capture's, then shows the run's |
|
|
995
|
+
|
|
996
|
+
A value a loader pushes as a promise is therefore in the HTML of a document
|
|
997
|
+
MISS (the render awaits it) and of a route without `ppr`, and not in the
|
|
998
|
+
HTML of a shell HIT. When a crawler or the first paint needs it, change one
|
|
999
|
+
line:
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
ctx.use(Notes)(db.stockNote(id)); // a promise: live, after hydration on a HIT
|
|
1003
|
+
ctx.use(Notes)(await db.stockNote(id)); // settled: baked into the shell
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
The settled push bakes with the shell and is invalidated with it (its TTL,
|
|
1007
|
+
its tags). A promise is not baked, whenever it settles: it is the shape that
|
|
1008
|
+
says "per request", the same as a promise in the loader's data.
|
|
1009
|
+
|
|
1010
|
+
A pinned loader's pushes are exactly the ones its capture made. If its body
|
|
1011
|
+
pushes a settled value only on some runs (behind a condition), a replay that
|
|
1012
|
+
runs it shows the capture's answer, a push or none, never a later run's next
|
|
1013
|
+
to the pinned data. A deferred push is the exception by design: it is
|
|
1014
|
+
delivered on every replay.
|
|
1015
|
+
|
|
1016
|
+
One arrangement mixes sources by design. A loader that reads a
|
|
1017
|
+
`cache()`-bound loader with `ctx.use()` before that binding starts (a loader
|
|
1018
|
+
declared ahead of it, a parent layout) runs it live while the binding serves
|
|
1019
|
+
its entry, so the cached loader's data and its handle push can come from
|
|
1020
|
+
different runs on that request. To keep them together, declare the cached
|
|
1021
|
+
loader first, or read it from the handler: a handler's `ctx.use()` is where a
|
|
1022
|
+
loader read is baked. An entry such a request writes holds no pushes, and a
|
|
1023
|
+
later HIT of it then shows none for that loader.
|
|
924
1024
|
|
|
925
1025
|
Four hard edges (each e2e/unit-pinned):
|
|
926
1026
|
|
|
@@ -1187,8 +1287,9 @@ eligible captured segment snapshot, per-request CSP nonce, `streamMode:
|
|
|
1187
1287
|
request the route's own `cache()` scope refuses (`cache(false)`, or a
|
|
1188
1288
|
`condition()` returning false for it), and any store without the shell family.
|
|
1189
1289
|
A stored shell is invalidated when
|
|
1190
|
-
`React.version` changes (postponed state is build-coupled)
|
|
1191
|
-
self-
|
|
1290
|
+
`React.version` changes (postponed state is build-coupled) or when the router's
|
|
1291
|
+
document version changes, so a deploy that changes the router self-heals via
|
|
1292
|
+
recapture, and a deploy that does not keeps its shells.
|
|
1192
1293
|
|
|
1193
1294
|
The per-request CSP nonce guarantee covers BOTH ways a nonce arrives — the
|
|
1194
1295
|
`createRouter({ nonce })` provider AND a direct `ctx.set(nonce, value)` token
|
|
@@ -1234,13 +1335,13 @@ path(
|
|
|
1234
1335
|
);
|
|
1235
1336
|
```
|
|
1236
1337
|
|
|
1237
|
-
| Field | Default | Notes
|
|
1238
|
-
| ------------------ | ------- |
|
|
1239
|
-
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default)
|
|
1240
|
-
| `swr` | — | stale window: serve the stale shell + background recapture
|
|
1241
|
-
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below
|
|
1242
|
-
| `maxSnapshotBytes` | 8 MiB | cap on the loader pins in the entry's snapshot (the bake-lane loader records). The recorded handler layer is exempt. Over the cap the pins are dropped and the shell is stored with its handler layer (warned once per key); bake-lane loaders then read the live store, and drift is repaired client-side |
|
|
1243
|
-
| `captureTimeout` | 15000ms | ONE capture deadline: the handler layer settling (promises it passes or pushes, async server components, loaders it awaits), bake-lane loaders, and the prerender; a capture that misses it stores nothing rather than a partial shell
|
|
1338
|
+
| Field | Default | Notes |
|
|
1339
|
+
| ------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1340
|
+
| `ttl` | `300` | shell freshness window in seconds (`ppr: true` uses the default) |
|
|
1341
|
+
| `swr` | — | stale window: serve the stale shell + background recapture |
|
|
1342
|
+
| `tags` | — | operational tags UNIONED with the tags the capture collects — see "Invalidation" below |
|
|
1343
|
+
| `maxSnapshotBytes` | 8 MiB | cap on the loader pins in the entry's snapshot (the bake-lane loader records). The recorded handler layer is exempt. Over the cap the pins are dropped and the shell is stored with its handler layer (warned once per key); bake-lane loaders then run on every replay and read the live store, their handle pushes come from that run like their data, and drift from the prelude is repaired client-side |
|
|
1344
|
+
| `captureTimeout` | 15000ms | ONE capture deadline: the handler layer settling (promises it passes or pushes, async server components, loaders it awaits), bake-lane loaders, and the prerender; a capture that misses it stores nothing rather than a partial shell |
|
|
1244
1345
|
|
|
1245
1346
|
A shell never outlives the route `cache()` entry it was captured from: under
|
|
1246
1347
|
a route `cache()`, the shell is fresh no longer than that entry is (at most
|
|
@@ -1268,8 +1369,9 @@ shrink what the shell bakes or move large regions under a live loader's
|
|
|
1268
1369
|
boundary.
|
|
1269
1370
|
|
|
1270
1371
|
The shell store is always the app-level `createRouter({ cache })` store; the
|
|
1271
|
-
default key is `${host}${pathname}${sortedSearch}:shell
|
|
1272
|
-
multi-tenant shells never collide
|
|
1372
|
+
default key is `${routerId}@${host}${pathname}${sortedSearch}:shell`: host-scoped
|
|
1373
|
+
so multi-tenant shells never collide, and router-scoped so two routers on one
|
|
1374
|
+
store keep their own (see `/host-router`, "Shared cache store").
|
|
1273
1375
|
|
|
1274
1376
|
These options control the in-function shell entry only. They do not emit HTTP
|
|
1275
1377
|
`Cache-Control`. Adding `s-maxage` separately allows a platform CDN to cache the
|