@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
|
@@ -20,11 +20,12 @@ To choose between the in-app layers themselves, start at `/cache-guide`.
|
|
|
20
20
|
| `"use cache"` | one function result | yes | yes | caller, handlers, loaders, rendering |
|
|
21
21
|
| `cache()` | serialized Flight segments | yes | yes | middleware, loaders, HTML rendering |
|
|
22
22
|
| `Prerender()` | build-time Flight segments in the server bundle | yes | yes | middleware, loaders, HTML rendering |
|
|
23
|
+
| `Prerender(..., { onDemand })` | refreshed Flight segments in a writable store | yes | yes | middleware, loaders, HTML rendering |
|
|
23
24
|
| `ppr` | HTML prelude + React postponed state | yes | **yes, before shell commit** | middleware, holes, hydration payload |
|
|
24
25
|
| `createDocumentCacheMiddleware()` | complete response in the app cache store | yes | outer middleware runs; the hit skips its downstream pipeline | nothing downstream |
|
|
25
26
|
| HTTP CDN cache (`s-maxage`) | complete HTTP response outside the app | **no** | **no** | nothing |
|
|
26
27
|
|
|
27
|
-
The first
|
|
28
|
+
The first six rows enter the app. The first five preserve the complete request
|
|
28
29
|
model; a document-cache middleware hit intentionally short-circuits its
|
|
29
30
|
downstream pipeline. The last row is a platform cache: on a hit it serves bytes
|
|
30
31
|
without invoking Rango at all.
|
|
@@ -71,12 +72,31 @@ Worker at the edge, middleware runs there, and PPR shell lookup/resume stays in
|
|
|
71
72
|
that Worker. `CFCacheStore` supplies the app cache families; client assets are
|
|
72
73
|
served separately as assets.
|
|
73
74
|
|
|
75
|
+
An on-demand prerender store (`/prerender` → "On-demand refresh") on Workers
|
|
76
|
+
is `createKVPrerenderStore(env.PRERENDER_KV)` from
|
|
77
|
+
`@rangojs/router/prerender/cloudflare`: refreshes run from `scheduled`,
|
|
78
|
+
`queue` or a webhook route, and every request to an on-demand route pays one
|
|
79
|
+
KV read plus one per tag on the entry.
|
|
80
|
+
|
|
81
|
+
The same `router.prerender()` call warms any other route's caches before
|
|
82
|
+
traffic, when `CFCacheStore` has `kv` (shared by every colo). It fills the
|
|
83
|
+
calling colo and KV; a colo that already holds its own copy keeps it until it
|
|
84
|
+
expires, so a content refresh is `updateTag()` and then the warm: no colo
|
|
85
|
+
serves the old copy, and a colo without one reads the warmed entry
|
|
86
|
+
(`/cloudflare` → "Warm routes on Workers").
|
|
87
|
+
|
|
74
88
|
### Vercel
|
|
75
89
|
|
|
76
90
|
The Vercel preset emits static client assets plus one streaming Node Function.
|
|
77
91
|
HTML, Flight, prerender payloads, and PPR shells are served from that function.
|
|
78
92
|
`VercelCacheStore` uses Runtime Cache inside the function; Runtime Cache is not
|
|
79
|
-
Vercel's CDN/ISR cache.
|
|
93
|
+
Vercel's CDN/ISR cache. Rango's on-demand prerender does not use Vercel ISR
|
|
94
|
+
either: it needs a durable `WritablePrerenderStore` shared by every function
|
|
95
|
+
instance, and v1 ships none for Vercel (the in-memory store is per process),
|
|
96
|
+
so a Vercel app writes a small get/set adapter over its own storage.
|
|
97
|
+
Warming a plain route (`/prerender` → "Warm any route before traffic") needs
|
|
98
|
+
no such store: it writes to `VercelCacheStore`, in the region the call runs
|
|
99
|
+
in.
|
|
80
100
|
|
|
81
101
|
The preset deliberately emits no Vercel `.prerender-config.json`, response
|
|
82
102
|
`chain`, or CDN-stitched resume function. Vercel's open-source Build Output
|
|
@@ -155,15 +175,16 @@ the app store, should own the complete response.
|
|
|
155
175
|
|
|
156
176
|
## Decision guide
|
|
157
177
|
|
|
158
|
-
| Requirement | Choose
|
|
159
|
-
| --------------------------------------------------- |
|
|
160
|
-
| Per-request auth or request shaping | in-function caching; never shared CDN HTML
|
|
161
|
-
| Stable shell with cart/session/live prices | `ppr` with live holes
|
|
162
|
-
| Build-known segments with live loaders | `Prerender()`
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
| One
|
|
178
|
+
| Requirement | Choose |
|
|
179
|
+
| --------------------------------------------------- | ----------------------------------------------------- |
|
|
180
|
+
| Per-request auth or request shaping | in-function caching; never shared CDN HTML |
|
|
181
|
+
| Stable shell with cart/session/live prices | `ppr` with live holes |
|
|
182
|
+
| Build-known segments with live loaders | `Prerender()` |
|
|
183
|
+
| Prerendered page refreshed on a CMS edit, no deploy | `Prerender(..., { onDemand })` + `router.prerender()` |
|
|
184
|
+
| Fully public response, whole-page TTL is acceptable | HTTP `s-maxage` + SWR |
|
|
185
|
+
| Whole response reused inside the app store | `createDocumentCacheMiddleware()` |
|
|
186
|
+
| One query or component is expensive | `"use cache"` |
|
|
187
|
+
| One route subtree is expensive | `cache()` |
|
|
167
188
|
|
|
168
189
|
For a large dynamic app, CDN-stitched PPR would mainly improve shell first-byte
|
|
169
190
|
latency and avoid sending the prelude from the function. It would not remove the
|
|
@@ -175,7 +196,7 @@ provably public and shared.
|
|
|
175
196
|
## Related skills
|
|
176
197
|
|
|
177
198
|
- `/ppr` — shell capture, holes, middleware commit point, invalidation
|
|
178
|
-
- `/prerender` — build-time Flight segments
|
|
199
|
+
- `/prerender` — build-time Flight segments, `Prerender + ppr`, and on-demand refresh
|
|
179
200
|
- `/document-cache` — store-backed complete-response middleware
|
|
180
201
|
- `/vercel` — Vercel Build Output preset and Runtime Cache wiring
|
|
181
202
|
- `/cloudflare` — Worker deployment and bindings
|
|
@@ -175,20 +175,22 @@ header or logs to identify an actual CDN hit.
|
|
|
175
175
|
|
|
176
176
|
## Cache Key Generation
|
|
177
177
|
|
|
178
|
-
The default key is the host, pathname, and sorted
|
|
179
|
-
`_rsc*` params removed, `cache.searchParams` filtering
|
|
180
|
-
suffixes that separate response variants:
|
|
178
|
+
The default key is the serving router's id, the host, pathname, and sorted
|
|
179
|
+
search string (internal `_rsc*` params removed, `cache.searchParams` filtering
|
|
180
|
+
applied), followed by suffixes that separate response variants:
|
|
181
181
|
|
|
182
|
-
- HTML document requests: `{host}{pathname}[?search]:html`
|
|
182
|
+
- HTML document requests: `{routerId}@{host}{pathname}[?search]:html`
|
|
183
183
|
- RSC requests (client navigations and full-document Flight fetches):
|
|
184
|
-
`{host}{pathname}[?search]:rsc`
|
|
184
|
+
`{routerId}@{host}{pathname}[?search]:rsc`
|
|
185
185
|
- Client navigations additionally add a hash of the segments the client already
|
|
186
186
|
has (`:{segmentHash}`), so navigations from pages with different layouts get
|
|
187
187
|
different cached payloads, and a `:fragments` marker when the client accepts
|
|
188
188
|
fragment envelopes.
|
|
189
189
|
|
|
190
|
-
A custom `keyGenerator(url)` replaces
|
|
191
|
-
|
|
190
|
+
A custom `keyGenerator(url)` replaces the whole
|
|
191
|
+
`{routerId}@{host}{pathname}[?search]` part, router id included, so a generator
|
|
192
|
+
used by routers that share a store must keep them apart itself; the variant
|
|
193
|
+
suffixes are still appended.
|
|
192
194
|
|
|
193
195
|
## What Gets Cached
|
|
194
196
|
|
|
@@ -320,13 +322,13 @@ everything else.
|
|
|
320
322
|
|
|
321
323
|
## Document Cache vs Segment Cache
|
|
322
324
|
|
|
323
|
-
| Feature | Document Cache
|
|
324
|
-
| ------------ |
|
|
325
|
-
| Granularity | Full response
|
|
326
|
-
| Opt-in | `Cache-Control` `s-maxage`
|
|
327
|
-
| Loaders | Frozen with the response
|
|
328
|
-
| Use case | Static pages
|
|
329
|
-
| Key includes | host + URL + variant (+ segment hash) | host + pathname + params + search |
|
|
325
|
+
| Feature | Document Cache | Segment Cache (`cache()`) |
|
|
326
|
+
| ------------ | ---------------------------------------------- | ------------------------------------------ |
|
|
327
|
+
| Granularity | Full response | Individual segments |
|
|
328
|
+
| Opt-in | `Cache-Control` `s-maxage` | `cache({ ttl, swr })` |
|
|
329
|
+
| Loaders | Frozen with the response | Always run fresh |
|
|
330
|
+
| Use case | Static pages | Dynamic compositions |
|
|
331
|
+
| Key includes | router + host + URL + variant (+ segment hash) | router + host + pathname + params + search |
|
|
330
332
|
|
|
331
333
|
Use document cache for mostly-static pages. Use segment cache when different parts of a page have different cache requirements.
|
|
332
334
|
|
package/skills/hooks/SKILL.md
CHANGED
|
@@ -68,6 +68,9 @@ the one for your case.
|
|
|
68
68
|
- [`./state.md`](./state.md) — `useLocationState` (persistent + flash state,
|
|
69
69
|
`.read()`/`.read(location)`/`.write()`/`.delete()`, typed `state` on `router.push()` /
|
|
70
70
|
`router.replace()`, serializability errors that name the failing field),
|
|
71
|
+
state from another deploy (dropped by the router, no version to manage), the
|
|
72
|
+
`createLocationState` options (`flash`; `clearOnReload` for state that must
|
|
73
|
+
not come back after a refresh, such as a "load more" list),
|
|
71
74
|
`invalidateClientCache()`.
|
|
72
75
|
- [`./outlets.md`](./outlets.md) — `Outlet`, `ParallelOutlet`, `useOutlet`.
|
|
73
76
|
- [`./urls.md`](./urls.md) — `useParams`, `usePathname`, `useSearchParams`,
|
|
@@ -94,3 +97,25 @@ the one for your case.
|
|
|
94
97
|
| `useAction()` | Server action state | state, actionId, payload, error, result |
|
|
95
98
|
| `useLocationState()` | History state (persists or flash) | T \| undefined |
|
|
96
99
|
| `invalidateClientCache()` | Force client caches to miss (function, not a hook; root entry) | `void` |
|
|
100
|
+
|
|
101
|
+
## Hydration
|
|
102
|
+
|
|
103
|
+
On a document load every router hook renders what the server rendered until
|
|
104
|
+
the document has finished streaming, wherever the component sits. A component
|
|
105
|
+
in a `<Suspense>` or `loading()` boundary that streams in after the rest of
|
|
106
|
+
the page hydrates with the document's pathname, params, search params and
|
|
107
|
+
segments, an idle `useNavigation()` and `useAction()`, a `useLinkStatus()` that
|
|
108
|
+
is not pending, the handle data the HTML was rendered from, and `undefined`
|
|
109
|
+
location state, even when a navigation or an action has started meanwhile. It
|
|
110
|
+
cannot mismatch its server HTML.
|
|
111
|
+
|
|
112
|
+
- Navigation state, params and action state: the component shows the live
|
|
113
|
+
value right after it hydrates.
|
|
114
|
+
- Handle data a loader pushed late, and the entry's location state: every
|
|
115
|
+
reader gets them once the whole document has streamed and hydrated, in one
|
|
116
|
+
transition. A reader re-renders only when its selection changes.
|
|
117
|
+
- A navigation that commits before then mounts its page with the live values
|
|
118
|
+
from its first render. A boundary of the document that persists across that
|
|
119
|
+
navigation and has not hydrated yet then hydrates against the live values;
|
|
120
|
+
where they differ from its HTML, React reports the mismatch and renders that
|
|
121
|
+
boundary on the client.
|
|
@@ -39,6 +39,11 @@ the capture, so its pushes always apply client-side; with `ssr: false` they
|
|
|
39
39
|
bake into the shell. See `/loader` → "Writing Handles from Loaders". `useHandle`
|
|
40
40
|
re-renders on every handle update, late loader pushes included.
|
|
41
41
|
|
|
42
|
+
On a document load `useHandle` hydrates with the handle data its HTML was
|
|
43
|
+
rendered from, wherever the reader sits; late loader pushes reach every reader
|
|
44
|
+
once the document has finished streaming and every boundary has hydrated (see
|
|
45
|
+
`SKILL.md`, "Hydration").
|
|
46
|
+
|
|
42
47
|
Handles can be passed as props from server to client components:
|
|
43
48
|
|
|
44
49
|
```tsx
|
|
@@ -27,6 +27,34 @@ function IsLoading() {
|
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
#### Pending UI, Back/Forward included
|
|
31
|
+
|
|
32
|
+
A link click, `router.push()` and a Back/Forward that has to fetch its entry
|
|
33
|
+
report a pending navigation the same way. While the page being left is still
|
|
34
|
+
on screen:
|
|
35
|
+
|
|
36
|
+
| What you want to show | Read |
|
|
37
|
+
| ---------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
38
|
+
| a request is out, nothing has arrived | `state === "loading"`; `pendingUrl` is the URL being fetched |
|
|
39
|
+
| the page has not fully arrived yet | `isStreaming`: true from the start of the request until the stream ends |
|
|
40
|
+
| which page is on screen while the request runs | `location`, or `usePathname()` / `useSearchParams()` (see [`./urls.md`](./urls.md)) |
|
|
41
|
+
|
|
42
|
+
`state` and `pendingUrl` cover the network wait. Once the response has
|
|
43
|
+
arrived a loader may still be streaming, and React keeps the page being left
|
|
44
|
+
on screen where a navigation holds its content (a same-route change, a
|
|
45
|
+
`transition()` route); `isStreaming` covers that part too, so use it for a
|
|
46
|
+
progress indicator that should last until the new page is complete.
|
|
47
|
+
|
|
48
|
+
On Back/Forward the address bar changes at once, before any of this: during
|
|
49
|
+
the wait `window.location` is the entry being fetched, the same URL as
|
|
50
|
+
`pendingUrl`, and `location` is the page on screen. A Back/Forward served
|
|
51
|
+
from the client's history cache restores its page in one commit and reports
|
|
52
|
+
no pending state.
|
|
53
|
+
|
|
54
|
+
`location` moves when the response arrives. Where React then holds the page
|
|
55
|
+
being left, it is ahead of the screen for that last part of the wait, and so
|
|
56
|
+
is a URL hook in a component that first mounts then (issue #1046).
|
|
57
|
+
|
|
30
58
|
### useRouter()
|
|
31
59
|
|
|
32
60
|
Access stable router actions. The returned object never changes identity, so
|
|
@@ -138,6 +166,16 @@ function Breadcrumbs() {
|
|
|
138
166
|
const isShopRoute = useSegments((s) => s.path[0] === "shop");
|
|
139
167
|
```
|
|
140
168
|
|
|
169
|
+
The selector is applied when the router store changes (a navigation or new
|
|
170
|
+
handle data), as with `useNavigation` and `useAction`. A new selector function
|
|
171
|
+
on a re-render takes effect at the next change. To derive from a prop, select
|
|
172
|
+
the state and derive in render:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
const segments = useSegments();
|
|
176
|
+
const part = segments.path[depth];
|
|
177
|
+
```
|
|
178
|
+
|
|
141
179
|
### useLinkStatus()
|
|
142
180
|
|
|
143
181
|
Track pending state inside a Link component. `pending` is `true` while a
|
package/skills/hooks/state.md
CHANGED
|
@@ -84,6 +84,59 @@ definition:
|
|
|
84
84
|
const state = useLocationState<{ from?: string }>(); // { from?: string } | undefined
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### When a reader sees an entry's state
|
|
88
|
+
|
|
89
|
+
A reader sees a history entry's location state together with that entry's
|
|
90
|
+
page, never before it. The router keeps the state of the entry on screen next
|
|
91
|
+
to the page it renders and changes both in one React commit. While a
|
|
92
|
+
navigation is pending (its loader still streaming, a `transition()` holding
|
|
93
|
+
the page) what is on screen keeps the state of the entry being left. That
|
|
94
|
+
holds for every reader in it, including one that mounts during the wait: it
|
|
95
|
+
reads the entry on screen, not the one `history.state` already holds.
|
|
96
|
+
Back/forward and state the server sets on a navigation
|
|
97
|
+
(`ctx.setLocationState()`, `redirect(url, { state })`) follow the same rule.
|
|
98
|
+
|
|
99
|
+
| What changes the state | A reader gets it |
|
|
100
|
+
| ------------------------------------------------------------ | ---------------------------------------------------------------- |
|
|
101
|
+
| `<Link state>`, `router.push()` / `router.replace()` | in the commit that shows the destination |
|
|
102
|
+
| back/forward | in the commit that restores the entry (cached or refetched) |
|
|
103
|
+
| the server, on a navigation (`setLocationState`, a redirect) | in the commit that shows the destination |
|
|
104
|
+
| a navigation the server answers with nothing to re-render | with the new location: the page on screen is the entry's |
|
|
105
|
+
| `router.push(url, { revalidate: false })` (no server fetch) | with the new location: the page does not change |
|
|
106
|
+
| a server action's `setLocationState` (no navigation) | when the action's response arrives, added to what readers show |
|
|
107
|
+
| `Def.write()` / `Def.delete()` | when the entry is next restored: back/forward to it, or a reload |
|
|
108
|
+
|
|
109
|
+
Three consequences:
|
|
110
|
+
|
|
111
|
+
- A value keeps its identity until the entry's state changes it. A navigation
|
|
112
|
+
that carries no state re-renders no reader. One that does re-renders each
|
|
113
|
+
reader once, in the commit that already renders the page; a reader of a
|
|
114
|
+
slot the navigation carried forward unchanged (the value it got from the
|
|
115
|
+
hook, passed back) or of a slot a server action did not touch gets the same
|
|
116
|
+
object again, so its effects and memoized children do not run. Any other
|
|
117
|
+
object is a new value, equal content included: a link clicked twice
|
|
118
|
+
announces its state twice. A value is always a copy of what was passed.
|
|
119
|
+
- `Def.write()` and `Def.delete()` change the history entry and nothing a
|
|
120
|
+
reader shows, whether it is mounted or mounts later. `Def.read()` returns
|
|
121
|
+
the written value at once.
|
|
122
|
+
- Outside the router's provider (a second React root, a component rendered
|
|
123
|
+
bare in a test) a reader has no entry and returns `undefined`. Test with
|
|
124
|
+
`renderRoute` (see `/testing`).
|
|
125
|
+
|
|
126
|
+
A reader that mounts with the destination reads the destination's state on
|
|
127
|
+
its first render. Inside an optimistically rendered `clientUrls()` destination
|
|
128
|
+
that is the state the navigation carries, before the server has answered (see
|
|
129
|
+
`/client-urls`, "Pitfalls").
|
|
130
|
+
|
|
131
|
+
The URL hooks follow the same rule for a component that is mounted across the
|
|
132
|
+
navigation: on back/forward too, `usePathname()` and `useSearchParams()`
|
|
133
|
+
change with the page, not at the `popstate` event (see
|
|
134
|
+
[`./urls.md`](./urls.md), "When the URL hooks change"). One difference
|
|
135
|
+
remains (issue #1046): a component that first mounts in the page being left,
|
|
136
|
+
after the response arrived and while React still holds the destination, reads
|
|
137
|
+
its own entry's location state from `useLocationState` and the destination's
|
|
138
|
+
URL and params from `usePathname()`, `useSearchParams()` and `useParams()`.
|
|
139
|
+
|
|
87
140
|
### State on router.push() / router.replace()
|
|
88
141
|
|
|
89
142
|
The same `state` option exists on `router.push()` / `router.replace()` (see
|
|
@@ -198,12 +251,14 @@ function FlashBanner() {
|
|
|
198
251
|
```
|
|
199
252
|
|
|
200
253
|
Flash behavior is determined by the definition (`{ flash: true }`), not by which
|
|
201
|
-
hook reads it. `useLocationState` reads the value during render (on
|
|
202
|
-
|
|
203
|
-
SSR and hydration agree), then clears it from `history.state` via
|
|
204
|
-
`replaceState` in a `useEffect`.
|
|
205
|
-
definition
|
|
206
|
-
|
|
254
|
+
hook reads it. `useLocationState` reads the value during render (on a
|
|
255
|
+
document load it returns `undefined` until the document has finished streaming
|
|
256
|
+
and hydrating, so SSR and hydration agree), then clears it from `history.state` via
|
|
257
|
+
`replaceState` in a `useEffect`. Every component reading the same flash
|
|
258
|
+
definition sees the value, whenever it mounts, until the next navigation or
|
|
259
|
+
back/forward commits its entry's state: an entry that carries no flash shows
|
|
260
|
+
none. A server action's state does not end it. Pressing back/forward will not
|
|
261
|
+
re-show the flash since it was cleared.
|
|
207
262
|
|
|
208
263
|
Set flash state from the server via `redirect()` with state (from a handler,
|
|
209
264
|
middleware, or server action). Import the definition from the shared module so
|
|
@@ -221,7 +276,9 @@ import { FlashMessage } from "../location-states";
|
|
|
221
276
|
};
|
|
222
277
|
```
|
|
223
278
|
|
|
224
|
-
Or via `ctx.setLocationState()`
|
|
279
|
+
Or via `ctx.setLocationState()` from a handler or middleware. It is delivered
|
|
280
|
+
with a client navigation's or an action's response; a document response
|
|
281
|
+
carries no location state (there is no history entry to write it to yet):
|
|
225
282
|
|
|
226
283
|
```tsx
|
|
227
284
|
(ctx) => {
|
|
@@ -230,6 +287,149 @@ Or via `ctx.setLocationState()` on any response (handlers and middleware):
|
|
|
230
287
|
};
|
|
231
288
|
```
|
|
232
289
|
|
|
290
|
+
### State from another deploy
|
|
291
|
+
|
|
292
|
+
History entries survive reloads and back/forward, so a tab left open across a
|
|
293
|
+
release still holds the state the older build wrote. The router drops it for
|
|
294
|
+
you: every entry records the app version its location state was written under,
|
|
295
|
+
and a read under any other version returns `undefined`, for typed slots and
|
|
296
|
+
for plain state alike. You do not manage a version and there is none to read;
|
|
297
|
+
the app version is the one the router already uses to reload a client that is
|
|
298
|
+
behind the server. It is computed from the app's built code (its server code
|
|
299
|
+
and the client assets), so it changes with a build that changes the app and
|
|
300
|
+
stays the same across a rebuild or redeploy of unchanged code.
|
|
301
|
+
|
|
302
|
+
| An entry's location state, read by | Result |
|
|
303
|
+
| ---------------------------------------------------------------- | ----------- |
|
|
304
|
+
| the version that wrote it: navigation, back/forward, refresh | the value |
|
|
305
|
+
| the same code rebuilt or redeployed | the value |
|
|
306
|
+
| a changed build, after a refresh or a restored tab | `undefined` |
|
|
307
|
+
| a changed build, on back/forward to an entry the older one wrote | `undefined` |
|
|
308
|
+
| any build, when the entry predates this behavior | `undefined` |
|
|
309
|
+
|
|
310
|
+
So location state is for the current session of the current version of the
|
|
311
|
+
app: a filter carried on a link, a flash message, a scroll target. State that
|
|
312
|
+
has to outlive a deploy that changes the app belongs in the URL, a cookie or
|
|
313
|
+
storage. In development a
|
|
314
|
+
server-module edit (HMR) changes the version too; the running page keeps its
|
|
315
|
+
state, and the next document load drops it.
|
|
316
|
+
|
|
317
|
+
### createLocationState options
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
createLocationState<TState>({
|
|
321
|
+
flash?: boolean, // read once, cleared after paint (above)
|
|
322
|
+
clearOnReload?: boolean, // drop the state on a document load (below)
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The value is always stored as-is. `clearOnReload` changes the slot's KEY in
|
|
327
|
+
`history.state` instead, by a suffix on the key the Vite plugin injects
|
|
328
|
+
(`<key>` below, `__rsc_ls_<file>#<ExportName>`):
|
|
329
|
+
|
|
330
|
+
| Options | Key in `history.state` | Reads `undefined` when |
|
|
331
|
+
| --------------- | ---------------------- | ----------------------------------------------- |
|
|
332
|
+
| none, `flash` | `<key>` | the slot is empty (`flash`: once read) |
|
|
333
|
+
| `clearOnReload` | `<key>~r` | the entry's document was loaded since the write |
|
|
334
|
+
|
|
335
|
+
`Def.__rsc_ls_key` is that key, suffix included. `~` cannot appear in an export
|
|
336
|
+
name, so no other definition can end up with a suffixed key.
|
|
337
|
+
|
|
338
|
+
`flash` with `clearOnReload` is rejected: `createLocationState` throws in
|
|
339
|
+
development.
|
|
340
|
+
|
|
341
|
+
### clearOnReload: state the server did not render with
|
|
342
|
+
|
|
343
|
+
Location state lives in the browser, so the server renders a document without
|
|
344
|
+
it. `useLocationState` therefore hydrates as `undefined` and applies the stored
|
|
345
|
+
value once the document has finished streaming and every boundary has
|
|
346
|
+
hydrated. For most state that is invisible. For state that decides
|
|
347
|
+
how much content is on the page, it is a layout shift on every refresh.
|
|
348
|
+
|
|
349
|
+
The case this option exists for is a "load more" list. `?page=6` loads that
|
|
350
|
+
page's 50 products through a loader, and the 250 already on screen ride along
|
|
351
|
+
as location state on the link, so the next page shows them at once and
|
|
352
|
+
streams the new ones:
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
// location-states.ts
|
|
356
|
+
import { createLocationState } from "@rangojs/router";
|
|
357
|
+
|
|
358
|
+
export const CarriedProducts = createLocationState<Product[]>({
|
|
359
|
+
clearOnReload: true,
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```tsx
|
|
364
|
+
"use client";
|
|
365
|
+
import { Link, useLoader, useLocationState } from "@rangojs/router/client";
|
|
366
|
+
import { ProductsLoader } from "./loaders"; // the page named by ?page
|
|
367
|
+
import { CarriedProducts } from "./location-states";
|
|
368
|
+
|
|
369
|
+
export function ProductList() {
|
|
370
|
+
const { data } = useLoader(ProductsLoader);
|
|
371
|
+
const carried = useLocationState(CarriedProducts) ?? [];
|
|
372
|
+
const products = [...carried, ...data.products];
|
|
373
|
+
|
|
374
|
+
return (
|
|
375
|
+
<>
|
|
376
|
+
<ul>
|
|
377
|
+
{products.map((product) => (
|
|
378
|
+
<li key={product.id}>{product.name}</li>
|
|
379
|
+
))}
|
|
380
|
+
</ul>
|
|
381
|
+
<Link
|
|
382
|
+
to={`/products?page=${data.page + 1}`}
|
|
383
|
+
state={[CarriedProducts(products)]}
|
|
384
|
+
scroll={false}
|
|
385
|
+
>
|
|
386
|
+
Load more
|
|
387
|
+
</Link>
|
|
388
|
+
</>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The list is a plain concatenation. After "Load more" the next page's loader
|
|
394
|
+
streams while the current page stays on screen, and `carried` stays the
|
|
395
|
+
current entry's until that page lands: the carried products and the loader's
|
|
396
|
+
page change in one commit, so no product is ever listed twice.
|
|
397
|
+
|
|
398
|
+
Without the option, a refresh of `?page=6` renders 50 products on the server
|
|
399
|
+
and then inserts the stored 250 above them. With it, the refreshed page stays
|
|
400
|
+
as the server rendered it:
|
|
401
|
+
|
|
402
|
+
| What happens to the entry | A `clearOnReload` slot |
|
|
403
|
+
| ------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
|
404
|
+
| The reader mounts during a client navigation (`<Link>`, `router.push`) | applied, as without the option |
|
|
405
|
+
| back/forward inside the running app (popstate), a server action's state | applied, as without the option |
|
|
406
|
+
| A document load: refresh, or a back/forward that loads the document | not applied, and removed from `history.state` |
|
|
407
|
+
| Later on that page: a reader mounts, or a navigation returns to the entry | still `undefined`; the next write (the next "Load more") stores anew |
|
|
408
|
+
| The page is restored from the browser's back/forward cache | untouched: nothing is loaded or hydrated |
|
|
409
|
+
|
|
410
|
+
- The rule is any document load of the entry, not only the Reload button:
|
|
411
|
+
restoring a closed tab or duplicating a tab loads the document too.
|
|
412
|
+
- Only the entry being loaded is cleared. Going back from it to an earlier
|
|
413
|
+
entry inside the running app is a client navigation and applies that
|
|
414
|
+
entry's state.
|
|
415
|
+
- The router removes every `~r` key when the client starts, before hydration.
|
|
416
|
+
It goes by the key alone, so it does not matter whether a reader is mounted,
|
|
417
|
+
where it sits (a `<Suspense>` boundary that hydrates late included), or
|
|
418
|
+
whether the definition's module is loaded at all. Other slots on the entry
|
|
419
|
+
are left alone.
|
|
420
|
+
- State the server sets is never dropped by this: a document response carries
|
|
421
|
+
no location state, and `ctx.setLocationState()` / `redirect(url, { state })`
|
|
422
|
+
reach `history.state` only through navigations and actions in the running
|
|
423
|
+
app, after start-up.
|
|
424
|
+
- `.write()` stores under the same key, so it does not survive a refresh
|
|
425
|
+
either.
|
|
426
|
+
- Adding the option to an existing definition moves it to the `~r` key: state
|
|
427
|
+
stored before, under the plain key, is no longer read (and is not removed).
|
|
428
|
+
Removing the option moves it back to the plain key; what was stored under
|
|
429
|
+
`~r` is removed at the next document load.
|
|
430
|
+
- It cannot be combined with `flash`: flash state is removed at its first
|
|
431
|
+
read, so the pair could only drop a message nobody has seen yet.
|
|
432
|
+
|
|
233
433
|
### .read() (non-hook access)
|
|
234
434
|
|
|
235
435
|
Read current location state outside React components (client-side only):
|
|
@@ -264,9 +464,11 @@ Static counterparts to `.read()`. Both mutate the current history entry's
|
|
|
264
464
|
bookkeeping, other location state slots). Both are client-only; they throw
|
|
265
465
|
when called on the server.
|
|
266
466
|
|
|
267
|
-
Neither
|
|
268
|
-
|
|
269
|
-
|
|
467
|
+
Neither changes what a `useLocationState` reader shows, mounted or mounting
|
|
468
|
+
later: readers get the entry's state as it was committed (see "When a reader
|
|
469
|
+
sees an entry's state"). The written value is there for `.read()` at once,
|
|
470
|
+
and for readers when the entry is next restored (back/forward to it, or a
|
|
471
|
+
reload).
|
|
270
472
|
|
|
271
473
|
```tsx
|
|
272
474
|
"use client";
|
|
@@ -275,7 +477,7 @@ import { ProductState } from "./state";
|
|
|
275
477
|
// Persisted across hard refresh and back/forward of this entry.
|
|
276
478
|
ProductState.write({ name: "Widget", price: 9.99 });
|
|
277
479
|
|
|
278
|
-
// Read later
|
|
480
|
+
// Read later.
|
|
279
481
|
const current = ProductState.read();
|
|
280
482
|
|
|
281
483
|
// Manually clear the slot. Idempotent if it isn't set.
|
package/skills/hooks/urls.md
CHANGED
|
@@ -47,7 +47,7 @@ function CurrentPage() {
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Returns the pathname string without search params or hash.
|
|
50
|
+
Returns the pathname string without search params or hash. It is the pathname of the page on screen: it changes when a navigation's destination commits, Back/Forward included (see [When the URL hooks change](#when-the-url-hooks-change)).
|
|
51
51
|
|
|
52
52
|
### useSearchParams()
|
|
53
53
|
|
|
@@ -109,6 +109,45 @@ Options: `replace` (default false — push), `scroll` (default true),
|
|
|
109
109
|
`revalidate` (default true; `false` skips the server fetch — legal because
|
|
110
110
|
the setter never changes the pathname).
|
|
111
111
|
|
|
112
|
+
### When the URL hooks change
|
|
113
|
+
|
|
114
|
+
`usePathname()`, `useSearchParams()` and `useParams()` describe the page on
|
|
115
|
+
screen. They change in the commit that shows a navigation's destination, not
|
|
116
|
+
when the navigation starts. That holds for Back/Forward as it does for a link
|
|
117
|
+
click or `router.push()`.
|
|
118
|
+
|
|
119
|
+
| Navigation | The hooks change |
|
|
120
|
+
| ------------------------------------------------- | -------------------------------------------------------------------------- |
|
|
121
|
+
| `<Link>`, `router.push()` / `router.replace()` | when the destination commits |
|
|
122
|
+
| Back/Forward, entry in the client's history cache | with the restored page, in one commit |
|
|
123
|
+
| Back/Forward, entry fetched again | when the fetched page commits; until then the page being left is on screen |
|
|
124
|
+
| a Back/Forward whose fetch fails | with the error boundary: they report the entry the browser is on |
|
|
125
|
+
| `router.push(url, { revalidate: false })` | at once: nothing is fetched and the page stays |
|
|
126
|
+
| inside an optimistic `clientUrls()` destination | at the click, for that branch only (see `/client-urls`) |
|
|
127
|
+
|
|
128
|
+
**The hooks can disagree with the address bar.** On Back/Forward the browser
|
|
129
|
+
changes the URL and `history.state` before it tells the router. When the
|
|
130
|
+
entry has to be fetched (it left the client's history cache, or the cache was
|
|
131
|
+
cleared), the page being left stays on screen for the wait and the hooks keep
|
|
132
|
+
reporting its URL, while `window.location` is already the destination's. That
|
|
133
|
+
is deliberate: an active-link highlight, a breadcrumb or a filter panel
|
|
134
|
+
derived from the hooks stays in step with the content next to it. For
|
|
135
|
+
anything you render, read the hooks, not `window.location`.
|
|
136
|
+
|
|
137
|
+
For pending UI during that wait read `useNavigation()` (see
|
|
138
|
+
[`./navigation.md`](./navigation.md)): `state === "loading"` and `pendingUrl`
|
|
139
|
+
(the entry being fetched) while the request is out, `isStreaming` for the
|
|
140
|
+
whole wait. A Back/Forward served from the history cache has no wait.
|
|
141
|
+
|
|
142
|
+
`setSearchParams((prev) => ...)` builds on the same location: called during a
|
|
143
|
+
pending Back/Forward, `prev` is the search of the page on screen.
|
|
144
|
+
|
|
145
|
+
One gap (issue #1046): a component that first mounts in the page being left
|
|
146
|
+
after the navigation's response has arrived, while React still holds the
|
|
147
|
+
destination behind a loader or a `transition()`, reads the destination's URL
|
|
148
|
+
and params from these hooks. Components that were already mounted are not
|
|
149
|
+
affected. It applies to a push and to Back/Forward alike.
|
|
150
|
+
|
|
112
151
|
### useHref()
|
|
113
152
|
|
|
114
153
|
Mount-aware href for client components inside `include()` scopes:
|
|
@@ -370,3 +370,34 @@ apps; each app owns its own document.
|
|
|
370
370
|
|
|
371
371
|
**Within-app** navigation is unchanged — a normal soft SPA update (the document
|
|
372
372
|
stays mounted). Only crossing an app boundary triggers the reload.
|
|
373
|
+
|
|
374
|
+
## Deploys and the cache
|
|
375
|
+
|
|
376
|
+
Each `createRouter()` app has its own cache version, computed by `vite build` from that app's built code, so deploying a change to one app does not clear the cache of the others. Lazily mounting sub-apps (`.lazy(() => import("./apps/admin"))`) is what keeps them independent: an app's version covers everything the modules that statically import its router can run, and a lazy mount is where that stops. Routers that one module imports statically share that module's code, so a change to either moves both versions.
|
|
377
|
+
|
|
378
|
+
| Deploy | Cached data | Stored HTML | Open tabs |
|
|
379
|
+
| ------------------------------------- | ------------------- | --------------------- | ----------------------- |
|
|
380
|
+
| Rebuild, no code change | kept | kept | untouched |
|
|
381
|
+
| Server code of app A changes | cleared for A only | cleared for A only | A's tabs reload |
|
|
382
|
+
| Client code of any app changes | kept for every app | cleared for every app | every app's tabs reload |
|
|
383
|
+
| Server code shared by A and B changes | cleared for A and B | cleared for A and B | A's and B's tabs reload |
|
|
384
|
+
|
|
385
|
+
The host entry (the file that creates the host router and its middleware) is not part of a lazily mounted sub-app's version. If host middleware changes what an app renders from inside a cache boundary, change that app's `version` or invalidate its tags. See `/cache-guide`.
|
|
386
|
+
|
|
387
|
+
## Shared cache store
|
|
388
|
+
|
|
389
|
+
Sub-apps may share one cache store (one KV namespace, one `MemorySegmentCacheStore`). Every entry that is a router's output for a host and path is keyed by that router as well; its key starts with the router's id (`{routerId}@{host}{path}...`):
|
|
390
|
+
|
|
391
|
+
- PPR shells (document, navigation and build-time);
|
|
392
|
+
- route `cache()` records, document-cache responses and response-route entries;
|
|
393
|
+
- a loader's own `cache()` entry, and a `"use cache"` entry of a function called with a handler or loader `ctx`. A `ctx` is one router's: its context variables, its `env`, its `reverse()` map.
|
|
394
|
+
|
|
395
|
+
So two sub-apps never read each other's entry, including where they serve the same host and path:
|
|
396
|
+
|
|
397
|
+
- with the `hostOverride` cookie, which picks the app and forwards the request unchanged, so every app answers under the one preview origin;
|
|
398
|
+
- after a `fallback()` that hands a request to another app's handler;
|
|
399
|
+
- after a `router.prerender()` warm that resolved another app's host as its origin.
|
|
400
|
+
|
|
401
|
+
There is nothing to configure and no namespace to manage. What it relies on is that each router has a stable id: the one the Vite plugin injects for a `createRouter({ ... })` call written with an object literal, or the `id` you pass (`createRouter({ id: "admin", ... })`). A router the plugin cannot reach, for example `createRouter(options)` with an options variable, falls back to a counter (`router_0`, `router_1`, ...) that follows module evaluation order. One such router is fine. With more than one, the build already warns (`N routers use auto-generated IDs`), because routes and cache versions are matched by the same id; the fix is the same here: pass the object literal inline or set an `id`.
|
|
402
|
+
|
|
403
|
+
Not keyed by router, on purpose: a `"use cache"` entry of a function called with plain arguments or the bare `Request` (the function and its arguments name no router, so both apps share the entry), tag markers (an invalidation reaches every app), and any key you build yourself (`cache({ key })` on a route or a loader, the document cache's `keyGenerator`): keep those apart yourself where two apps must not share them.
|
|
@@ -238,6 +238,12 @@ always activates. `when` is not re-evaluated during action revalidation, so
|
|
|
238
238
|
an open modal stays open after an action. A selector that throws does not
|
|
239
239
|
intercept: the full page renders and the error is logged with the route name.
|
|
240
240
|
|
|
241
|
+
The server learns `from` from the `X-RSC-Router-Client-Path` header, which the
|
|
242
|
+
rango client sends on every navigation. A partial request without it falls
|
|
243
|
+
back to `Referer`, and only when that `Referer` is on the request's own origin
|
|
244
|
+
(scheme, host and port). A request with no usable navigation context is not
|
|
245
|
+
intercepted: it gets the full page.
|
|
246
|
+
|
|
241
247
|
A prefetch of a route an intercept targets is stored per source page, whether
|
|
242
248
|
or not the intercept applies from that page (`rsc-rendering.ts`
|
|
243
249
|
`x-rsc-prefetch-scope: source`, set from `MatchResult.interceptTargeted`). A
|