@rangojs/router 0.26.6 → 0.28.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-coordinator.d.ts +0 -4
- package/dist/types/browser/action-fence.d.ts +11 -3
- package/dist/types/browser/event-controller.d.ts +20 -1
- package/dist/types/browser/history-state.d.ts +7 -9
- package/dist/types/browser/intercept-utils.d.ts +23 -0
- package/dist/types/browser/navigation-store.d.ts +8 -6
- package/dist/types/browser/react/filter-segment-order.d.ts +2 -2
- package/dist/types/browser/rsc-router.d.ts +1 -1
- package/dist/types/browser/segment-reconciler.d.ts +6 -4
- package/dist/types/browser/types.d.ts +2 -1
- package/dist/types/browser/wire-protocol.d.ts +25 -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/tag-invalidation.d.ts +10 -0
- 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 +4 -6
- 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 +4 -4
- 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 +12 -11
- package/dist/types/router/match-handlers.d.ts +2 -6
- package/dist/types/router/match-middleware/background-revalidation.d.ts +10 -20
- package/dist/types/router/match-middleware/cache-lookup.d.ts +35 -87
- package/dist/types/router/match-middleware/cache-store.d.ts +2 -2
- package/dist/types/router/match-middleware/segment-resolution.d.ts +8 -6
- 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/revalidation.d.ts +6 -1
- package/dist/types/router/router-context.d.ts +9 -63
- package/dist/types/router/router-interfaces.d.ts +8 -14
- 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 +93 -0
- package/dist/types/router/segment-resolution/revalidation.d.ts +82 -31
- 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 +10 -0
- 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 +2 -2
- 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 +24 -104
- package/dist/types/server/rewrite-trail.d.ts +2 -2
- package/dist/types/testing/dispatch.d.ts +35 -18
- package/dist/types/testing/serve-shell-request.d.ts +7 -0
- package/dist/types/types/cache-types.d.ts +6 -3
- package/dist/types/types/error-types.d.ts +9 -1
- package/dist/types/types/handler-context.d.ts +18 -7
- 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 +34 -0
- package/dist/vite/index.js +3809 -3710
- 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 +2 -1
- package/skills/document-cache/SKILL.md +18 -1
- package/skills/handler-use/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +53 -13
- package/skills/layout/SKILL.md +11 -7
- package/skills/loader/SKILL.md +30 -13
- package/skills/middleware/SKILL.md +19 -1
- package/skills/migrate-nextjs/SKILL.md +5 -4
- package/skills/parallel/SKILL.md +5 -4
- package/skills/ppr/SKILL.md +19 -3
- package/skills/prerender/SKILL.md +39 -15
- package/skills/rango/SKILL.md +23 -11
- package/skills/server-actions/SKILL.md +51 -6
- package/skills/testing/predicates.md +1 -1
- 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/ux-architecture/SKILL.md +3 -2
- package/src/__internal.ts +7 -1
- package/src/browser/action-coordinator.ts +2 -13
- package/src/browser/action-fence.ts +11 -3
- package/src/browser/connection-warmup.ts +3 -1
- package/src/browser/event-controller.ts +42 -3
- package/src/browser/history-state.ts +15 -13
- package/src/browser/intercept-utils.ts +51 -0
- package/src/browser/navigation-bridge.ts +34 -30
- package/src/browser/navigation-client.ts +21 -33
- package/src/browser/navigation-store.ts +25 -27
- package/src/browser/navigation-transaction.ts +7 -11
- package/src/browser/partial-update.ts +8 -0
- 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/rsc-router.tsx +17 -2
- package/src/browser/segment-reconciler.ts +66 -53
- package/src/browser/server-action-bridge.ts +265 -127
- package/src/browser/types.ts +2 -1
- package/src/browser/wire-protocol.ts +41 -0
- package/src/cache/cache-key-utils.ts +11 -5
- package/src/cache/cache-runtime.ts +110 -50
- 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 +42 -15
- package/src/cache/handle-snapshot.ts +19 -7
- package/src/cache/segment-codec.ts +55 -0
- package/src/cache/shell-snapshot.ts +2 -2
- package/src/cache/tag-invalidation.ts +21 -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 +4 -7
- 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 +4 -4
- package/src/router/instrument.ts +4 -3
- package/src/router/intercept-resolution.ts +77 -75
- package/src/router/loader-resolution.ts +5 -4
- package/src/router/match-api.ts +4 -5
- package/src/router/match-context.ts +33 -12
- package/src/router/match-handlers.ts +8 -20
- package/src/router/match-middleware/background-revalidation.ts +41 -53
- package/src/router/match-middleware/cache-lookup.ts +680 -720
- package/src/router/match-middleware/cache-store.ts +14 -12
- package/src/router/match-middleware/intercept-resolution.ts +3 -23
- package/src/router/match-middleware/segment-resolution.ts +34 -61
- package/src/router/match-result.ts +1 -20
- package/src/router/middleware.ts +69 -11
- package/src/router/navigation-snapshot.ts +4 -3
- 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/revalidation.ts +33 -7
- package/src/router/route-snapshot.ts +2 -5
- package/src/router/router-context.ts +14 -117
- package/src/router/router-interfaces.ts +8 -14
- package/src/router/segment-resolution/loader-cache.ts +14 -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 +137 -0
- package/src/router/segment-resolution/revalidation.ts +897 -776
- 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 +10 -0
- package/src/router.ts +12 -13
- 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 +757 -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 +172 -349
- package/src/rsc/server-action.ts +87 -113
- package/src/rsc/shell-capture-constants.ts +3 -1
- package/src/rsc/shell-capture.ts +212 -169
- 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 +26 -115
- package/src/server/rewrite-trail.ts +6 -4
- package/src/ssr/index.tsx +1 -1
- package/src/ssr/ssr-root.tsx +1 -0
- package/src/testing/dispatch.ts +223 -710
- package/src/testing/e2e/page-helpers.ts +2 -2
- package/src/testing/serve-shell-request.ts +37 -32
- package/src/types/cache-types.ts +6 -3
- package/src/types/error-types.ts +9 -1
- package/src/types/handler-context.ts +21 -7
- 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 +35 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +0 -72
- package/src/router/segment-resolution/fresh.ts +0 -945
|
@@ -309,16 +309,16 @@ layout(DocsNav, () => [
|
|
|
309
309
|
|
|
310
310
|
## Interaction with DSL Items
|
|
311
311
|
|
|
312
|
-
| DSL item | Behavior with Prerender
|
|
313
|
-
| -------------- |
|
|
314
|
-
| `loader()` | Live at runtime, bundled normally. Use `cache()` for caching.
|
|
315
|
-
| `revalidate()` | Without Passthrough: no
|
|
316
|
-
| `cache()` | Orthogonal -- prerendered segments bypass the runtime cache. Use `cache()` on parent layouts and loaders.
|
|
317
|
-
| `layout()` | Child layouts inside path are pre-rendered. Parent layouts are live.
|
|
318
|
-
| `parallel()` | Parallel slots inside path are pre-rendered.
|
|
319
|
-
| `middleware()` | Skipped while collecting build-time Flight payloads (no request). For `Prerender` + `ppr`, the build-shell step replays global and route middleware with `ctx.build === true`; `ctx.dynamic()` skips that shell. At request time middleware runs normally on every request.
|
|
320
|
-
| `loading()` | Ignored without Passthrough. Works for live fallback with Passthrough.
|
|
321
|
-
| `intercept()` | Pre-rendered at build time. Intercept variant stored under `/i` key alongside main segments. At runtime, the correct variant is served based on `ctx.isIntercept`. `when` config conditions are skipped at build time (all intercepts are pre-rendered unconditionally).
|
|
312
|
+
| DSL item | Behavior with Prerender |
|
|
313
|
+
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
314
|
+
| `loader()` | Live at runtime, bundled normally. Use `cache()` for caching. |
|
|
315
|
+
| `revalidate()` | Without Passthrough: re-runs no prerendered handler (segments are re-served from the build artifact); when the params change, a held parent layout whose `revalidate()` asks for it gets its baked copy. Loader revalidation still works. With Passthrough: re-renders through the live handler. |
|
|
316
|
+
| `cache()` | Orthogonal -- prerendered segments bypass the runtime cache. Use `cache()` on parent layouts and loaders. |
|
|
317
|
+
| `layout()` | Child layouts inside path are pre-rendered. Parent layouts are pre-rendered too: every hit, intercept variant included, serves them from the build artifact and runs no layout handler. On a navigation a parent layout the client holds stays held, even when the params change, as on a live route. |
|
|
318
|
+
| `parallel()` | Parallel slots inside path are pre-rendered. A parent layout's slots are pre-rendered too: a hit serves them from the build artifact with the layout, and one the client holds stays held, as on a live route. |
|
|
319
|
+
| `middleware()` | Skipped while collecting build-time Flight payloads (no request). For `Prerender` + `ppr`, the build-shell step replays global and route middleware with `ctx.build === true`; `ctx.dynamic()` skips that shell. At request time middleware runs normally on every request. |
|
|
320
|
+
| `loading()` | Ignored without Passthrough. Works for live fallback with Passthrough. |
|
|
321
|
+
| `intercept()` | Pre-rendered at build time. Intercept variant stored under `/i` key alongside the main entry, without the route's own segments. At runtime, the correct variant is served based on `ctx.isIntercept`. `when` config conditions are skipped at build time (all intercepts are pre-rendered unconditionally); a navigation serves only the intercept it selected. |
|
|
322
322
|
|
|
323
323
|
When Passthrough revalidation is enabled, remember that revalidation is
|
|
324
324
|
still partial: opting a child segment into revalidation does not
|
|
@@ -1426,19 +1426,24 @@ export const urlpatterns = urls(({ path, include }) => [
|
|
|
1426
1426
|
## Interaction with intercept()
|
|
1427
1427
|
|
|
1428
1428
|
When a pre-rendered route is also the target of an `intercept()`, the build system
|
|
1429
|
-
resolves the intercept handler at build time and stores
|
|
1430
|
-
|
|
1429
|
+
resolves the intercept handler at build time and stores an intercept variant
|
|
1430
|
+
under an `/i`-suffixed key alongside the main entry. The variant holds what an
|
|
1431
|
+
intercept navigation renders: the main segments without the route's own (its
|
|
1432
|
+
handler, layouts and slots, which a render under an intercept skips), then the
|
|
1433
|
+
intercept segments, with the handle values of exactly those segments:
|
|
1431
1434
|
|
|
1432
1435
|
```
|
|
1433
1436
|
prerender store keys:
|
|
1434
1437
|
"blog.post/a1b2c3" -> main segments (full page)
|
|
1435
|
-
"blog.post/a1b2c3/i" -> main segments + intercept segments
|
|
1438
|
+
"blog.post/a1b2c3/i" -> main segments without the route's own + intercept segments
|
|
1436
1439
|
```
|
|
1437
1440
|
|
|
1438
1441
|
At runtime, the cache-lookup middleware checks `ctx.isIntercept`:
|
|
1439
1442
|
|
|
1440
1443
|
- **Intercept navigation**: looks up `paramHash/i` first. If found, yields
|
|
1441
|
-
the
|
|
1444
|
+
the variant's segments with only the slot of the intercept the navigation
|
|
1445
|
+
selected (`selectInterceptVariant`); a variant without that slot is a miss.
|
|
1446
|
+
`handleCacheHitIntercept()` extracts intercept segments
|
|
1442
1447
|
(filtered by `namespace?.startsWith("intercept:")`) and sets up slots.
|
|
1443
1448
|
- **Direct navigation**: looks up `paramHash` (no suffix). Standard prerender path.
|
|
1444
1449
|
- **Intercept miss (no `/i` entry)**: falls through to the normal pipeline so
|
|
@@ -1447,7 +1452,26 @@ At runtime, the cache-lookup middleware checks `ctx.isIntercept`:
|
|
|
1447
1452
|
|
|
1448
1453
|
The `when` config selector receives an `InterceptSelectorContext` whose `from`
|
|
1449
1454
|
location (the page being left) is unknown at build time. All intercepts are pre-rendered unconditionally;
|
|
1450
|
-
`when` is evaluated at runtime by the intercept-resolution middleware
|
|
1455
|
+
`when` is evaluated at runtime by the intercept-resolution middleware, and a
|
|
1456
|
+
navigation shows only the intercept it selected: the innermost whose `when`
|
|
1457
|
+
passes, as on a route that is not pre-rendered. Two intercepts on one layout
|
|
1458
|
+
that fill the same slot are not pre-rendered: the variant could not tell them
|
|
1459
|
+
apart, so they render live. Intercepts declared in the route's own layouts are
|
|
1460
|
+
never selected and are not pre-rendered.
|
|
1461
|
+
|
|
1462
|
+
The build warns once per route and slot for an intercept a navigation can
|
|
1463
|
+
select that is not pre-rendered: two intercepts that fill one slot, a handler
|
|
1464
|
+
or layout that returned a `Response` at build time, or an intercept that
|
|
1465
|
+
targets the route by its name inside an `include()` (`"item"` for the route
|
|
1466
|
+
`"shop.item"`, or for a route named `"item"` in an unnamed `include()`), which
|
|
1467
|
+
only the local-name fallback selects (name the `include()` and target the
|
|
1468
|
+
full name, or declare the intercept inside it with `".item"`, to pre-render
|
|
1469
|
+
it). That intercept renders live, and its handler runs per request without
|
|
1470
|
+
`BuildContext`; a route in an unnamed `include()` is named by its URL:
|
|
1471
|
+
|
|
1472
|
+
```
|
|
1473
|
+
[rango] Prerender route "item": intercept @modal is not pre-rendered: two intercepts on one layout fill @modal. A navigation that selects it renders it live, and its handler runs per request without BuildContext. To pre-render it, give each intercept its own slot.
|
|
1474
|
+
```
|
|
1451
1475
|
|
|
1452
1476
|
### Example: Pre-rendered route with intercept
|
|
1453
1477
|
|
package/skills/rango/SKILL.md
CHANGED
|
@@ -87,10 +87,12 @@ are immune to partial-revalidation staleness by construction.
|
|
|
87
87
|
the entry's children too — a hard `false` on one side of a
|
|
88
88
|
producer/consumer pair desyncs it.
|
|
89
89
|
4. **Cross-entry sharing** — an outer `layout()` entry feeding descendants.
|
|
90
|
-
Outer entries do NOT revalidate on actions
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
Outer entries do NOT revalidate on actions, or on Back/Forward onto a page
|
|
91
|
+
an action made stale, by default (the revalidation trace calls this
|
|
92
|
+
`action:parent-chain-skip` / `stale:parent-chain-skip`), so this rung always
|
|
93
|
+
requires a shared revalidation contract: the same named `revalidate()`
|
|
94
|
+
function on the producer and every consumer, returning `true` on `stale`
|
|
95
|
+
as well as on its actions. See `/layout` → "Revalidation Contracts".
|
|
94
96
|
Before writing one, check whether the producer can move down a rung.
|
|
95
97
|
|
|
96
98
|
The failure mode this ladder prevents: a consumer re-runs, its producer
|
|
@@ -247,9 +249,15 @@ identity".
|
|
|
247
249
|
The predicate arg carries the action's full context, not just its identity. Match
|
|
248
250
|
_which_ action with `ctx.isAction(addToCart)` (rename-safe); branch on _what it
|
|
249
251
|
returned_ with `ctx.actionResult` — the value your `"use server"` function
|
|
250
|
-
returned, for outcome-conditional revalidation.
|
|
252
|
+
returned, for outcome-conditional revalidation. When the action threw into an
|
|
253
|
+
`errorBoundary()`, a predicate above the boundary still runs, with
|
|
254
|
+
`actionResult` set to the thrown value: check a field's shape before reading
|
|
255
|
+
it (`Array.isArray(actionResult?.items)`). A `redirect()` it throws then redirects, as
|
|
256
|
+
after a success. The arg also exposes `actionId`
|
|
251
257
|
(raw `path#export`), `actionUrl`, `formData` (form-based actions only), `method`,
|
|
252
|
-
and `stale` (the `_rsc_stale` signal that an action ran in this or another tab
|
|
258
|
+
and `stale` (the `_rsc_stale` signal that an action ran in this or another tab;
|
|
259
|
+
that Back/Forward revalidation takes an action's defaults, but `isAction()` is
|
|
260
|
+
false on it).
|
|
253
261
|
`actionId`, `actionUrl`, `actionResult`, and `formData` are `undefined` on plain
|
|
254
262
|
navigation; `method` is `"GET"` there and `"POST"` for an action. Call
|
|
255
263
|
`ctx.isAction()` with no arguments to ask "was this any action?".
|
|
@@ -258,9 +266,10 @@ navigation; `method` is `"GET"` there and `"POST"` for an action. Call
|
|
|
258
266
|
default already does: after an action, loaders, the route, and segments inside
|
|
259
267
|
the `path()` re-run; layouts and parallels above the route are skipped.
|
|
260
268
|
|
|
261
|
-
- **Add a signal** where the default skips:
|
|
262
|
-
|
|
263
|
-
|
|
269
|
+
- **Add a signal** where the default skips:
|
|
270
|
+
`ctx.isAction(CartActions) || ctx.stale || undefined` on a parent layout
|
|
271
|
+
re-renders it after cart actions and on Back after an action, and defers
|
|
272
|
+
otherwise. On a loader or route segment it changes nothing.
|
|
264
273
|
- **Narrow after actions** where the default re-runs:
|
|
265
274
|
`ctx.isAction() ? ctx.isAction(CartActions) : undefined` re-runs only for cart
|
|
266
275
|
actions and keeps the navigation default (params or search changed).
|
|
@@ -270,8 +279,11 @@ the `path()` re-run; layouts and parallels above the route are skipped.
|
|
|
270
279
|
|
|
271
280
|
```ts
|
|
272
281
|
// on a parent layout (skipped after actions by default): also re-render when
|
|
273
|
-
// checkout succeeded; defer to the default otherwise
|
|
274
|
-
revalidate(
|
|
282
|
+
// checkout succeeded, and on Back after an action; defer to the default otherwise
|
|
283
|
+
revalidate(
|
|
284
|
+
(ctx) =>
|
|
285
|
+
(ctx.isAction(checkout) && ctx.actionResult?.ok) || ctx.stale || undefined,
|
|
286
|
+
),
|
|
275
287
|
```
|
|
276
288
|
|
|
277
289
|
The same `actionResult` idiom works in a `clientUrls()` group's loader
|
|
@@ -428,8 +428,9 @@ several actions, or a namespace import (`import * as CartActions`). Wrap it by
|
|
|
428
428
|
what the segment's default already does:
|
|
429
429
|
|
|
430
430
|
- **Add a signal** where the default skips (a parent layout or layout-level
|
|
431
|
-
parallel): `ctx.isAction(CartActions) || undefined` re-renders it
|
|
432
|
-
actions and
|
|
431
|
+
parallel): `ctx.isAction(CartActions) || ctx.stale || undefined` re-renders it
|
|
432
|
+
after cart actions and on Back after an action, and defers otherwise. On a
|
|
433
|
+
loader or route segment it changes nothing.
|
|
433
434
|
- **Narrow after actions** where the default re-runs (loaders, the route, and
|
|
434
435
|
segments inside the `path()`):
|
|
435
436
|
`ctx.isAction() ? ctx.isAction(CartActions) : undefined` re-runs only for cart
|
|
@@ -508,8 +509,10 @@ consumer:
|
|
|
508
509
|
import type { Revalidate } from "@rangojs/router";
|
|
509
510
|
import * as CartActions from "./actions/cart";
|
|
510
511
|
|
|
512
|
+
// True on a stale Back/Forward too: it re-runs CartPage, and a producer that
|
|
513
|
+
// sits it out leaves the page reading undefined.
|
|
511
514
|
const revalidateCart: Revalidate = (ctx) =>
|
|
512
|
-
ctx.isAction(CartActions) || undefined;
|
|
515
|
+
ctx.stale || ctx.isAction(CartActions) || undefined;
|
|
513
516
|
|
|
514
517
|
urls(({ path, layout, loader, revalidate }) => [
|
|
515
518
|
layout(CartLayout, () => [
|
|
@@ -534,6 +537,41 @@ revalidates), flushes the prefetch cache, rotates the router's state cookie,
|
|
|
534
537
|
and tells other open tabs to do the same. This happens for every action that
|
|
535
538
|
reached the server, including ones that threw. No setup is needed.
|
|
536
539
|
|
|
540
|
+
### When the user navigates while the action runs
|
|
541
|
+
|
|
542
|
+
Users rarely wait for an action. They click "Add to cart" and then a link, or
|
|
543
|
+
go Back. That navigation is an ordinary one: a prefetched link shows its page at
|
|
544
|
+
once, as it would with no action in flight. That page may show data from before
|
|
545
|
+
the write, such as a layout's cart count the navigation kept. So once the last
|
|
546
|
+
action in flight settles, the router revalidates the page on screen once, in
|
|
547
|
+
the background: every loader on it re-runs, and the result commits with no
|
|
548
|
+
`loading()` fallback and no remount. `useNavigation().state` stays `"idle"`. This covers an action that failed too.
|
|
549
|
+
|
|
550
|
+
```text
|
|
551
|
+
/product/1: click "Add to cart", then a link to /checkout
|
|
552
|
+
/checkout shows at once (layout: cart:0)
|
|
553
|
+
the action settles
|
|
554
|
+
one background revalidation (layout: cart:1)
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
A few details:
|
|
558
|
+
|
|
559
|
+
- If the user stays on the page, nothing extra is sent. The action's own
|
|
560
|
+
response already re-ran that page's loaders.
|
|
561
|
+
- The revalidation waits for a navigation the user started and never cancels
|
|
562
|
+
it. Several actions in flight share one revalidation.
|
|
563
|
+
- A `redirect()` from the action replaces it: the redirect target renders
|
|
564
|
+
fresh.
|
|
565
|
+
- If an `intercept()` is on screen, nothing runs. Leaving the intercept with
|
|
566
|
+
Back restores the page underneath and revalidates it as stale (the
|
|
567
|
+
action marked the history cache stale). Only loaders that opt in with
|
|
568
|
+
`revalidate(({ stale }) => stale)` re-run there; the others keep their data
|
|
569
|
+
until a request re-runs them.
|
|
570
|
+
- It holds no segments, so every loader on the page re-runs whatever its
|
|
571
|
+
`revalidate()` says, and the document cache never answers it.
|
|
572
|
+
- Going Back or Forward out of an intercept does not abort an action sent from
|
|
573
|
+
the intercept. `useAction()` and your `await` get its real result.
|
|
574
|
+
|
|
537
575
|
### `keepClientCache()` — skip it for no-op actions
|
|
538
576
|
|
|
539
577
|
Some hot actions change nothing a route renders: a debounced draft autosave, an
|
|
@@ -555,8 +593,8 @@ export async function saveDraft(formData: FormData) {
|
|
|
555
593
|
|
|
556
594
|
What it suppresses, for this action only: the stale-marking of the history
|
|
557
595
|
cache, the prefetch flush, the state-cookie rotation, the cross-tab broadcast,
|
|
558
|
-
and the background
|
|
559
|
-
the action was in flight. What it does not change:
|
|
596
|
+
and the background revalidation the client runs when the user navigated while
|
|
597
|
+
the action was in flight (above). What it does not change:
|
|
560
598
|
|
|
561
599
|
- The action's own response still carries the revalidation render your
|
|
562
600
|
`revalidate()` rules select, and the client applies it.
|
|
@@ -666,7 +704,14 @@ is usually too aggressive.
|
|
|
666
704
|
Throw for genuinely exceptional conditions (network failure, DB outage,
|
|
667
705
|
auth violation). The nearest `errorBoundary()` above the page the action was
|
|
668
706
|
called from catches them, and the boundary renders inside route middleware like
|
|
669
|
-
a normal render.
|
|
707
|
+
a normal render. Above the boundary the response is the action's revalidation,
|
|
708
|
+
as after a success: the layouts' loaders re-run as their `revalidate()`
|
|
709
|
+
decides, so a cart count shows what the action wrote before it threw. A
|
|
710
|
+
route's `errorBoundary()` replaces the route: its handler and loaders do not
|
|
711
|
+
run. A layout's `errorBoundary()` replaces what the layout renders in its
|
|
712
|
+
outlet: the layout's own loaders, slots and orphan layouts revalidate as on a
|
|
713
|
+
success, and nothing inside the outlet runs. An action sent from an intercept
|
|
714
|
+
gets the boundary alone.
|
|
670
715
|
|
|
671
716
|
```typescript
|
|
672
717
|
// urls.tsx — inside the urls() callback
|
|
@@ -80,7 +80,7 @@ Defaults model a same-URL navigation with no action, for a route-owned loader: a
|
|
|
80
80
|
| `method` | `string` | Overrides the request method (`POST` with an `action`, otherwise `GET`). |
|
|
81
81
|
| `segmentType` | `"layout" \| "route" \| "parallel" \| "loader"` | The segment type the predicate is attached to. Defaults to `"loader"`. |
|
|
82
82
|
| `belongsToRoute` | `boolean` | Defaults to `true`; `false` models a layout's loader, which a navigation keeps by default. |
|
|
83
|
-
| `stale` | `boolean` | The `stale` arg (a history-entry background revalidation). Defaults to `false`.
|
|
83
|
+
| `stale` | `boolean` | The `stale` arg (a history-entry background revalidation), which takes an action's defaults. Defaults to `false`. |
|
|
84
84
|
| `routeName` | `string` | `toRouteName` (and `routeName`). |
|
|
85
85
|
| `fromRouteName` | `string` | `fromRouteName`. |
|
|
86
86
|
| `env` | `TEnv` | Environment bindings of the seeded context. |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Layer:** integration (node) · **Import:** `@rangojs/router/testing` · **DSL it tests:** response routes (json/text/html/xml/md), redirects, 404 (see `/response-routes`, `/mime-routes`)
|
|
4
4
|
|
|
5
|
-
`dispatch` runs the
|
|
5
|
+
`dispatch` runs the production handler's REAL request stages — classify (matching + content negotiation), the terminal plans (reloads, the rewritten-action rule), and the real global + route-level middleware chain — with no RSC render, so redirects, 404s, reloads, response routes, content negotiation, and middleware short-circuits behave exactly as in production. You SEED the request and `env`; everything else (matching, middleware, header/cookie merge) is real machinery.
|
|
6
6
|
|
|
7
7
|
Matching goes through the route trie, as every request in `vite dev` and production does.
|
|
8
8
|
|
|
@@ -83,8 +83,9 @@ describe("api routes via dispatch", () => {
|
|
|
83
83
|
- trailing-slash redirects from the trie — a route with `trailingSlash: "never"` or `"always"` answers the other form with a 308 and the `Location` (query preserved). A route with no `trailingSlash` serves its URL with and without a trailing slash, with no redirect, as production does;
|
|
84
84
|
- the open-redirect guard on browser-followed redirects (a cross-origin `Location` is rewritten to the basename root unless `redirect(url, { external: true })`);
|
|
85
85
|
- content-negotiated routes (`Vary: Accept` appended);
|
|
86
|
+
- the reloads production answers inside the global middleware chain: a request whose `_rsc_v` names another deploy's document version (`setBuildVersions`, `./serve-shell-request.md`), and a `_rsc_partial` request whose `_rsc_rid` names another router (a cross-app navigation), get an `X-RSC-Reload` at the visitor's URL — on any route, a component route or an unmatched path included;
|
|
86
87
|
- cached response routes (`cache({ ... })` around a `path.json` etc.): hit, SWR, and tag writes. The cache write is scheduled via `waitUntil` (a microtask in tests), so flush microtasks between the seeding `dispatch` and the one that asserts the hit;
|
|
87
|
-
- `createRouter({ telemetry })` `request.start` / `request.end` / `request.error` events, and `createRouter({ onError })` for cache-read/write failures.
|
|
88
|
+
- `createRouter({ telemetry })` `request.start` / `request.end` / `request.error` events, all with one `requestId`, which a response route's `request.timeout` carries too (a rewritten pass's included), and `createRouter({ onError })` for cache-read/write failures.
|
|
88
89
|
|
|
89
90
|
## Caveats
|
|
90
91
|
|
|
@@ -94,9 +95,10 @@ describe("api routes via dispatch", () => {
|
|
|
94
95
|
- Setup: needs the preset (alias + virtual stubs) or a Vite-RSC env (see `./setup.md`); a bare router import throws on Vite virtuals.
|
|
95
96
|
- Building the trie runs route discovery: every `include()`d `urls()` handler runs once more, and every async include provider (`() => import(...)`) is called. Count provider calls or handler runs after the first `dispatch` on that router, not from router construction. A provider that rejects fails the `dispatch`, as it fails `vite dev` and the build.
|
|
96
97
|
- A router using `Prerender()`/`createLoader()`/`Static()` now constructs in a bare test (each assigns a runtime fallback `$$id`). Importing the whole router _file_ may still need the plugin (its page modules pull app deps / `virtual:` modules) — build from a focused include (your API routes) for whole-router dispatch.
|
|
97
|
-
- A `throw rewrite(target)` from global or route middleware re-dispatches as the router does: global middleware matched on the target's path, the target's route middleware and handler, the same request context, `x-rango-rewrite` on the response, 508 on a loop. The visitor's URL is matched before middleware runs, so it must be a response route (or a redirect or 404) too; a rewrite target that is a component route throws the same error as a direct request.
|
|
98
|
+
- A `throw rewrite(target)` from global or route middleware re-dispatches as the router does: global middleware matched on the target's path, the target's route middleware and handler, the same request context, `x-rango-rewrite` on the response, 508 on a loop. The visitor's URL is matched before middleware runs, so it must be a response route (or a redirect or 404) too; a rewrite target that is a component route throws the same error as a direct request. An action request (`?_rsc_action`) a rewrite moves to a response route, a redirect or an unmatched path rejects with production's error, `A rewrite of a server action or form post must target a page route; …`.
|
|
99
|
+
- The route's params and name are set inside the global middleware chain, as production sets them: a global middleware reads `ctx.routeName` (and `getRequestContext().params`) after `await next()`, not before it. A trailing-slash redirect sets none.
|
|
98
100
|
- A `_rsc_partial` request to a response route runs global middleware first (an auth gate can still 401/redirect), then returns `X-RSC-Reload` — route-level middleware is skipped, exactly like production.
|
|
99
|
-
- `dispatch` does NOT execute server actions (`?_rsc_action`), but it DOES run the global middleware chain on an action request — middleware can still 401/redirect it,
|
|
101
|
+
- `dispatch` does NOT execute server actions (`?_rsc_action`), but it DOES run the global middleware chain on an action request — middleware can still 401/redirect it. When a global middleware matches the path, any 3xx that leaves the chain on a partial OR action request (a global or route middleware's redirect, a response route's, a trailing-slash redirect) becomes a `204` + `X-RSC-Redirect` (fetch-safe interception), the raw `Location` dropped. Without one, an action request's 3xx goes out raw (through the open-redirect guard), as production sends it; a trailing-slash redirect on a partial request is a `204` either way. Location state carried on such a redirect is dropped too (production embeds it in a Flight payload); cover that at e2e.
|
|
100
102
|
- `createRouter({ onError })` is NOT invoked for a thrown non-Response HANDLER error here (the typed error Response still matches production), and `cache.decision` / `loader.*` telemetry never fires (no RSC render runs). Cover both at e2e.
|
|
101
103
|
|
|
102
104
|
## See also
|
|
@@ -20,13 +20,13 @@ A `Prerender` route is served as in production: from the artifact `router.matchF
|
|
|
20
20
|
|
|
21
21
|
### Options — `ServeShellRequestOptions`
|
|
22
22
|
|
|
23
|
-
| Field | Type
|
|
24
|
-
| ------------ |
|
|
25
|
-
| `cacheStore` | `SegmentCacheStore`
|
|
26
|
-
| `env` | `TEnv`
|
|
27
|
-
| `headers` | `HeadersInit`
|
|
28
|
-
| `partial` | `true \| { from?: string; rewriteTarget?: string; segments?: string[]; prefetch?: boolean; fill?: boolean; interceptSource?: string }` | Serve the navigation request the browser sends instead of a document GET: from `from` (default the site root) with `segments` mounted (default none). `prefetch: true` sends it as a `<Link>` prefetch (`X-Rango-Prefetch`), so `prefetch: false` work is deferred. `fill: true` sends the follow-up request (`_rsc_fill=1`) the browser makes after adopting a prefetch; pass the prefetch's delivered ids, without the deferred ones, as `segments`, and the same URL as `from`. `interceptSource` sends the intercept source (`X-RSC-Router-Intercept-Source`) the browser sends with the fill of an intercept, whose `from` is that source too. `rewriteTarget` sends what the page at `from` carried as its payload's `metadata.rewriteTarget` when middleware rewrote it (`_rsc_rt`), as the browser does, so the server diffs against the route that rendered that page. |
|
|
29
|
-
| `action` | `{ ref: Function; id: string; args?: unknown[]; segments?: string[]; rewriteTarget?: string }`
|
|
23
|
+
| Field | Type | Meaning |
|
|
24
|
+
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `cacheStore` | `SegmentCacheStore` | The store for this request, in place of the store your `createRouter({ cache })` config returns. The rest of that config (`enabled`, `searchParams`) still applies. Omit it to use the router's own store. |
|
|
26
|
+
| `env` | `TEnv` | Bindings, as `router.fetch(request, { env })` passes them. A function `cache` config receives them. |
|
|
27
|
+
| `headers` | `HeadersInit` | Request headers (e.g. `cookie`). A document request defaults `accept` to `text/html`. |
|
|
28
|
+
| `partial` | `true \| { from?: string; rewriteTarget?: string; segments?: string[]; prefetch?: boolean; fill?: boolean; interceptSource?: string; stale?: boolean }` | Serve the navigation request the browser sends instead of a document GET: from `from` (default the site root) with `segments` mounted (default none). `prefetch: true` sends it as a `<Link>` prefetch (`X-Rango-Prefetch`), so `prefetch: false` work is deferred. `fill: true` sends the follow-up request (`_rsc_fill=1`) the browser makes after adopting a prefetch; pass the prefetch's delivered ids, without the deferred ones, as `segments`, and the same URL as `from`. `interceptSource` sends the intercept source (`X-RSC-Router-Intercept-Source`) the browser sends with the fill of an intercept, whose `from` is that source too. `rewriteTarget` sends what the page at `from` carried as its payload's `metadata.rewriteTarget` when middleware rewrote it (`_rsc_rt`), as the browser does, so the server diffs against the route that rendered that page. `stale: true` sends a stale request (`_rsc_stale`), as the browser sends it: every `revalidate()` sees `stale: true` (see `revalidate()`'s `stale`), segments take an action's revalidation defaults, and it renders live, as an action does: neither the route cache (`cache()`) nor the document cache serves or stores it, and a `ppr` route's `replayStatus` is `{ outcome: "BYPASS", reason: "stale" }`. |
|
|
29
|
+
| `action` | `{ ref: Function; id: string; args?: unknown[]; segments?: string[]; rewriteTarget?: string }` | Send the request a JS server action call makes from the page at `url`: a POST of `args` (encoded as the browser encodes them) with `?_rsc_action`, the `rsc-action` header, `X-RSC-Router-Client-Path` and `_rsc_segments` (the page's mounted segment ids, default none), plus the page's `rewriteTarget` as for `partial`. Registers `ref` under `id` (`<module id>#<export name>`). Not with `partial` or `form`. |
|
|
30
30
|
|
|
31
31
|
### Returns — `ServeShellRequestResult`
|
|
32
32
|
|
|
@@ -68,10 +68,17 @@ export async function ProductCard({ id }: { id: string }) {
|
|
|
68
68
|
A result Flight cannot encode cleanly is not cached: an async child component
|
|
69
69
|
that throws, a promise in the result or in a handle push that rejects, a
|
|
70
70
|
function or a class instance. Storing it would bake the failure into every hit
|
|
71
|
-
until expiry. Instead the write is skipped and reported to `onError`
|
|
72
|
-
`"cache"`, category `cache-write`, or `stale-revalidation` for a
|
|
73
|
-
refresh):
|
|
74
|
-
|
|
71
|
+
until expiry. Instead the write is skipped, logged and reported to `onError`
|
|
72
|
+
(phase `"cache"`, category `cache-write`, or `stale-revalidation` for a
|
|
73
|
+
background refresh): the next call runs the function again, and a stale entry
|
|
74
|
+
keeps serving.
|
|
75
|
+
|
|
76
|
+
A miss renders a decoded copy of what it encodes, as a hit renders the stored
|
|
77
|
+
entry, so a child that throws or a promise that rejects throws where the copy
|
|
78
|
+
renders, as it would on a hit. A value Flight cannot encode at all (a
|
|
79
|
+
function, a class instance) in the data the function returns is the
|
|
80
|
+
exception: the call returns the live value, uncached. One inside a promise in
|
|
81
|
+
that data rejects that promise instead, as it would on a hit.
|
|
75
82
|
|
|
76
83
|
## Named Cache Profiles
|
|
77
84
|
|
|
@@ -233,6 +240,35 @@ export const ProductLoader = createLoader(async (ctx) => getProduct(ctx));
|
|
|
233
240
|
per-request values (cookies, headers) are not reflected in the cache key. Without
|
|
234
241
|
this guard, one user's data would be served to another.
|
|
235
242
|
|
|
243
|
+
That includes the server components the function returns. The value is
|
|
244
|
+
encoded once, inside the same guard, and the page renders the decoded copy, as
|
|
245
|
+
on a hit, so the guard refuses there every read and write it refuses in the
|
|
246
|
+
body, and a refused read fails the request as it would in the body: at the
|
|
247
|
+
value's root the call throws, and below the root that part renders as an
|
|
248
|
+
error, to its nearest error boundary. Nothing is stored. The same holds for a
|
|
249
|
+
`ctx.use(Loader)` in the returned tree (the loader runs inside the guard, and
|
|
250
|
+
the request's memo of it rejects, so a later read of it in the request throws
|
|
251
|
+
too) and for a `React.lazy` or a memoizing thenable created in the body.
|
|
252
|
+
|
|
253
|
+
A handle value the function pushes (`ctx.use(Handle)`) is encoded inside the
|
|
254
|
+
guard as well, and one that reads the request is not stored. One handle case
|
|
255
|
+
is not covered yet: a handle value that holds a lazy component or a memoized
|
|
256
|
+
promise. Don't push lazy state that reads the request from a cached function.
|
|
257
|
+
|
|
258
|
+
Two memo gaps remain, where the read happened before the guard could see it:
|
|
259
|
+
|
|
260
|
+
- In the body: a `React.cache`-memoized read of `cookies()` or `headers()` (a
|
|
261
|
+
`getSession = cache(() => cookies()...)` auth helper) that the page called
|
|
262
|
+
before the cached function runs returns the memo without reading again, so
|
|
263
|
+
the guard never sees it and the entry stores that visitor's value.
|
|
264
|
+
- In the body or the returned tree: a per-request memo of your own, keyed by
|
|
265
|
+
`getRequestContext()`, that the page filled and the cached function or a
|
|
266
|
+
component it returns reads. `React.cache` itself is safe in the returned
|
|
267
|
+
tree, which renders in its own Flight request.
|
|
268
|
+
|
|
269
|
+
Pass the value in as an argument instead of reading a memo inside the cached
|
|
270
|
+
function or the tree it returns.
|
|
271
|
+
|
|
236
272
|
The raw reads throw the same way: `ctx.request.headers` on any `ctx` passed in
|
|
237
273
|
(handler, loader, middleware; it is one `Request` object, also
|
|
238
274
|
`getRequestContext().request`), and `getRequestContext().cookie()` /
|
|
@@ -397,9 +433,11 @@ export const Page = Prerender(
|
|
|
397
433
|
|
|
398
434
|
## Performance: waitUntil
|
|
399
435
|
|
|
400
|
-
On cache miss, the function executes and
|
|
401
|
-
|
|
402
|
-
the
|
|
436
|
+
On cache miss, the function executes and its result is encoded; the call
|
|
437
|
+
returns once the decoded copy's root has arrived, so a server component at the
|
|
438
|
+
root of the value renders before the call resolves. The rest of the encode and
|
|
439
|
+
the cache **store write** (`setItem`) are deferred to `waitUntil` and do NOT
|
|
440
|
+
block the response.
|
|
403
441
|
|
|
404
442
|
On stale hit, stale data is returned immediately. Background revalidation
|
|
405
443
|
(re-execute + store write) runs entirely inside `waitUntil`.
|
|
@@ -407,7 +445,7 @@ On stale hit, stale data is returned immediately. Background revalidation
|
|
|
407
445
|
| Phase | Blocks response? |
|
|
408
446
|
| ------------------------------------ | ---------------- |
|
|
409
447
|
| Function execution (miss) | Yes |
|
|
410
|
-
| Result
|
|
448
|
+
| Result encode, to its root (miss) | Yes |
|
|
411
449
|
| Cache store write (miss) | No (waitUntil) |
|
|
412
450
|
| Stale value return (stale hit) | No (immediate) |
|
|
413
451
|
| Background revalidation (stale) | No (waitUntil) |
|
|
@@ -228,8 +228,9 @@ them right:
|
|
|
228
228
|
|
|
229
229
|
- A boolean is a hard answer, `undefined` defers to the default. Pick by the
|
|
230
230
|
default you are changing: the ternary narrows a loader that re-runs after
|
|
231
|
-
every action; `isAction(X) || undefined` adds a re-run
|
|
232
|
-
skips (a server layout's own
|
|
231
|
+
every action; `isAction(X) || stale || undefined` adds a re-run, after `X`
|
|
232
|
+
and on Back after an action, where the default skips (a server layout's own
|
|
233
|
+
handler).
|
|
233
234
|
- `revalidate(true)` and `revalidate(false)` are shorthands for `() => true`
|
|
234
235
|
and `() => false`.
|
|
235
236
|
- Never `revalidate(false)` on data keyed by a param: product to product then
|
package/src/__internal.ts
CHANGED
|
@@ -85,10 +85,16 @@ export type {
|
|
|
85
85
|
*/
|
|
86
86
|
export type {
|
|
87
87
|
RouterContext,
|
|
88
|
-
RevalidationContext,
|
|
89
88
|
InterceptResult,
|
|
90
89
|
} from "./router/router-context.js";
|
|
91
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @internal
|
|
93
|
+
* The request side of a revalidating resolution pass (RouterContext's
|
|
94
|
+
* revalidating resolvers take it).
|
|
95
|
+
*/
|
|
96
|
+
export type { ResolutionPassInput } from "./router/segment-resolution/resolution-pass.js";
|
|
97
|
+
|
|
92
98
|
/**
|
|
93
99
|
* @internal
|
|
94
100
|
* Route match context during pipeline processing.
|
|
@@ -6,11 +6,7 @@ import type { ActionEntry } from "./event-controller.js";
|
|
|
6
6
|
* reconciliation; this only covers successfully-reconciled partial responses.
|
|
7
7
|
*/
|
|
8
8
|
type ActionScenario =
|
|
9
|
-
| {
|
|
10
|
-
type: "navigated-away";
|
|
11
|
-
historyKeyChanged: boolean;
|
|
12
|
-
onInterceptRoute: boolean;
|
|
13
|
-
}
|
|
9
|
+
| { type: "navigated-away" }
|
|
14
10
|
| { type: "hmr-missing" }
|
|
15
11
|
| { type: "consolidation-needed"; segmentIds: string[] }
|
|
16
12
|
| { type: "concurrent-skip"; otherFetchingCount: number }
|
|
@@ -41,8 +37,6 @@ export interface ActionOutcomeInput {
|
|
|
41
37
|
reconciledSegmentCount: number;
|
|
42
38
|
/** Number of matched segment IDs from server */
|
|
43
39
|
matchedCount: number;
|
|
44
|
-
/** Current intercept source URL (null when not on intercept route) */
|
|
45
|
-
currentInterceptSource: string | null;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
// Segment IDs to re-fetch when concurrent actions each revalidated different
|
|
@@ -85,12 +79,7 @@ export function classifyActionOutcome(
|
|
|
85
79
|
input.currentPathname !== input.actionStartPathname ||
|
|
86
80
|
input.currentLocationKey !== input.actionStartLocationKey
|
|
87
81
|
) {
|
|
88
|
-
return {
|
|
89
|
-
type: "navigated-away",
|
|
90
|
-
historyKeyChanged:
|
|
91
|
-
input.currentLocationKey !== input.actionStartLocationKey,
|
|
92
|
-
onInterceptRoute: input.currentInterceptSource !== null,
|
|
93
|
-
};
|
|
82
|
+
return { type: "navigated-away" };
|
|
94
83
|
}
|
|
95
84
|
|
|
96
85
|
if (input.reconciledSegmentCount < input.matchedCount) {
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* start. While the fence is up, the decision of whether the action invalidated
|
|
7
7
|
* anything is deferred to the response:
|
|
8
8
|
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* - a navigation during the flight is an ordinary one: a prefetch is served,
|
|
10
|
+
* and the page it commits may hold pre-write data until the follow-up below,
|
|
11
|
+
* - a navigation without a prefetch, and a prefetch, fetch with
|
|
12
|
+
* `cache: "no-store"` so they cannot be served stale bytes from the
|
|
13
|
+
* browser's Vary-keyed HTTP cache,
|
|
12
14
|
* - popstate reads are treated as stale-while-revalidate.
|
|
13
15
|
*
|
|
14
16
|
* Nothing is wiped, rotated, or broadcast while the fence is up — that is what
|
|
@@ -16,6 +18,12 @@
|
|
|
16
18
|
* actions compose: each action raises and lowers its own reference, and the
|
|
17
19
|
* fence is down only when the count reaches zero.
|
|
18
20
|
*
|
|
21
|
+
* The drop runs the batch's follow-up: when an action of the batch did not
|
|
22
|
+
* land on the page on screen, the action bridge refetches that page once,
|
|
23
|
+
* whole, after the navigation the user started, unless an intercept is on
|
|
24
|
+
* screen (server-action-bridge.ts followUp; the rules are R1-R7 in
|
|
25
|
+
* docs/internal/execution-model.md).
|
|
26
|
+
*
|
|
19
27
|
* The refcount is a single module-level counter, not keyed by routerId.
|
|
20
28
|
* Consumers (navigation-client.ts, prefetch/fetch.ts, navigation-bridge.ts)
|
|
21
29
|
* read it unscoped, so an action in one router would suppress another router's
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* state regardless of listener ordering.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { WIRE } from "../wire-params.js";
|
|
21
|
+
|
|
20
22
|
const IDLE_TIMEOUT = 60_000;
|
|
21
23
|
const DEBOUNCE_DELAY = 150;
|
|
22
24
|
|
|
@@ -61,7 +63,7 @@ export function startConnectionWarmup(
|
|
|
61
63
|
|
|
62
64
|
function sendWarmup() {
|
|
63
65
|
coldLatch = false;
|
|
64
|
-
env.fetch(
|
|
66
|
+
env.fetch(`/?${WIRE.warmup}`, { method: "HEAD" }).catch(() => {});
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function triggerWarmup() {
|