@rangojs/router 0.21.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/rango.js +13 -1
- package/dist/types/browser/event-controller.d.ts +30 -13
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +14 -0
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +1 -1
- package/dist/types/cache/cf/cf-cache-store.d.ts +7 -1
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +23 -0
- package/dist/types/cache/index.d.ts +1 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +10 -1
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +63 -14
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +7 -1
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +7 -0
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +14 -0
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +30 -0
- package/dist/types/router/router-options.d.ts +24 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +2 -1
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +6 -4
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +8 -0
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +9 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +63 -2
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/flight.entry.d.ts +2 -0
- package/dist/types/testing/index.d.ts +4 -0
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/serve-shell-request.d.ts +34 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +7 -0
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/state.d.ts +11 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/vite/index.js +179 -496
- package/package.json +12 -2
- package/skills/cache-guide/SKILL.md +18 -17
- package/skills/caching/SKILL.md +40 -4
- package/skills/catalog.json +1 -1
- package/skills/cloudflare/SKILL.md +131 -0
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +22 -0
- package/skills/hooks/handle-and-actions.md +3 -4
- package/skills/hooks/navigation.md +10 -0
- package/skills/hooks/state.md +5 -4
- package/skills/host-router/SKILL.md +18 -0
- package/skills/loader/SKILL.md +6 -4
- package/skills/middleware/SKILL.md +1 -1
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +31 -8
- package/skills/prerender/SKILL.md +645 -8
- package/skills/rango/SKILL.md +1 -0
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/serve-shell-request.md +104 -8
- package/skills/use-cache/SKILL.md +9 -5
- package/skills/vercel/SKILL.md +2 -0
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/browser/event-controller.ts +69 -25
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +75 -35
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +9 -2
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +48 -12
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/react/Link.tsx +37 -42
- package/src/browser/react/NavigationProvider.tsx +63 -3
- package/src/browser/react/context.ts +17 -0
- package/src/browser/react/location-state.ts +5 -19
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +4 -20
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +79 -39
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +26 -6
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +38 -36
- package/src/cache/cf/cf-cache-store.ts +16 -11
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +50 -0
- package/src/cache/index.ts +1 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +17 -0
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +69 -14
- package/src/cache/vercel/vercel-cache-store.ts +20 -6
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +7 -2
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +230 -38
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +33 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +15 -17
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router.ts +107 -1
- package/src/rsc/handler.ts +51 -4
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +53 -11
- package/src/rsc/shell-build-manifest.ts +10 -8
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +108 -19
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +8 -0
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +15 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +13 -0
- package/src/server/request-context.ts +87 -1
- package/src/ssr/ssr-root.tsx +20 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +3 -0
- package/src/testing/index.ts +19 -0
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/render-route.tsx +25 -12
- package/src/testing/serve-shell-request.ts +190 -34
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +7 -0
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/shell-prerender-phase.ts +5 -5
- package/src/vite/discovery/state.ts +17 -1
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugins/virtual-entries.ts +8 -1
- package/src/vite/router-discovery.ts +14 -0
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/dist/types/browser/react/subscribe-to-nothing.d.ts +0 -7
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/browser/react/subscribe-to-nothing.ts +0 -7
- package/src/segment-content-promise.ts +0 -67
|
@@ -416,13 +416,82 @@ default) or serves an open-ended param space. See `/prerender` for full API.
|
|
|
416
416
|
|
|
417
417
|
Next.js route segment config maps onto Rango's explicit primitives:
|
|
418
418
|
|
|
419
|
-
| Next.js segment config | Rango
|
|
420
|
-
| --------------------------------------------------- |
|
|
421
|
-
| `dynamic = "force-static"` + `generateStaticParams` | `Static()` / `Prerender()` (see `/prerender`)
|
|
422
|
-
| `revalidate = 60` (ISR) | `cache({ ttl: 60, swr: ... })` on the route (see `/caching`) |
|
|
423
|
-
| `dynamic = "force-dynamic"` | the default — routes are dynamic unless you cache them
|
|
424
|
-
| `dynamicParams = true` | `Passthrough()` (above)
|
|
425
|
-
| `experimental_ppr = true` | the `ppr` path option (below, and `/ppr`)
|
|
419
|
+
| Next.js segment config | Rango |
|
|
420
|
+
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
421
|
+
| `dynamic = "force-static"` + `generateStaticParams` | `Static()` / `Prerender()` (see `/prerender`) |
|
|
422
|
+
| `revalidate = 60` (ISR) | `cache({ ttl: 60, swr: ... })` on the route (see `/caching`), or on-demand prerender with `onDemand: { ttl: 60 }` (below) |
|
|
423
|
+
| `dynamic = "force-dynamic"` | the default — routes are dynamic unless you cache them |
|
|
424
|
+
| `dynamicParams = true` | `Passthrough()` (above) |
|
|
425
|
+
| `experimental_ppr = true` | the `ppr` path option (below, and `/ppr`) |
|
|
426
|
+
|
|
427
|
+
### On-demand ISR → `Prerender(..., { onDemand })` + `router.prerender()`
|
|
428
|
+
|
|
429
|
+
A Next.js page with `generateStaticParams` refreshed by `revalidatePath()` or
|
|
430
|
+
`revalidateTag()` from a CMS webhook maps to an on-demand prerender route. The
|
|
431
|
+
route opts in, a store holds refreshed payloads, and the webhook re-renders the
|
|
432
|
+
page instead of invalidating it:
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
// Next.js:
|
|
436
|
+
export const revalidate = 3600;
|
|
437
|
+
export async function generateStaticParams() {
|
|
438
|
+
return (await getFeatured()).map((p) => ({ id: p.id }));
|
|
439
|
+
}
|
|
440
|
+
// app/api/revalidate/route.ts
|
|
441
|
+
revalidatePath(`/products/${id}`);
|
|
442
|
+
revalidateTag(`product:${id}`);
|
|
443
|
+
|
|
444
|
+
// Rango:
|
|
445
|
+
export const ProductPage = Prerender<{ id: string }>(
|
|
446
|
+
async () => (await getFeatured()).map((p) => ({ id: p.id })),
|
|
447
|
+
async (ctx) => <Product data={await getProduct(ctx.params.id)} />,
|
|
448
|
+
{ onDemand: { ttl: 3600, tags: ({ params }) => [`product:${params.id}`] } },
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
// router: createRouter({ prerender: { store, ttl, onRevalidate? } })
|
|
452
|
+
// webhook (route handler, cron or queue):
|
|
453
|
+
const prerender = router.prerender({ env, ctx });
|
|
454
|
+
await prerender(`/products/${id}`); // revalidatePath
|
|
455
|
+
await prerender.markStale([`product:${id}`]); // revalidateTag
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
| Next.js | Rango |
|
|
459
|
+
| ------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
460
|
+
| `revalidatePath(path)` on an ISR page | `await router.prerender({ env, ctx })(path)`: renders now, serves the new payload next request |
|
|
461
|
+
| `revalidateTag(tag)` on ISR pages | `router.prerender({ env, ctx }).markStale([tag])`: marks only; a stale hit schedules `onRevalidate` |
|
|
462
|
+
| `revalidate = N` (time-based ISR) | `onDemand: { ttl: N }` + `onRevalidate`; `ttl` is soft, entries never expire |
|
|
463
|
+
| `dynamicParams = false` | plain on-demand `Prerender`: an unknown param 404s until something refreshes it |
|
|
464
|
+
| `dynamicParams = true` | wrap it in `Passthrough()`: unknown params render live until refreshed |
|
|
465
|
+
|
|
466
|
+
A page whose item was deleted. In the Pages Router, `getStaticProps` returns
|
|
467
|
+
`{ notFound: true }` on a revalidation: "With `notFound: true`, the page will
|
|
468
|
+
return a `404` even if there was a successfully generated page before. This is
|
|
469
|
+
meant to support use cases like user-generated content getting removed by its
|
|
470
|
+
author. Note, `notFound` follows the same `revalidate` behavior described
|
|
471
|
+
here." In Rango a `notFound()` anywhere in the on-demand render (the route
|
|
472
|
+
handler, a layout or slot handler, any server component) does the same on the
|
|
473
|
+
next `prerender(path)`: the result is `{ ok: true, status: "removed" }`, the
|
|
474
|
+
page answers 404 instead of the page or its build-time entry, and the removal
|
|
475
|
+
is rechecked per the route's `ttl`, as Next's is per `revalidate`. When the
|
|
476
|
+
data source may be failing, throw anything else: that keeps the page.
|
|
477
|
+
`prerender.remove(path)` removes without rendering, for a webhook that already
|
|
478
|
+
knows the item is gone, and is permanent until a `prerender(path)` renders the
|
|
479
|
+
page again. See `/prerender` → "Remove a page".
|
|
480
|
+
|
|
481
|
+
Three differences to plan for:
|
|
482
|
+
|
|
483
|
+
- Prerender tags are their own namespace. `updateTag()` / `revalidateTag()`
|
|
484
|
+
from `@rangojs/router` purge the runtime cache and never reach on-demand
|
|
485
|
+
entries; `markStale()` never reaches the runtime cache.
|
|
486
|
+
- A refresh is requestless: `cookies()` / `headers()` in the page make it
|
|
487
|
+
return `skipped-personalized`, and `ctx.env` is the live env you pass.
|
|
488
|
+
- `markStale()` does not re-render: without `onRevalidate` or a cron calling
|
|
489
|
+
`prerender(target, { onlyIfStale: true })` (the runner from
|
|
490
|
+
`router.prerender({ env, ctx })`), a marked page keeps
|
|
491
|
+
serving its old payload.
|
|
492
|
+
|
|
493
|
+
See `/prerender` → "On-demand refresh (ISR)" for the store options, the serve
|
|
494
|
+
table and the rules.
|
|
426
495
|
|
|
427
496
|
### Partial prerendering → the `ppr` path option
|
|
428
497
|
|
|
@@ -552,7 +621,9 @@ cookie (`rango-state-fresh`); other users, and other locations on
|
|
|
552
621
|
propagates (`/caching` → "The fresh-reads cookie").
|
|
553
622
|
Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`, `VercelCacheStore`)
|
|
554
623
|
index by tag. Next's
|
|
555
|
-
`revalidatePath` has no path-based equivalent — tag the
|
|
624
|
+
`revalidatePath` has no path-based equivalent for runtime-cached data — tag the
|
|
625
|
+
relevant entries instead. For a prerendered ISR page it is
|
|
626
|
+
`router.prerender({ env, ctx })(path)` (see "On-demand ISR" above).
|
|
556
627
|
|
|
557
628
|
**2. Partial-render selection (which segments re-run after an action).** This is
|
|
558
629
|
NOT cache invalidation — it is `revalidate()`, controlling which segments
|
|
@@ -594,7 +665,9 @@ When migrating:
|
|
|
594
665
|
|
|
595
666
|
- `revalidateTag(tag)` → `await updateTag(tag)` (in a server action) or
|
|
596
667
|
`revalidateTag(tag)` (in a route handler / webhook). Effectively 1:1.
|
|
597
|
-
- `revalidatePath(path)`
|
|
668
|
+
- `revalidatePath(path)` on an ISR page → `router.prerender({ env, ctx })(path)`
|
|
669
|
+
(on-demand prerender, §3); on runtime-cached data, no path-based
|
|
670
|
+
equivalent; tag the entries on that
|
|
598
671
|
route (`cache({ tags })` / `cacheTag(...)`) and invalidate by tag.
|
|
599
672
|
- To also force specific segments to re-render after the action (independent of
|
|
600
673
|
cache busting), attach a `revalidate()` rule at those segment boundaries.
|
package/skills/ppr/SKILL.md
CHANGED
|
@@ -170,7 +170,7 @@ The partition follows nested scopes (issue #970): a `ppr` route under a
|
|
|
170
170
|
inner boundary has none, and by both composed (outermost first, joined by
|
|
171
171
|
`|`) when it has its own. Each `key()` result is namespaced as `key:` plus
|
|
172
172
|
its URI encoding (issue #975), exactly as in the record key, so the shell of
|
|
173
|
-
`key: () => "tier:gold"` is `<host><path>:shell|key%3Atier%253Agold`. The
|
|
173
|
+
`key: () => "tier:gold"` is `<router>@<host><path>:shell|key%3Atier%253Agold`. The
|
|
174
174
|
route's record adds the inner boundary's own default key when it has no
|
|
175
175
|
`key`; the shell partition does not, since the shell key already carries the
|
|
176
176
|
URL (a store `keyGenerator`'s result is kept when it adds anything, an
|
|
@@ -220,10 +220,10 @@ does not render from: otherwise the first variant captured is served for every
|
|
|
220
220
|
value of it.
|
|
221
221
|
|
|
222
222
|
In tests, `serveShellRequest` reports the key the serve path resolved
|
|
223
|
-
(`result.key`, partition included); `shellCacheKey(url, searchParams,
|
|
223
|
+
(`result.key`, partition included); `shellCacheKey(router, url, searchParams,
|
|
224
224
|
partition)` builds the same key from the `key()` result, namespacing it as
|
|
225
225
|
production does. Under nested keyed scopes, pass the `key()` results as an
|
|
226
|
-
array, outermost first (`shellCacheKey(url, undefined, ["tier:gold",
|
|
226
|
+
array, outermost first (`shellCacheKey(router, url, undefined, ["tier:gold",
|
|
227
227
|
"v:a"])`); for a store `keyGenerator` partition pass
|
|
228
228
|
`{ keys, generated }` (`/testing`, `cache-prerender.md`).
|
|
229
229
|
|
|
@@ -264,7 +264,7 @@ On a document GET to a ppr route the router runs:
|
|
|
264
264
|
DSL `middleware()`; both are guards, and the COMMIT POINT is after all of
|
|
265
265
|
them: any rejection/redirect/401 wins before a single shell byte, on MISS
|
|
266
266
|
and on a warmed HIT alike;
|
|
267
|
-
3. **shell lookup** — `getShell(key)` on the app store (key =
|
|
267
|
+
3. **shell lookup** — `getShell(key)` on the app store (key = router id +
|
|
268
268
|
host+pathname+sorted search). `CFCacheStore` stores the entry prelude-first
|
|
269
269
|
and the serve path reads only its head and prelude here, with the tag-marker
|
|
270
270
|
read running alongside; the capture snapshot is read off the commit path.
|
|
@@ -561,6 +561,19 @@ curl -s -D - -o /dev/null https://app.example.com/products/1 | grep -i x-rango-s
|
|
|
561
561
|
writes, and marker rejection, with `cf-ray`/colo and read timings. The flag is baked at
|
|
562
562
|
build time; setting only a Worker runtime variable is too late.
|
|
563
563
|
|
|
564
|
+
### Capturing a shell before the first visitor
|
|
565
|
+
|
|
566
|
+
A shell is captured by the first document MISS for its key, so the first
|
|
567
|
+
visitor after a deploy or an `updateTag()` renders without one. To capture it
|
|
568
|
+
ahead of them, warm the URL: `router.prerender({ env, ctx })(url)` renders it
|
|
569
|
+
through the handler, schedules the capture and waits for it, so the next
|
|
570
|
+
document request is a HIT. A warm replaces a stored shell in place (the old
|
|
571
|
+
one serves until the new one is written) and reports the capture's outcome
|
|
572
|
+
(`result.caches.shell`, with `refusal` when the capture refused). It is not
|
|
573
|
+
subject to the per-key in-flight dedup or the refused-capture backoff, which
|
|
574
|
+
exist to stop a herd of visitors repeating work; every correctness guard on
|
|
575
|
+
this page still decides. See `/prerender` → "Warm any route before traffic".
|
|
576
|
+
|
|
564
577
|
### Unit / integration testing (public primitives)
|
|
565
578
|
|
|
566
579
|
Import from `@rangojs/router/testing` (Vitest) or `@rangojs/router/testing/e2e`
|
|
@@ -571,17 +584,18 @@ Import from `@rangojs/router/testing` (Vitest) or `@rangojs/router/testing/e2e`
|
|
|
571
584
|
| `assertShellStatus(res, "HIT" \| "MISS")` | Document Response from a real RSC serve / e2e `page.request` |
|
|
572
585
|
| `assertPprReplayStatus(res, expected)` | Partial response fresh/stale replay or bounded bypass |
|
|
573
586
|
| `parsePprReplayStatus(res)` | Read structured replay/bypass status or null |
|
|
574
|
-
| `shellCacheKey(url)`
|
|
587
|
+
| `shellCacheKey(router, url)` | Production store key for `store.getShell` / custom stores |
|
|
575
588
|
| `MemorySegmentCacheStore` + `getShell`/`putShell` | Custom store contract / tag eviction (no faked HIT) |
|
|
576
589
|
| `serveShellRequest(router, url, opts)` | A real MISS → capture → HIT (react-server Vitest) |
|
|
577
590
|
|
|
578
591
|
```ts
|
|
579
592
|
import { MemorySegmentCacheStore } from "@rangojs/router/cache";
|
|
580
593
|
import { assertShellStatus, shellCacheKey } from "@rangojs/router/testing";
|
|
594
|
+
import { router } from "./router";
|
|
581
595
|
|
|
582
596
|
// Unit: store + key (after a real putShell / capture flush in e2e)
|
|
583
597
|
const store = new MemorySegmentCacheStore();
|
|
584
|
-
const key = shellCacheKey("http://localhost/products/1");
|
|
598
|
+
const key = shellCacheKey(router, "http://localhost/products/1");
|
|
585
599
|
// ... after production putShell ...
|
|
586
600
|
expect(await store.getShell(key)).not.toBeNull();
|
|
587
601
|
|
|
@@ -788,6 +802,9 @@ identifiers — served to anonymous visitors.)
|
|
|
788
802
|
- `ctx.use(H)(promise)` from a handler — a TOP-LEVEL pushed promise is awaited
|
|
789
803
|
at capture and BAKED into the shell (bounded by `ppr.captureTimeout`, 15s by
|
|
790
804
|
default).
|
|
805
|
+
- A promise pushed from a LOADER is never in the shell and the capture does not
|
|
806
|
+
wait for it: however slow it is (or if it never settles), the shell is still
|
|
807
|
+
stored, and the HIT's own loader run delivers the value after hydration.
|
|
791
808
|
- `ctx.use(H)({ x: promise })` from a HANDLER — also baked. The capture waits
|
|
792
809
|
for every promise nested in a handler's push, at any depth of plain objects,
|
|
793
810
|
arrays and JSX props, before it records the handles every HIT replays. Keep
|
|
@@ -930,6 +947,11 @@ pin: a snapshot captured by a navigation alone carries none, and a shell over
|
|
|
930
947
|
`ppr.maxSnapshotBytes` drops them. There the loader runs fresh, and its
|
|
931
948
|
pushes are that run's, like its data.
|
|
932
949
|
|
|
950
|
+
A `Prerender` + `ppr` page follows the same table. Its handler layer comes
|
|
951
|
+
from the prerendered payload, which never holds a loader push (loaders do not
|
|
952
|
+
run at build), so the shell entry records the loader pushes its HTML rendered
|
|
953
|
+
on its own, and a HIT restores them as above (#1057).
|
|
954
|
+
|
|
933
955
|
A dependency the route does not register, which an `ssr: false` loader
|
|
934
956
|
awaited at capture outside any live loader, is on neither lane: its settled
|
|
935
957
|
pushes stay as the prelude rendered them, even where a live loader also
|
|
@@ -1347,8 +1369,9 @@ shrink what the shell bakes or move large regions under a live loader's
|
|
|
1347
1369
|
boundary.
|
|
1348
1370
|
|
|
1349
1371
|
The shell store is always the app-level `createRouter({ cache })` store; the
|
|
1350
|
-
default key is `${host}${pathname}${sortedSearch}:shell
|
|
1351
|
-
multi-tenant shells never collide
|
|
1372
|
+
default key is `${routerId}@${host}${pathname}${sortedSearch}:shell`: host-scoped
|
|
1373
|
+
so multi-tenant shells never collide, and router-scoped so two routers on one
|
|
1374
|
+
store keep their own (see `/host-router`, "Shared cache store").
|
|
1352
1375
|
|
|
1353
1376
|
These options control the in-function shell entry only. They do not emit HTTP
|
|
1354
1377
|
`Cache-Control`. Adding `s-maxage` separately allows a platform CDN to cache the
|