@rangojs/router 0.27.0 → 0.29.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 +1 -1
- package/dist/types/__internal.d.ts +7 -1
- package/dist/types/browser/action-fence.d.ts +2 -1
- package/dist/types/browser/event-controller.d.ts +8 -2
- package/dist/types/browser/history-state.d.ts +3 -9
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/partial-update.d.ts +11 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/react/use-action.d.ts +7 -2
- package/dist/types/browser/react/use-handle.d.ts +19 -16
- package/dist/types/browser/react/use-link-status.d.ts +5 -2
- package/dist/types/browser/segment-reconciler.d.ts +32 -4
- package/dist/types/browser/types.d.ts +24 -5
- package/dist/types/browser/wire-protocol.d.ts +26 -0
- package/dist/types/cache/cache-key-utils.d.ts +11 -5
- package/dist/types/cache/cache-scope.d.ts +34 -22
- package/dist/types/cache/cf/cf-cache-constants.d.ts +2 -2
- package/dist/types/cache/cf/cf-cache-store.d.ts +47 -65
- package/dist/types/cache/cf/cf-kv-utils.d.ts +1 -1
- package/dist/types/cache/handle-snapshot.d.ts +9 -4
- package/dist/types/cache/segment-codec.d.ts +14 -0
- package/dist/types/cache/shell-snapshot.d.ts +1 -1
- package/dist/types/cache/taint.d.ts +5 -3
- package/dist/types/cache/types.d.ts +7 -3
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +8 -6
- package/dist/types/prefetch-markers.d.ts +5 -14
- package/dist/types/prerender/store.d.ts +31 -0
- package/dist/types/prerender/warm-request.d.ts +3 -3
- package/dist/types/prerender.d.ts +2 -2
- package/dist/types/route-name.d.ts +5 -0
- package/dist/types/router/error-handling.d.ts +11 -5
- package/dist/types/router/instrument.d.ts +4 -3
- package/dist/types/router/intercept-resolution.d.ts +25 -9
- package/dist/types/router/match-context.d.ts +6 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +13 -22
- package/dist/types/router/match-middleware/cache-lookup.d.ts +34 -86
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- package/dist/types/router/navigation-snapshot.d.ts +7 -0
- package/dist/types/router/prerender-match.d.ts +21 -6
- package/dist/types/router/preview-match.d.ts +2 -7
- package/dist/types/router/request-classification.d.ts +5 -0
- package/dist/types/router/router-context.d.ts +7 -46
- package/dist/types/router/router-interfaces.d.ts +8 -14
- package/dist/types/router/segment-resolution/helpers.d.ts +19 -3
- package/dist/types/router/segment-resolution/loader-cache.d.ts +3 -3
- package/dist/types/router/segment-resolution/loader-mask.d.ts +4 -4
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +20 -18
- package/dist/types/router/segment-resolution/resolution-pass.d.ts +95 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +2 -0
- package/dist/types/router/segment-resolution.d.ts +1 -2
- package/dist/types/router/segment-wrappers.d.ts +11 -48
- package/dist/types/router/telemetry.d.ts +2 -0
- package/dist/types/router/types.d.ts +11 -1
- package/dist/types/rsc/full-payload.d.ts +2 -2
- package/dist/types/rsc/handler-context.d.ts +6 -6
- package/dist/types/rsc/json-route-result.d.ts +5 -9
- package/dist/types/rsc/request-stages.d.ts +203 -0
- package/dist/types/rsc/response-cache-serve.d.ts +7 -12
- package/dist/types/rsc/response-route-handler.d.ts +2 -2
- package/dist/types/rsc/routine-plan.d.ts +1 -1
- package/dist/types/rsc/server-action.d.ts +2 -2
- package/dist/types/rsc/shell-capture-constants.d.ts +1 -1
- package/dist/types/rsc/shell-capture.d.ts +132 -15
- package/dist/types/rsc/shell-serve.d.ts +37 -12
- package/dist/types/server/context.d.ts +9 -8
- package/dist/types/server/handle-store.d.ts +16 -0
- package/dist/types/server/render-pass.d.ts +72 -0
- package/dist/types/server/request-context.d.ts +53 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/render-route.d.ts +43 -8
- package/dist/types/testing/serve-shell-request.d.ts +8 -1
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +30 -3
- package/dist/types/types/handler-context.d.ts +9 -1
- package/dist/types/types/segments.d.ts +1 -1
- package/dist/types/vite/discovery/build-discovery-phase.d.ts +40 -0
- package/dist/types/vite/discovery/build-env.d.ts +22 -0
- package/dist/types/vite/discovery/bundle-scan-phase.d.ts +12 -0
- package/dist/types/vite/discovery/dev-prerender-cache.d.ts +3 -2
- package/dist/types/vite/discovery/dev-server-phase.d.ts +26 -0
- package/dist/types/vite/discovery/discovery-errors.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +12 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +1 -1
- package/dist/types/vite/plugins/virtual-entries.d.ts +1 -1
- package/dist/types/vite/router-discovery.d.ts +8 -47
- package/dist/types/vite/temp-rsc-server.d.ts +39 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +9 -0
- package/dist/types/wire-params.d.ts +39 -0
- package/dist/vite/index.js +3822 -3727
- package/package.json +1 -1
- package/skills/cache-guide/SKILL.md +8 -5
- package/skills/caching/SKILL.md +37 -18
- package/skills/client-urls/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +13 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/hooks/SKILL.md +1 -1
- package/skills/hooks/data.md +8 -1
- package/skills/hooks/handle-and-actions.md +33 -2
- package/skills/hooks/navigation.md +121 -15
- package/skills/hooks/state.md +6 -4
- package/skills/hooks/urls.md +16 -5
- package/skills/intercept/SKILL.md +23 -7
- package/skills/middleware/SKILL.md +18 -0
- package/skills/observability/SKILL.md +30 -0
- package/skills/ppr/SKILL.md +18 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +5 -1
- package/skills/server-actions/SKILL.md +15 -1
- package/skills/testing/client-components.md +25 -18
- package/skills/testing/response-routes.md +6 -4
- package/skills/testing/serve-shell-request.md +7 -7
- package/skills/use-cache/SKILL.md +46 -8
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/__internal.ts +7 -1
- package/src/browser/action-fence.ts +2 -1
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +37 -13
- package/src/browser/history-state.ts +6 -12
- package/src/browser/navigation-bridge.ts +59 -26
- package/src/browser/navigation-client.ts +20 -25
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +4 -1
- package/src/browser/network-error-handler.ts +4 -0
- package/src/browser/partial-update.ts +33 -6
- package/src/browser/prefetch/cache.ts +2 -1
- package/src/browser/prefetch/fetch.ts +8 -16
- package/src/browser/prefetch/queue.ts +4 -4
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/filter-segment-order.ts +2 -2
- package/src/browser/react/use-action.ts +24 -48
- package/src/browser/react/use-handle.ts +22 -18
- package/src/browser/react/use-link-status.ts +48 -60
- package/src/browser/react/use-navigation.ts +92 -41
- package/src/browser/rsc-router.tsx +1 -0
- package/src/browser/segment-reconciler.ts +109 -53
- package/src/browser/server-action-bridge.ts +81 -61
- package/src/browser/types.ts +25 -5
- package/src/browser/wire-protocol.ts +43 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +132 -75
- package/src/cache/cache-scope.ts +110 -64
- package/src/cache/cf/cf-cache-constants.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +486 -874
- package/src/cache/cf/cf-kv-utils.ts +1 -1
- package/src/cache/document-cache.ts +64 -46
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/read-through-swr.ts +20 -18
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +6 -2
- package/src/cache/taint.ts +5 -3
- package/src/cache/types.ts +7 -3
- package/src/cache/vercel/vercel-cache-store.ts +243 -310
- package/src/handles/pushed-by.ts +1 -1
- package/src/prefetch-markers.ts +5 -16
- package/src/prerender/store.ts +76 -0
- package/src/prerender/warm-request.ts +8 -4
- package/src/prerender.ts +2 -2
- package/src/route-name.ts +8 -0
- package/src/router/error-handling.ts +22 -10
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +82 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +5 -5
- package/src/router/match-context.ts +23 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +46 -57
- package/src/router/match-middleware/cache-lookup.ts +677 -726
- package/src/router/match-middleware/cache-store.ts +4 -8
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +14 -4
- package/src/router/prerender-match.ts +100 -44
- package/src/router/preview-match.ts +6 -41
- package/src/router/request-classification.ts +13 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +10 -95
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/helpers.ts +49 -13
- package/src/router/segment-resolution/loader-cache.ts +15 -9
- package/src/router/segment-resolution/loader-mask.ts +5 -5
- package/src/router/segment-resolution/prefetch-deferral.ts +30 -27
- package/src/router/segment-resolution/resolution-pass.ts +139 -0
- package/src/router/segment-resolution/revalidation.ts +939 -789
- package/src/router/segment-resolution/streamed-handler-boundary.ts +3 -0
- package/src/router/segment-resolution.ts +4 -13
- package/src/router/segment-wrappers.ts +51 -269
- package/src/router/telemetry.ts +5 -0
- package/src/router/types.ts +11 -1
- package/src/router.ts +19 -17
- package/src/rsc/full-payload.ts +6 -5
- package/src/rsc/handler-context.ts +8 -14
- package/src/rsc/handler.ts +126 -507
- package/src/rsc/json-route-result.ts +5 -9
- package/src/rsc/loader-fetch.ts +6 -5
- package/src/rsc/progressive-enhancement.ts +5 -0
- package/src/rsc/request-stages.ts +759 -0
- package/src/rsc/response-cache-serve.ts +10 -14
- package/src/rsc/response-route-handler.ts +13 -5
- package/src/rsc/rewrite-passes.ts +2 -1
- package/src/rsc/routine-plan.ts +1 -1
- package/src/rsc/rsc-rendering.ts +164 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +1 -1
- package/src/rsc/shell-capture.ts +275 -227
- package/src/rsc/shell-serve.ts +121 -18
- package/src/rsc/ssr-setup.ts +5 -4
- package/src/segment-system.tsx +3 -3
- package/src/server/context.ts +22 -25
- package/src/server/handle-store.ts +55 -0
- package/src/server/render-pass.ts +291 -0
- package/src/server/request-context.ts +75 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/testing/dispatch.ts +226 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/render-route.tsx +199 -13
- package/src/testing/serve-shell-request.ts +36 -40
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +30 -3
- package/src/types/handler-context.ts +12 -1
- package/src/types/segments.ts +1 -1
- package/src/vite/discovery/build-discovery-phase.ts +204 -0
- package/src/vite/discovery/build-env.ts +148 -0
- package/src/vite/discovery/bundle-scan-phase.ts +128 -0
- package/src/vite/discovery/dev-prerender-cache.ts +8 -3
- package/src/vite/discovery/dev-server-phase.ts +1776 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +1 -1
- package/src/vite/discovery/prerender-collection.ts +24 -39
- package/src/vite/discovery/state.ts +15 -0
- package/src/vite/plugins/version-plugin.ts +1 -1
- package/src/vite/plugins/virtual-entries.ts +1 -1
- package/src/vite/router-discovery.ts +29 -2428
- package/src/vite/temp-rsc-server.ts +223 -0
- package/src/vite/utils/prerender-utils.ts +47 -0
- package/src/wire-params.ts +40 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
package/package.json
CHANGED
|
@@ -320,11 +320,14 @@ domains, and the router id keeps two routers on one store apart when both
|
|
|
320
320
|
serve the same host and path (see `/host-router`, "Shared cache store"); the search part is sorted, excludes the router's internal params, and
|
|
321
321
|
honors `createRouter({ cache: { searchParams } })`.
|
|
322
322
|
|
|
323
|
-
A custom `key` function replaces the
|
|
324
|
-
or locale)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
323
|
+
A custom `key` function replaces the URL part of the default key (e.g., to
|
|
324
|
+
key by user role or locale), not the request type: document loads, client
|
|
325
|
+
navigations and intercept navigations still keep their own records, and the
|
|
326
|
+
key decides which requests of one type share a record. It also bypasses the
|
|
327
|
+
store's `keyGenerator` and the search-param filter. It runs once per request
|
|
328
|
+
and may read `cookies()`. Its result is stored namespaced (`key:` plus its URI
|
|
329
|
+
encoding, then the request type), so no value it returns, raw request input
|
|
330
|
+
included, names another route's record. A nested `cache()`
|
|
328
331
|
keys its records within the enclosing `key()` partition (`/caching` → "Keys
|
|
329
332
|
nest"). Normalize the result to the values you serve: each distinct value is
|
|
330
333
|
a record. On a `ppr` route, `key()` (and a store `keyGenerator` that returns
|
package/skills/caching/SKILL.md
CHANGED
|
@@ -102,7 +102,9 @@ tainted objects" below).
|
|
|
102
102
|
|
|
103
103
|
Handle pushes follow the same split. Handler pushes (`ctx.use(Meta)`,
|
|
104
104
|
`ctx.use(Breadcrumbs)`, custom handles) are stored with the segments and
|
|
105
|
-
replayed on a hit.
|
|
105
|
+
replayed on a hit. On a navigation the hit replays them only for the segments
|
|
106
|
+
it sends: a layout the browser keeps keeps its values on screen, as on an
|
|
107
|
+
uncached navigation. Pushes from a `loader()` body are not stored: the loader
|
|
106
108
|
re-runs on the hit and pushes again, so each value appears once. A loader that
|
|
107
109
|
runs only because a cached handler awaited it with `ctx.use(Loader)` does not
|
|
108
110
|
re-run on a hit, so its pushes are stored with the handler's.
|
|
@@ -154,7 +156,7 @@ cache(
|
|
|
154
156
|
swr: 300, // seconds a stale entry is served while it refreshes.
|
|
155
157
|
// Default: store.defaults.swr, else no SWR window
|
|
156
158
|
tags: ["blog"], // or (ctx) => string[]; see "Tag-Based Invalidation"
|
|
157
|
-
key: (ctx) => `blog:${ctx.pathname}`, // replaces the default key
|
|
159
|
+
key: (ctx) => `blog:${ctx.pathname}`, // replaces the URL part of the default key
|
|
158
160
|
condition: (ctx) => !ctx.request.headers.has("x-preview"), // false = bypass
|
|
159
161
|
store: blogStore, // per-boundary store; see "Custom Cache Store"
|
|
160
162
|
},
|
|
@@ -164,15 +166,26 @@ cache(
|
|
|
164
166
|
);
|
|
165
167
|
```
|
|
166
168
|
|
|
167
|
-
- **Default key**: `{doc|partial|intercept}:{routerId}@{host}{pathname}[:params][?search]`
|
|
168
|
-
— document requests, client navigations, and intercept
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
169
|
+
- **Default key**: `{doc|partial|intercept:<intercept>}:{routerId}@{host}{pathname}[:params][?search]`
|
|
170
|
+
— under this key, document requests, client navigations, and intercept
|
|
171
|
+
navigations are cached separately, each intercept that targets the route
|
|
172
|
+
apart (`<intercept>` names the one the navigation selected), and so are two
|
|
173
|
+
routers that share a store (see `/host-router`, "Shared cache store"). The
|
|
174
|
+
search part honors `cache.searchParams` (below).
|
|
175
|
+
- **`key`** replaces the URL part of the default key (host, path, params,
|
|
176
|
+
search): it bypasses those, the store's `keyGenerator`, and the
|
|
177
|
+
search-param filter. The request kind stays: document requests, client
|
|
178
|
+
navigations and each intercept navigation keep their own record under one
|
|
179
|
+
`key()` result, so `key()` decides which requests of one kind share a
|
|
180
|
+
record (a `key()` that ignores `:id` shares one document record across
|
|
181
|
+
ids), never that a document request reads a navigation's record (which
|
|
182
|
+
keeps the fallback of a `loading(..., { ssr: false })` that a document
|
|
183
|
+
leaves out) or an intercept's. The
|
|
184
|
+
result is stored namespaced (`key:` plus its URI encoding, then the kind:
|
|
185
|
+
`tier:gold` is `key:tier%3Agold|doc` for a document request and
|
|
186
|
+
`key:tier%3Agold|partial` for a navigation), so whatever it returns,
|
|
187
|
+
request input included, never names another route's default-keyed record
|
|
188
|
+
or a nested partition's. A nested `cache()` keys its records within that
|
|
176
189
|
partition (see "Keys nest" under "Nested Cache Boundaries"). On a `ppr`
|
|
177
190
|
route the same key (or, without one, the store's `keyGenerator`)
|
|
178
191
|
partitions the PPR shell: each partition captures and serves its own shell
|
|
@@ -532,7 +545,7 @@ const router = createRouter({
|
|
|
532
545
|
baked-shell manifest gate (a URL whose only params are excluded ones now
|
|
533
546
|
matches the prerendered shell), and `"use cache"` ctx key normalization.
|
|
534
547
|
- **`cache({ key })` override wins**: a custom key bypasses the filter along
|
|
535
|
-
with the rest of default key
|
|
548
|
+
with the rest of the default key's URL part.
|
|
536
549
|
- **The footgun**: excluding a param promises the rendered output does not
|
|
537
550
|
depend on it. If it does, the first variant is cached and served to everyone.
|
|
538
551
|
That is why the default is `"all"`.
|
|
@@ -725,7 +738,9 @@ default, shared by every router on the host with that prefix; `HttpOnly`,
|
|
|
725
738
|
`SameSite=Lax`, `Path=/`, `Secure` on https, `Max-Age` 11 s with KV and 3 s
|
|
726
739
|
without at the defaults: the longest memo staleness plus 1 s). It is strictly
|
|
727
740
|
functional: its value is `1`, it identifies no one, and it only sends that
|
|
728
|
-
browser's reads past the memos.
|
|
741
|
+
browser's reads past the memos. A stale entry's background refresh (`cache()`,
|
|
742
|
+
`"use cache"`, a cached loader, the document cache) sets none: no visitor made
|
|
743
|
+
that invalidation. The same user's requests that carry it skip
|
|
729
744
|
both memos on every isolate, so a mutating user never sees a memoized shell or
|
|
730
745
|
marker from before their write. What they read past the memos is the store's
|
|
731
746
|
own consistency. `VercelCacheStore`: `expireTag` is global (about 300 ms), so
|
|
@@ -1084,7 +1099,8 @@ cache({ ttl: 300, key: (ctx) => `tier:${tier(ctx)}` }, () => [
|
|
|
1084
1099
|
path("/faq", FaqPage, { name: "faq" }),
|
|
1085
1100
|
]),
|
|
1086
1101
|
|
|
1087
|
-
// Its own key() composes with the outer one
|
|
1102
|
+
// Its own key() composes with the outer one, then the request kind
|
|
1103
|
+
// ("key:tier%3Agold|key:v%3Ab|doc")
|
|
1088
1104
|
cache(
|
|
1089
1105
|
{ ttl: 60, key: (ctx) => `v:${ctx.searchParams.get("v") ?? "a"}` },
|
|
1090
1106
|
() => [path("/plans", PlansPage, { name: "plans" })],
|
|
@@ -1099,10 +1115,10 @@ results first, outermost first:
|
|
|
1099
1115
|
|
|
1100
1116
|
| Boundary the route sits in | Record key |
|
|
1101
1117
|
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1102
|
-
| a single keyed `cache()` | Its `key()` result, namespaced (`tier:gold` is stored as `key:tier%3Agold`)
|
|
1118
|
+
| a single keyed `cache()` | Its `key()` result, namespaced, then the request kind (`tier:gold` is stored as `key:tier%3Agold\|doc` for a document request) |
|
|
1103
1119
|
| `cache({ ttl })`, no `key`, under a keyed one | The chain's `key()` results, then this boundary's default key (its store's `keyGenerator` result, else the default key), encoded |
|
|
1104
|
-
| `cache({ key })` under a keyed one | The chain's `key()` results
|
|
1105
|
-
| deeper nesting | Composes the same way;
|
|
1120
|
+
| `cache({ key })` under a keyed one | The chain's `key()` results, then the request kind |
|
|
1121
|
+
| deeper nesting | Composes the same way; the innermost boundary adds its default key without a `key` of its own, its request kind with one |
|
|
1106
1122
|
| under a `cache({ store })` on another store | Also that store's `keyGenerator` result, one part per store: empty for a store that returns the default key while another's differs, none when all return it |
|
|
1107
1123
|
| `cache(false)` | No caching, as before; a `cache()` re-enabled below it stays in the partition |
|
|
1108
1124
|
| no `key` anywhere | The default key, or the store's `keyGenerator` result, as before |
|
|
@@ -1134,7 +1150,10 @@ results first, outermost first:
|
|
|
1134
1150
|
empty result there fails key resolution, so the request renders uncached,
|
|
1135
1151
|
and the router warns once naming the store. On the same store the inner
|
|
1136
1152
|
boundary's own default key already carries it (or its own `key()`
|
|
1137
|
-
overrides it).
|
|
1153
|
+
overrides it). Keep the default key in the result
|
|
1154
|
+
(`` `${locale}:${defaultKey}` ``): its prefix is the request kind, and a
|
|
1155
|
+
result without it gives a document request, a navigation and each
|
|
1156
|
+
intercept navigation one record.
|
|
1138
1157
|
- A response route's entry (`path.json()` and the other response routes) is
|
|
1139
1158
|
keyed the same way, behind a `response:` prefix. A `ppr` route's shell is
|
|
1140
1159
|
partitioned by the chain's `key()` results, namespaced the same way, since
|
|
@@ -734,8 +734,10 @@ a hard load of the target URL renders the full route.
|
|
|
734
734
|
values during SSR (live request seeds the SSR store; on ppr routes the
|
|
735
735
|
capture/resume renders seed the shell key's own sorted search — search is
|
|
736
736
|
part of shell identity, so static-part reads are legal).
|
|
737
|
-
`useNavigation`/`useLinkStatus` report the canonical nav
|
|
738
|
-
|
|
737
|
+
`useNavigation`/`useLinkStatus` report the canonical nav (while the frame
|
|
738
|
+
shows, `useNavigation()` is `state: "loading"` with `location` the
|
|
739
|
+
committed URL, even though the URL hooks already report the frame's
|
|
740
|
+
destination), but a reader inside optimistically-swapped content (destination WITH `loading()`)
|
|
739
741
|
unmounts at click — put status readers in surviving chrome. Errors: wrap
|
|
740
742
|
throwers in the client `ErrorBoundary`; `useFetchLoader` works unchanged
|
|
741
743
|
(route-independent lane; `revalidate()` deliberately not consulted).
|
|
@@ -161,6 +161,16 @@ short-circuits the middleware's downstream pipeline; global middleware that
|
|
|
161
161
|
wraps it can still run. Route middleware, handlers, and loaders below it do not.
|
|
162
162
|
An external CDN hit is different: the function never runs at all.
|
|
163
163
|
|
|
164
|
+
The background revalidation re-runs that downstream pipeline for the stale
|
|
165
|
+
entry. Its cookies, and the headers its route handlers and the middleware
|
|
166
|
+
after the cache write, go on its own response, never on the visitor's `STALE`
|
|
167
|
+
response; middleware ahead of the cache gets the `STALE` response from
|
|
168
|
+
`next()`, and its writes after `next()` land there. It
|
|
169
|
+
starts from what middleware ahead of the cache wrote to the response before
|
|
170
|
+
`next()`, as a `MISS` does, and its response is stored by the rules below:
|
|
171
|
+
its headers are kept, and a cookie it sets keeps the refreshed page out of the
|
|
172
|
+
cache.
|
|
173
|
+
|
|
164
174
|
## Cache Status Header
|
|
165
175
|
|
|
166
176
|
Response includes `x-document-cache-status`:
|
|
@@ -215,7 +225,9 @@ suffixes are still appended.
|
|
|
215
225
|
everyone). That includes a response whose request called
|
|
216
226
|
`updateTag()`/`revalidateTag()` on a store that keeps per-isolate memos
|
|
217
227
|
(`CFCacheStore`, `VercelCacheStore`): it carries the fresh-reads cookie
|
|
218
|
-
(`rango-state-fresh` by default), so it is not stored.
|
|
228
|
+
(`rango-state-fresh` by default), so it is not stored. A stale entry's
|
|
229
|
+
background re-run sets no fresh-reads cookie (no visitor made that
|
|
230
|
+
invalidation), so its invalidation does not keep the refreshed page out.
|
|
219
231
|
- Non-200 responses
|
|
220
232
|
- A 200 whose render reported an error: a component that threw after the
|
|
221
233
|
response started streaming, such as an async server component whose fetch
|
|
@@ -368,7 +368,7 @@ Per-slot merge order is **handler.use → shared use → slot-local use**. Slot-
|
|
|
368
368
|
|
|
369
369
|
Other things to keep in mind about `loading()`:
|
|
370
370
|
|
|
371
|
-
- Any `loading()` (regardless of source) makes the segment a streaming unit. A handler that includes `loading()` in its `.use` opts every mount site into streaming by default. To opt back out, pass `loading(false)` at the mount site (`loading === false` handling in [segment-resolution/
|
|
371
|
+
- Any `loading()` (regardless of source) makes the segment a streaming unit. A handler that includes `loading()` in its `.use` opts every mount site into streaming by default. To opt back out, pass `loading(false)` at the mount site (`loading === false` handling in [segment-resolution/revalidation.ts](../../src/router/segment-resolution/revalidation.ts)) — use the slot descriptor form for parallel slots so the opt-out doesn't broadcast.
|
|
372
372
|
|
|
373
373
|
Rule of thumb: only put `loading()` in `handler.use` if you genuinely want every mount site to stream by default. Use the slot descriptor's `use` for any per-slot intent at a `parallel()` call.
|
|
374
374
|
|
package/skills/hooks/SKILL.md
CHANGED
|
@@ -86,7 +86,7 @@ the one for your case.
|
|
|
86
86
|
| `useHref()` | Mount-aware href | `(path) => string` |
|
|
87
87
|
| `useMount()` | Current include() mount path | `string` |
|
|
88
88
|
| `useReverse()` | Local reverse for imported routes | `(name, params?, search?) => string` |
|
|
89
|
-
| `useNavigation()` | Reactive navigation state | state, location, pendingUrl, isStreaming
|
|
89
|
+
| `useNavigation()` | Reactive navigation state | state, location, pendingUrl, isNavigating, isStreaming |
|
|
90
90
|
| `useRouter()` | Stable router actions | push, replace, refresh, prefetch, back, forward |
|
|
91
91
|
| `useSegments()` | URL path & segment IDs | path, segmentIds, location |
|
|
92
92
|
| `useLinkStatus()` | Link pending state | { pending } |
|
package/skills/hooks/data.md
CHANGED
|
@@ -14,7 +14,8 @@ function ProductPrice() {
|
|
|
14
14
|
|
|
15
15
|
// data: T (guaranteed - throws if not in context)
|
|
16
16
|
// isLoading: boolean (refetch/load() states + a held navigation replacing
|
|
17
|
-
// this data — NOT the initial streamed read
|
|
17
|
+
// this data — NOT the initial streamed read, and NOT a server action
|
|
18
|
+
// re-running the loader)
|
|
18
19
|
// error: Error | null
|
|
19
20
|
|
|
20
21
|
return <span>${data.price}</span>;
|
|
@@ -30,6 +31,12 @@ component renders, `data` is present. (On document loads a loader registered
|
|
|
30
31
|
with `{ ssr: false }` is already settled at first paint, so its
|
|
31
32
|
reads never suspend there — see `/loader`.)
|
|
32
33
|
|
|
34
|
+
**`isLoading` covers navigations and `load()` only.** When a server action
|
|
35
|
+
re-runs the loader (its revalidation), `isLoading` stays `false`: the action's
|
|
36
|
+
own progress is `useAction()`'s `state`, or `useNavigation().state`, which is
|
|
37
|
+
`"loading"` while an action runs. Read those for an action spinner and keep
|
|
38
|
+
`isLoading` for "this data is being replaced by a navigation or refresh".
|
|
39
|
+
|
|
33
40
|
**A held navigation flags the data it keeps on screen.** When a navigation
|
|
34
41
|
keeps the current content visible while the loader re-runs (a `transition()`
|
|
35
42
|
same-route nav, a same-structure search/filter nav), the reader that is still
|
|
@@ -50,7 +50,7 @@ page's values over the page being left:
|
|
|
50
50
|
| ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
51
|
| Document load | The values the HTML was rendered from. `push(promise)` and `.defer()` are awaited during SSR, so the document ships the resolved value. On a `ppr` shell HIT that holds for the handlers' and the `ssr: false` loaders' pushes, which the shell baked; a live-lane loader's push arrives right after hydration (see `/ppr`). |
|
|
52
52
|
| Navigation to a page that commits at once (a cold click that mounts new segments) | The destination's values, in the page's commit; the page's effects read them |
|
|
53
|
-
| Navigation to a page React holds in a transition (`transition()`, same-route search change, fully prefetched click, a handler that awaits) | The page being left keeps its values; the destination's land
|
|
53
|
+
| Navigation to a page React holds in a transition (`transition()`, same-route search change, fully prefetched click, a handler that awaits) | The page being left keeps its values; the destination's land right after the page's commit: without view transitions in the same task, before paint; with view transitions when the transition's `ready` resolves |
|
|
54
54
|
| `push(promise)` or `.defer()` on a client navigation | The previous value, then the new one when the promise resolves, possibly well after the page |
|
|
55
55
|
|
|
56
56
|
You never see a pending value, a blank, or the destination's title over the old
|
|
@@ -193,7 +193,34 @@ tracking actions called outside a `<form action={...}>` flow.
|
|
|
193
193
|
### useAction()
|
|
194
194
|
|
|
195
195
|
Track the lifecycle of a server action, wherever on the page it was invoked.
|
|
196
|
-
If the same action fires several times, the hook reports the latest call
|
|
196
|
+
If the same action fires several times, the hook reports the latest call: the
|
|
197
|
+
last one to start.
|
|
198
|
+
|
|
199
|
+
- When the latest call returns before an earlier one, the hook shows its
|
|
200
|
+
result. The earlier call settling afterwards changes nothing the hook
|
|
201
|
+
reports, a result or an error alike. That call still runs to its end: its
|
|
202
|
+
promise resolves or rejects for its caller, its revalidation re-renders the
|
|
203
|
+
page, and an error-boundary response replaces it.
|
|
204
|
+
- A latest call that was aborted reports nothing: `idle`, with no result or
|
|
205
|
+
error. The hook does not fall back to an earlier call. The router aborts
|
|
206
|
+
calls in flight when another call's response is an error boundary, and on
|
|
207
|
+
Back/Forward into or out of an intercept.
|
|
208
|
+
- `useNavigation()` stays `loading` while any call runs.
|
|
209
|
+
|
|
210
|
+
Which calls count as the same action depends on what you pass:
|
|
211
|
+
|
|
212
|
+
- A function, or a full id (`"src/actions/cart.ts#addToCart"`): that action
|
|
213
|
+
only.
|
|
214
|
+
- A string name (`"addToCart"`): every action with that export name, in any
|
|
215
|
+
`"use server"` file. The hook follows the latest call among all of them.
|
|
216
|
+
|
|
217
|
+
The hook tracks one call at a time, not one per argument. Every
|
|
218
|
+
`AddToCartButton` below reads the same `useAction(addToCart)`, so each shows
|
|
219
|
+
the latest call from any button, whatever its `productId`. A reader that
|
|
220
|
+
filters on `payload` (`payload?.[0] === productId`) cannot track an earlier
|
|
221
|
+
call once a later one starts: from then on the hook reports the later call
|
|
222
|
+
only. For per-call state, keep it with the call: `useActionState` or
|
|
223
|
+
`useTransition` in the component that makes it.
|
|
197
224
|
|
|
198
225
|
```tsx
|
|
199
226
|
"use client";
|
|
@@ -231,6 +258,10 @@ const isLoading = useAction(addToCart, (s) => s.state === "loading");
|
|
|
231
258
|
const isAdding = useAction("addToCart", (s) => s.state === "loading");
|
|
232
259
|
```
|
|
233
260
|
|
|
261
|
+
Each state change commits urgently, so it shows at once, also while React
|
|
262
|
+
holds a navigation's page. It starts no transition of its own, so it never
|
|
263
|
+
takes that page's transition types.
|
|
264
|
+
|
|
234
265
|
Pass an action imported directly into the client component, or its export
|
|
235
266
|
name as a string. An action received as a prop from a server component has
|
|
236
267
|
lost the metadata `useAction` needs and throws; use the string form there.
|
|
@@ -12,10 +12,11 @@ function NavIndicator() {
|
|
|
12
12
|
const nav = useNavigation();
|
|
13
13
|
|
|
14
14
|
// State properties
|
|
15
|
-
nav.state; // 'idle' | 'loading'
|
|
16
|
-
nav.
|
|
17
|
-
nav.
|
|
18
|
-
nav.
|
|
15
|
+
nav.state; // 'idle' | 'loading' — loading during a navigation AND a server action
|
|
16
|
+
nav.isNavigating; // boolean — a navigation is in flight (false during an action)
|
|
17
|
+
nav.isStreaming; // boolean — RSC data is still streaming (navigation or action)
|
|
18
|
+
nav.location; // The page on screen, until the next page shows
|
|
19
|
+
nav.pendingUrl; // The page being fetched (or null)
|
|
19
20
|
|
|
20
21
|
return nav.state === "loading" ? <Spinner /> : null;
|
|
21
22
|
}
|
|
@@ -27,6 +28,22 @@ function IsLoading() {
|
|
|
27
28
|
}
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
#### Navigation versus server action
|
|
32
|
+
|
|
33
|
+
`state` is `"loading"` while a navigation is in flight and also while a server
|
|
34
|
+
action runs. That is deliberate: something on this screen is loading, so a
|
|
35
|
+
spinner or a progress bar has a signal for an action too. When a bar should
|
|
36
|
+
show for navigations only, read `isNavigating`: true from the start of a
|
|
37
|
+
navigation until its commit, false during an action (where `state` is still
|
|
38
|
+
`"loading"`). `isStreaming` covers the action's response as well as a
|
|
39
|
+
navigation's.
|
|
40
|
+
|
|
41
|
+
| Event | `state` | `isNavigating` | `isStreaming` |
|
|
42
|
+
| --------------- | ----------- | -------------- | ---------------------------- |
|
|
43
|
+
| a navigation | `"loading"` | `true` | true until the stream ends |
|
|
44
|
+
| a server action | `"loading"` | `false` | true until the response ends |
|
|
45
|
+
| neither | `"idle"` | `false` | `false` |
|
|
46
|
+
|
|
30
47
|
#### Pending UI, Back/Forward included
|
|
31
48
|
|
|
32
49
|
A link click, `router.push()` and a Back/Forward that has to fetch its entry
|
|
@@ -40,19 +57,49 @@ on screen:
|
|
|
40
57
|
| the page has not fully arrived yet | `isStreaming`: true from the start of the request until the stream ends |
|
|
41
58
|
| which page is on screen while the request runs | `location`, or `usePathname()` / `useSearchParams()` (see [`./urls.md`](./urls.md)) |
|
|
42
59
|
|
|
43
|
-
`pendingUrl` covers the network wait
|
|
60
|
+
`pendingUrl` covers the network wait, and `useLinkStatus()` covers more: a
|
|
61
|
+
link's `pending` stays `true` after `pendingUrl` turns `null`, while the
|
|
62
|
+
response streams and React holds the page being left, until the destination
|
|
63
|
+
commits (see [useLinkStatus()](#uselinkstatus)). For a spinner on the clicked
|
|
64
|
+
link, read `useLinkStatus()`; `pendingUrl` names the URL only while the
|
|
65
|
+
request is out.
|
|
66
|
+
|
|
67
|
+
Once the response has arrived a loader
|
|
44
68
|
may still be streaming, and React keeps the page being left on screen where a
|
|
45
69
|
navigation holds its content (a same-route change, a `transition()` route,
|
|
46
70
|
with or without a view transition). `state` stays `"loading"` through that
|
|
47
|
-
part:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
while React holds
|
|
71
|
+
part: `"idle"` commits with the destination's `useSearchParams()`,
|
|
72
|
+
`usePathname()` and `useParams()`, so a component that reads both does not
|
|
73
|
+
see the navigation finished on the URL it is leaving. That holds when a
|
|
74
|
+
server action settles, or a newer navigation ends with no page (an external
|
|
75
|
+
redirect, a reload), while React holds the page: `"idle"` waits for the page.
|
|
76
|
+
A newer navigation that fails shows the error screen, and `"idle"` arrives
|
|
77
|
+
with it. A newer click shows at once (`"loading"`, its `pendingUrl`), also
|
|
78
|
+
while an action is in flight. After a server action, `"idle"` arrives with
|
|
79
|
+
the action's revalidated content.
|
|
52
80
|
`isStreaming` can outlast that commit (a loader still streaming below a
|
|
53
81
|
boundary that already showed), so use it for a progress indicator that should
|
|
54
82
|
last until the new page is complete.
|
|
55
83
|
|
|
84
|
+
While React holds a page, every field follows one rule, whatever the reader
|
|
85
|
+
reads (a selector, the full state): work that starts shows at once
|
|
86
|
+
(`"loading"`, `isStreaming` and `isNavigating` turning `true`, a newer click's
|
|
87
|
+
`pendingUrl`), and what settles waits for the page (`"idle"`, `false`, a
|
|
88
|
+
`null` `pendingUrl`). So a progress bar that reads
|
|
89
|
+
`useNavigation((n) => n.isStreaming)` stays up until the held page shows, even
|
|
90
|
+
when the response stream ended earlier, and agrees with every other
|
|
91
|
+
`useNavigation()` reader on the screen. A page that ends the hold urgently (a
|
|
92
|
+
Back to a page without `transition()`, a link with `transition={false}`)
|
|
93
|
+
shows with its own values in every `useNavigation()` reader. `usePathname()`
|
|
94
|
+
can read the path of the page being left on that page's first screen, until
|
|
95
|
+
React finishes the render of the page it held.
|
|
96
|
+
|
|
97
|
+
The rule holds for readers mounted before the page was handed to React. A
|
|
98
|
+
reader that mounts while the page is held (a menu opened during the wait)
|
|
99
|
+
never saw the hand-over and reads the live values, the destination's `idle`
|
|
100
|
+
and location, until the page shows. This is by design (#1046): its first
|
|
101
|
+
render has no page of its own to wait for.
|
|
102
|
+
|
|
56
103
|
On Back/Forward the address bar changes at once, before any of this: during
|
|
57
104
|
the wait `window.location` is the entry being fetched, the same URL as
|
|
58
105
|
`pendingUrl`, and `location` is the page on screen. A Back/Forward served
|
|
@@ -60,8 +107,28 @@ from the client's history cache restores its page in one commit and reports
|
|
|
60
107
|
no pending state.
|
|
61
108
|
|
|
62
109
|
`location` moves in that commit too. A component that first mounts while
|
|
63
|
-
React holds the page being left
|
|
64
|
-
|
|
110
|
+
React holds the page being left (a dropdown opened on the old page, a lazy
|
|
111
|
+
component or a `<Suspense>` there that resolves during the hold) reads the
|
|
112
|
+
destination's `state`, `location` and URL hooks, because each hook seeds its
|
|
113
|
+
first value from the router's live state (issue #1046; by design). A menu
|
|
114
|
+
opened while a click's page is held therefore highlights the destination
|
|
115
|
+
link. Components already mounted keep the page on screen's values until the
|
|
116
|
+
destination commits.
|
|
117
|
+
|
|
118
|
+
A second click while React holds the first click's page works the same way:
|
|
119
|
+
`"loading"` and the second click's `pendingUrl` show at once, and `location`
|
|
120
|
+
stays the page on screen. It moves to the first click's page only if that
|
|
121
|
+
page shows before the second one arrives; if the second page replaces it
|
|
122
|
+
first, the first page never shows and its URL is never `location`.
|
|
123
|
+
|
|
124
|
+
**One exception: inside an optimistic `clientUrls()` frame.** When a
|
|
125
|
+
cross-route navigation shows the destination's `loading()` frame at the click,
|
|
126
|
+
`usePathname()`, `useSearchParams()` and `useParams()` report the frame's
|
|
127
|
+
destination (see [`./urls.md`](./urls.md)), but `useNavigation()` reports the
|
|
128
|
+
real navigation: `state` is `"loading"` and `location` is the committed URL,
|
|
129
|
+
the page that is still the router's. So `location` is "which page is
|
|
130
|
+
committed", not always "which page the eye sees", and a status reader should
|
|
131
|
+
sit in chrome that survives the swap (see `/client-urls`).
|
|
65
132
|
|
|
66
133
|
### useRouter()
|
|
67
134
|
|
|
@@ -187,13 +254,17 @@ const segments = useSegments();
|
|
|
187
254
|
const part = segments.path[depth];
|
|
188
255
|
```
|
|
189
256
|
|
|
190
|
-
|
|
191
|
-
|
|
257
|
+
While React holds the page being left, `useSegments()` keeps that page's
|
|
258
|
+
values. The new page's `path` and `location` commit with it, so its effects
|
|
259
|
+
read them; `segmentIds` come with the page's handle data and can land right
|
|
260
|
+
after its commit, as handle values do (see
|
|
261
|
+
[handle-and-actions](./handle-and-actions.md#when-handle-values-change-by-design)).
|
|
192
262
|
|
|
193
263
|
### useLinkStatus()
|
|
194
264
|
|
|
195
265
|
Track pending state inside a Link component. `pending` is `true` while a
|
|
196
|
-
navigation to
|
|
266
|
+
navigation to the link's `to` is in flight, until the page it brings
|
|
267
|
+
commits:
|
|
197
268
|
|
|
198
269
|
```tsx
|
|
199
270
|
"use client";
|
|
@@ -210,3 +281,38 @@ function LoadingIndicator() {
|
|
|
210
281
|
<LoadingIndicator />
|
|
211
282
|
</Link>;
|
|
212
283
|
```
|
|
284
|
+
|
|
285
|
+
The spinner ends in the commit that shows the destination: its `loading()`
|
|
286
|
+
skeleton without `transition()`, or the page itself under `transition()`,
|
|
287
|
+
where React holds the page being left until the new one is ready. Through a
|
|
288
|
+
server redirect, the clicked link stays pending until the target page
|
|
289
|
+
commits. A newer navigation and an abort end it at once, outside the narrow
|
|
290
|
+
case below. A failure ends it with the error: a route's `errorBoundary()` commits like a page, and a
|
|
291
|
+
failure the router shows at its root (a network error, a response it cannot
|
|
292
|
+
read) replaces the page, links included.
|
|
293
|
+
|
|
294
|
+
`pending` follows the same rule as `useNavigation()`: a change commits in the
|
|
295
|
+
lane of the notification that carries it. The click's notification goes out
|
|
296
|
+
a task after the click and commits urgently, so the spinner shows at once,
|
|
297
|
+
and an abort or a newer click ends it the same way. The hook starts no
|
|
298
|
+
transition of its own, so it never takes the page's transition types. A
|
|
299
|
+
fully prefetched click whose page commits before that task shows no pending
|
|
300
|
+
state: its page is already there.
|
|
301
|
+
|
|
302
|
+
One narrow case is a known trade: when another flow's commit (a server
|
|
303
|
+
action's) is handed to React in the same task as the click, before the
|
|
304
|
+
click's notification goes out, the notification commits with that commit.
|
|
305
|
+
If React holds it, the spinner, and `useNavigation()`'s `loading`, show when
|
|
306
|
+
it lands; a newer click or an abort delivered that way ends an earlier
|
|
307
|
+
spinner when it lands too.
|
|
308
|
+
|
|
309
|
+
The two hooks answer different questions. `pending` is this link's
|
|
310
|
+
navigation; `useNavigation()` is the router's, and its `"idle"` waits for any
|
|
311
|
+
page or tree React holds. So a link whose navigation is aborted while React
|
|
312
|
+
holds another flow's commit reads `pending: false` while `state` stays
|
|
313
|
+
`"loading"` until that commit lands: the frame any link that is not the
|
|
314
|
+
destination reads during a navigation.
|
|
315
|
+
|
|
316
|
+
`pending` lasts longer than `useNavigation().pendingUrl`, which turns `null`
|
|
317
|
+
when the response arrives. While the response streams, a link reads `pending`
|
|
318
|
+
and `pendingUrl` is `null`.
|
package/skills/hooks/state.md
CHANGED
|
@@ -132,10 +132,12 @@ The URL hooks follow the same rule for a component that is mounted across the
|
|
|
132
132
|
navigation: on back/forward too, `usePathname()` and `useSearchParams()`
|
|
133
133
|
change with the page, not at the `popstate` event (see
|
|
134
134
|
[`./urls.md`](./urls.md), "When the URL hooks change"). One difference
|
|
135
|
-
|
|
136
|
-
after the response arrived and while React still holds the destination,
|
|
137
|
-
its own entry's location state from `useLocationState` and the
|
|
138
|
-
URL and params from `usePathname()`, `useSearchParams()` and
|
|
135
|
+
is by design (issue #1046): a component that first mounts in the page being
|
|
136
|
+
left, after the response arrived and while React still holds the destination,
|
|
137
|
+
reads its own entry's location state from `useLocationState` and the
|
|
138
|
+
destination's URL and params from `usePathname()`, `useSearchParams()` and
|
|
139
|
+
`useParams()`. Components already on screen keep the page on screen's values
|
|
140
|
+
until the destination commits.
|
|
139
141
|
|
|
140
142
|
### State on router.push() / router.replace()
|
|
141
143
|
|
package/skills/hooks/urls.md
CHANGED
|
@@ -125,6 +125,14 @@ click or `router.push()`.
|
|
|
125
125
|
| `router.push(url, { revalidate: false })` | at once: nothing is fetched and the page stays |
|
|
126
126
|
| inside an optimistic `clientUrls()` destination | at the click, for that branch only (see `/client-urls`) |
|
|
127
127
|
|
|
128
|
+
**A newer click that fails while a page is held.** Click a link, and while
|
|
129
|
+
React holds the page being left (a `transition()` route, or a loader still
|
|
130
|
+
streaming) click another whose request fails: the root error screen replaces
|
|
131
|
+
the app, and the held page never shows. Under that screen `useNavigation()`
|
|
132
|
+
reads `"idle"` with the first click's `location`, and no hook reader is on
|
|
133
|
+
screen to see it. The hooks are not wrong, there is simply nobody left to read
|
|
134
|
+
them; the error boundary's reset brings the app back.
|
|
135
|
+
|
|
128
136
|
**The hooks can disagree with the address bar.** On Back/Forward the browser
|
|
129
137
|
changes the URL and `history.state` before it tells the router. When the
|
|
130
138
|
entry has to be fetched (it left the client's history cache, or the cache was
|
|
@@ -142,11 +150,14 @@ whole wait. A Back/Forward served from the history cache has no wait.
|
|
|
142
150
|
`setSearchParams((prev) => ...)` builds on the same location: called during a
|
|
143
151
|
pending Back/Forward, `prev` is the search of the page on screen.
|
|
144
152
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
153
|
+
A component that first mounts in the page being left after the navigation's
|
|
154
|
+
response has arrived, while React still holds the destination behind a loader
|
|
155
|
+
or a `transition()`, reads the destination's URL and params from these hooks
|
|
156
|
+
(issue #1046; by design). Every router hook seeds its first value from the
|
|
157
|
+
router's live state, so a menu opened while a click's page is held highlights
|
|
158
|
+
the destination link. Components already on screen keep the values of the
|
|
159
|
+
page on screen until the destination commits, and then every hook agrees. It
|
|
160
|
+
applies to a push and to Back/Forward alike.
|
|
150
161
|
|
|
151
162
|
### useHref()
|
|
152
163
|
|
|
@@ -68,6 +68,7 @@ export const urlpatterns = urls(({ path, layout, intercept, loader, loading }) =
|
|
|
68
68
|
| ----------------------------------------------------- | ---------------------------------------------------- |
|
|
69
69
|
| Click link `/shop/product/abc` | `<ProductModal />` in `@modal`, background preserved |
|
|
70
70
|
| Direct URL `/shop/product/abc`, or a reload | Full `<ProductPage />` page |
|
|
71
|
+
| `router.refresh()`, or another tab's action | Full `<ProductPage />` page, as a reload |
|
|
71
72
|
| Back from the open intercept | The intercept closes; the source page stays |
|
|
72
73
|
| Back/Forward onto the intercept's entry | The intercept over its source page |
|
|
73
74
|
| The same, uncached, from a page other than the source | Full `<ProductPage />` page, as a document load |
|
|
@@ -81,6 +82,11 @@ state like any document load, and the entry is a full page from then on. In
|
|
|
81
82
|
dev, an edit that changes the app version clears the history cache, so the
|
|
82
83
|
next Back/Forward onto an intercept entry takes that path.
|
|
83
84
|
|
|
85
|
+
An intercept is a page in the middle of a navigation. A refresh reloads its
|
|
86
|
+
URL, so `router.refresh()` with the intercept open, and the refresh another
|
|
87
|
+
tab's action triggers, render the full page, as a document load does. The
|
|
88
|
+
entry is a full page from then on.
|
|
89
|
+
|
|
84
90
|
A server action on `/shop` that calls `redirect("/shop/product/abc")` opens the
|
|
85
91
|
intercept too, and so does a link from `/shop` to a route that redirects
|
|
86
92
|
there. The page under it is the browser's copy, as on a link click: it
|
|
@@ -370,9 +376,14 @@ below), and `cache()` onto the target route.
|
|
|
370
376
|
|
|
371
377
|
To cache a modal, put `cache()` on the target route (or a layout above it).
|
|
372
378
|
An intercept navigation is cached under that scope with its own `intercept:`
|
|
373
|
-
key, so it never collides with the full-page render of the same URL.
|
|
374
|
-
|
|
375
|
-
|
|
379
|
+
key, so it never collides with the full-page render of the same URL. The key
|
|
380
|
+
names the intercept the navigation selected, so two intercepts that target one
|
|
381
|
+
route with different `when()` conditions each keep their own record. Both
|
|
382
|
+
hold under a `cache({ key })` too: the `key()` replaces the URL part of the
|
|
383
|
+
key, never the request kind, so a document request and a plain navigation
|
|
384
|
+
never read an intercept's record (see `/caching`). For finer control, use
|
|
385
|
+
`"use cache"` in the intercept handler, or cache the intercept's loader:
|
|
386
|
+
`loader(ProductLoader, () => [cache()])`.
|
|
376
387
|
|
|
377
388
|
## Errors in an Intercept
|
|
378
389
|
|
|
@@ -463,7 +474,9 @@ contract and direction-aware examples.
|
|
|
463
474
|
## Interaction with Prerender
|
|
464
475
|
|
|
465
476
|
When the target route of an intercept uses `Prerender`, the intercept handler is
|
|
466
|
-
also resolved at build time and stored
|
|
477
|
+
also resolved at build time and stored in an intercept variant next to the
|
|
478
|
+
route's own pre-rendered page. The variant holds what an intercept navigation
|
|
479
|
+
renders: the layouts above the route and the intercept, not the route's page.
|
|
467
480
|
This means intercept navigations to pre-rendered routes are served from the
|
|
468
481
|
prerender store without executing handler code at runtime.
|
|
469
482
|
|
|
@@ -496,13 +509,16 @@ Build-time behavior:
|
|
|
496
509
|
- The intercept handler (`<ProductModal />`) is resolved with BuildContext
|
|
497
510
|
- Result is stored under the key `"detail/paramHash/i"` (intercept variant)
|
|
498
511
|
- `when` config conditions are skipped at build time (all intercepts pre-rendered unconditionally)
|
|
499
|
-
- `when` is still evaluated at runtime by the intercept-resolution middleware
|
|
512
|
+
- `when` is still evaluated at runtime by the intercept-resolution middleware,
|
|
513
|
+
and a navigation shows only the intercept it selected
|
|
514
|
+
- Two intercepts on one layout that fill the same slot are not pre-rendered:
|
|
515
|
+
they render live
|
|
500
516
|
|
|
501
517
|
Runtime behavior:
|
|
502
518
|
|
|
503
|
-
- Intercept navigation: prerender store serves the `/i` variant (frozen handler + fresh loaders)
|
|
519
|
+
- Intercept navigation: prerender store serves the `/i` variant (frozen handler + fresh loaders). A layout the client holds (`ShopLayout`) stays as it is on screen, as on a live intercept, unless its `revalidate()` asks for it
|
|
504
520
|
- Direct navigation: prerender store serves the main variant (full page)
|
|
505
|
-
- If no intercept prerender entry exists, falls through to live intercept resolution
|
|
521
|
+
- If no intercept prerender entry exists, or it lacks the selected intercept, falls through to live intercept resolution
|
|
506
522
|
|
|
507
523
|
Loaders inside the intercept always run fresh at request time, same as regular
|
|
508
524
|
pre-rendered routes.
|
|
@@ -319,6 +319,24 @@ export const timing: Middleware = async (ctx, next) => {
|
|
|
319
319
|
};
|
|
320
320
|
```
|
|
321
321
|
|
|
322
|
+
A middleware may answer without waiting for its `next()`, as a timeout
|
|
323
|
+
fallback does. The middleware ahead then see its `Response` after `next()`
|
|
324
|
+
and write on it. The abandoned render keeps running and builds its own
|
|
325
|
+
response, which nothing serves.
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
export const renderTimeout: Middleware = (ctx, next) =>
|
|
329
|
+
Promise.race([
|
|
330
|
+
next(),
|
|
331
|
+
new Promise<Response>((resolve) =>
|
|
332
|
+
setTimeout(
|
|
333
|
+
() => resolve(new Response("Try again", { status: 503 })),
|
|
334
|
+
2000,
|
|
335
|
+
),
|
|
336
|
+
),
|
|
337
|
+
]);
|
|
338
|
+
```
|
|
339
|
+
|
|
322
340
|
### Typed context variables in middleware
|
|
323
341
|
|
|
324
342
|
Use `createVar<T>()` for type-safe data sharing between middleware and handlers:
|