@rangojs/router 0.17.0 → 0.18.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/README.md +3 -2
- package/dist/types/browser/cookie-name.d.ts +6 -0
- package/dist/types/browser/history-state.d.ts +16 -0
- package/dist/types/cache/cache-key-utils.d.ts +17 -2
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +98 -14
- package/dist/types/cache/cache-tag.d.ts +92 -7
- package/dist/types/cache/cf/cf-base64.d.ts +6 -0
- package/dist/types/cache/cf/cf-cache-constants.d.ts +11 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +185 -44
- package/dist/types/cache/cf/cf-cache-types.d.ts +31 -4
- package/dist/types/cache/cf/cf-shell-frame.d.ts +95 -0
- package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +34 -0
- package/dist/types/cache/handle-snapshot.d.ts +4 -3
- package/dist/types/cache/index.d.ts +1 -0
- package/dist/types/cache/isolate-tag-memo.d.ts +149 -0
- package/dist/types/cache/shell-memo.d.ts +178 -0
- package/dist/types/cache/shell-snapshot.d.ts +110 -21
- package/dist/types/cache/tag-invalidation.d.ts +30 -3
- package/dist/types/cache/types.d.ts +207 -43
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +111 -13
- package/dist/types/prerender/build-shell-capture.d.ts +11 -4
- package/dist/types/router/instrument.d.ts +8 -0
- package/dist/types/router/middleware-types.d.ts +6 -1
- package/dist/types/router/segment-resolution/loader-cache.d.ts +40 -11
- package/dist/types/router/segment-resolution/loader-snapshot.d.ts +21 -10
- package/dist/types/router/segment-resolution/mask-nested.d.ts +65 -24
- package/dist/types/rsc/full-payload.d.ts +14 -0
- package/dist/types/rsc/handler.d.ts +1 -1
- package/dist/types/rsc/shell-build-manifest.d.ts +8 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +53 -6
- package/dist/types/rsc/shell-capture.d.ts +146 -15
- package/dist/types/rsc/shell-serve.d.ts +140 -17
- package/dist/types/rsc/types.d.ts +7 -0
- package/dist/types/server/context.d.ts +109 -15
- package/dist/types/server/cookie-store.d.ts +21 -0
- package/dist/types/server/request-context.d.ts +159 -33
- package/dist/types/ssr/index.d.ts +9 -0
- package/dist/types/ssr/ssr-root.d.ts +9 -8
- package/dist/types/testing/flight.entry.d.ts +7 -0
- package/dist/types/testing/run-loader.d.ts +18 -3
- package/dist/types/testing/serve-shell-request.d.ts +132 -0
- package/dist/types/testing/shell-status.d.ts +23 -10
- package/dist/types/types/handler-context.d.ts +14 -1
- package/dist/types/urls/pattern-types.d.ts +22 -18
- package/dist/types/vite/router-discovery.d.ts +29 -2
- package/dist/vite/index.js +46 -9
- package/package.json +1 -1
- package/skills/breadcrumbs/SKILL.md +17 -7
- package/skills/cache-guide/SKILL.md +123 -51
- package/skills/caching/SKILL.md +253 -23
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +11 -6
- package/skills/cloudflare/SKILL.md +15 -0
- package/skills/comparison/references/framework-comparison.md +44 -32
- package/skills/defer-hydration/SKILL.md +50 -30
- package/skills/deployment-caching/SKILL.md +5 -2
- package/skills/document-cache/SKILL.md +8 -2
- package/skills/handler-use/SKILL.md +2 -0
- package/skills/hooks/handle-and-actions.md +3 -1
- package/skills/hooks/state.md +3 -1
- package/skills/loader/SKILL.md +161 -39
- package/skills/middleware/SKILL.md +16 -9
- package/skills/migrate-nextjs/SKILL.md +67 -36
- package/skills/migrate-react-router/SKILL.md +2 -1
- package/skills/migrate-react-router/component-migration.md +3 -1
- package/skills/migrate-react-router/data-and-actions.md +3 -1
- package/skills/migrate-react-router/route-mapping.md +7 -4
- package/skills/observability/SKILL.md +64 -0
- package/skills/parallel/SKILL.md +13 -8
- package/skills/ppr/SKILL.md +655 -363
- package/skills/prerender/SKILL.md +27 -14
- package/skills/rango/SKILL.md +52 -33
- package/skills/route/SKILL.md +12 -7
- package/skills/router-setup/SKILL.md +4 -2
- package/skills/scripts/SKILL.md +8 -1
- package/skills/server-actions/SKILL.md +11 -0
- package/skills/shell-manifest/SKILL.md +21 -12
- package/skills/testing/SKILL.md +25 -21
- package/skills/testing/cache-prerender.md +15 -14
- package/skills/testing/loader.md +18 -1
- package/skills/testing/serve-shell-request.md +188 -0
- package/skills/theme/SKILL.md +52 -4
- package/skills/typesafety/env-and-bindings.md +10 -2
- package/skills/use-cache/SKILL.md +39 -16
- package/skills/vercel/SKILL.md +14 -2
- package/skills/view-transitions/SKILL.md +1 -1
- package/src/browser/cookie-name.ts +23 -0
- package/src/browser/history-state.ts +23 -0
- package/src/browser/rsc-router.tsx +5 -0
- package/src/cache/cache-key-utils.ts +22 -2
- package/src/cache/cache-policy.ts +22 -0
- package/src/cache/cache-runtime.ts +34 -7
- package/src/cache/cache-scope.ts +345 -33
- package/src/cache/cache-tag.ts +375 -11
- package/src/cache/cf/cf-base64.ts +36 -4
- package/src/cache/cf/cf-cache-constants.ts +12 -0
- package/src/cache/cf/cf-cache-store.ts +990 -348
- package/src/cache/cf/cf-cache-types.ts +32 -4
- package/src/cache/cf/cf-shell-frame.ts +258 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +143 -39
- package/src/cache/handle-snapshot.ts +4 -3
- package/src/cache/index.ts +2 -0
- package/src/cache/isolate-tag-memo.ts +308 -0
- package/src/cache/shell-memo.ts +341 -0
- package/src/cache/shell-snapshot.ts +217 -27
- package/src/cache/tag-invalidation.ts +104 -19
- package/src/cache/types.ts +211 -43
- package/src/cache/vercel/vercel-cache-store.ts +545 -61
- package/src/prerender/build-shell-capture.ts +65 -11
- package/src/route-definition/dsl-helpers.ts +4 -3
- package/src/router/handler-context.ts +38 -4
- package/src/router/instrument.ts +54 -2
- package/src/router/intercept-resolution.ts +19 -7
- package/src/router/loader-resolution.ts +63 -32
- package/src/router/match-api.ts +28 -9
- package/src/router/match-handlers.ts +5 -0
- package/src/router/match-middleware/cache-lookup.ts +17 -0
- package/src/router/match-middleware/cache-store.ts +31 -11
- package/src/router/middleware-types.ts +6 -1
- package/src/router/middleware.ts +23 -4
- package/src/router/prerender-match.ts +6 -0
- package/src/router/segment-resolution/fresh.ts +18 -9
- package/src/router/segment-resolution/helpers.ts +4 -2
- package/src/router/segment-resolution/loader-cache.ts +227 -56
- package/src/router/segment-resolution/loader-snapshot.ts +203 -51
- package/src/router/segment-resolution/mask-nested.ts +136 -42
- package/src/router/segment-resolution/revalidation.ts +27 -10
- package/src/rsc/full-payload.ts +25 -1
- package/src/rsc/handler.ts +14 -2
- package/src/rsc/progressive-enhancement.ts +3 -2
- package/src/rsc/response-cache-serve.ts +9 -6
- package/src/rsc/rsc-rendering.ts +601 -181
- package/src/rsc/shell-build-manifest.ts +27 -9
- package/src/rsc/shell-capture-constants.ts +60 -6
- package/src/rsc/shell-capture.ts +1001 -250
- package/src/rsc/shell-serve.ts +316 -34
- package/src/rsc/types.ts +7 -0
- package/src/server/context.ts +219 -28
- package/src/server/cookie-store.ts +87 -62
- package/src/server/request-context.ts +263 -38
- package/src/ssr/index.tsx +96 -37
- package/src/ssr/ssr-root.tsx +9 -8
- package/src/testing/flight.entry.ts +14 -0
- package/src/testing/run-loader.ts +63 -5
- package/src/testing/serve-shell-request.ts +378 -0
- package/src/testing/shell-status.ts +34 -12
- package/src/theme/ThemeProvider.tsx +7 -6
- package/src/types/handler-context.ts +14 -1
- package/src/urls/pattern-types.ts +22 -18
- package/src/vite/discovery/shell-prerender-phase.ts +9 -0
- package/src/vite/router-discovery.ts +14 -4
|
@@ -40,8 +40,10 @@ Common reasons to migrate:
|
|
|
40
40
|
build-time rendering instead of mixing rendering and caching behind conventions.
|
|
41
41
|
See: `/prerender`
|
|
42
42
|
- **Partial prerendering, shipped** — the `ppr` path option caches a page's
|
|
43
|
-
HTML shell and resumes
|
|
44
|
-
|
|
43
|
+
HTML shell and resumes its holes per request: loaders without `ssr: false`,
|
|
44
|
+
read under `loading()` or an inline `<Suspense>`. Handler output bakes into
|
|
45
|
+
the shell. It maps to Next's `experimental_ppr`, stable and per-route, but
|
|
46
|
+
holes come from loaders, not from any `<Suspense>`.
|
|
45
47
|
See: `/ppr`
|
|
46
48
|
- **Composable route tree** — layouts, includes, middleware, parallels, and
|
|
47
49
|
intercepts compose directly in the route definition.
|
|
@@ -93,6 +95,13 @@ its call site with the real Rango API:
|
|
|
93
95
|
| `next/font` | see `/fonts` |
|
|
94
96
|
| `next/script` `Script` | see `/scripts` |
|
|
95
97
|
| `next-themes` | `theme: true` in `createRouter` (see §10) |
|
|
98
|
+
| `next/dynamic` `dynamic(() => import(...), { ssr: false })` | render it after mount in a `"use client"` component (see the note below) |
|
|
99
|
+
|
|
100
|
+
`next/dynamic`'s `{ ssr: false }` keeps a component out of server rendering:
|
|
101
|
+
render it only after mount in a `"use client"` component (a `useEffect` flag,
|
|
102
|
+
with `React.lazy()` if its code should split out). Do not map it to
|
|
103
|
+
`loader(L, { ssr: false })`, which does the opposite: the server settles that
|
|
104
|
+
loader before the first flush, and under `ppr` it bakes into the shell.
|
|
96
105
|
|
|
97
106
|
If an import has no row here and no obvious Rango equivalent, stop and surface
|
|
98
107
|
it to the user — do not mock it to keep the build green.
|
|
@@ -418,9 +427,12 @@ Next.js route segment config maps onto Rango's explicit primitives:
|
|
|
418
427
|
### Partial prerendering → the `ppr` path option
|
|
419
428
|
|
|
420
429
|
Next.js PPR statically prerenders a shell at build time and streams the parts
|
|
421
|
-
inside `<Suspense>` at request time. Rango ships
|
|
430
|
+
inside `<Suspense>` at request time. Rango ships a similar model as a path
|
|
422
431
|
option — the shell is captured at runtime into the app cache store and resumed
|
|
423
|
-
on later requests, with the holes rendered fresh per request
|
|
432
|
+
on later requests, with the holes rendered fresh per request. The difference is
|
|
433
|
+
what counts as a hole: in Rango everything a handler produces is shell
|
|
434
|
+
material, as it is under `cache()`, including a promise it hands to a component
|
|
435
|
+
under `<Suspense>`. A live hole is loader data:
|
|
424
436
|
|
|
425
437
|
```typescript
|
|
426
438
|
// Next.js: app/products/[id]/page.tsx
|
|
@@ -435,14 +447,14 @@ export default async function Page({ params }) {
|
|
|
435
447
|
);
|
|
436
448
|
}
|
|
437
449
|
|
|
438
|
-
// Rango
|
|
439
|
-
//
|
|
440
|
-
//
|
|
450
|
+
// Rango: the direct carry-over BAKES. Under ppr the capture awaits a promise
|
|
451
|
+
// the handler hands down (bounded by ppr.captureTimeout), and every shell HIT
|
|
452
|
+
// shows the captured price.
|
|
441
453
|
import { Suspense } from "react";
|
|
442
454
|
import type { HandlerContext } from "@rangojs/router";
|
|
443
455
|
|
|
444
456
|
function ProductPage(ctx: HandlerContext) {
|
|
445
|
-
const price = fetchPrice(ctx.params.id); //
|
|
457
|
+
const price = fetchPrice(ctx.params.id); // awaited at capture, then frozen
|
|
446
458
|
return (
|
|
447
459
|
<ProductShell>
|
|
448
460
|
<Suspense fallback={<PriceSkeleton />}>
|
|
@@ -451,41 +463,54 @@ function ProductPage(ctx: HandlerContext) {
|
|
|
451
463
|
</ProductShell>
|
|
452
464
|
);
|
|
453
465
|
}
|
|
454
|
-
path("/products/:id", ProductPage, {
|
|
455
|
-
name: "product",
|
|
456
|
-
ppr: { ttl: 600, swr: 120 }, // or ppr: true (default ttl 300s)
|
|
457
|
-
});
|
|
458
466
|
|
|
459
|
-
// Rango,
|
|
460
|
-
// (
|
|
461
|
-
//
|
|
467
|
+
// Rango, live price: a loader without ssr: false, read with
|
|
468
|
+
// useLoader(LivePriceLoader) in a client component. loading() (or an inline
|
|
469
|
+
// <Suspense> around the reader) is the hole boundary (/ppr → The loader lane
|
|
470
|
+
// rule).
|
|
471
|
+
function ProductPageLive() {
|
|
472
|
+
return (
|
|
473
|
+
<ProductShell>
|
|
474
|
+
<LivePriceFromLoader /> {/* "use client": useLoader(LivePriceLoader) */}
|
|
475
|
+
</ProductShell>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
462
478
|
path(
|
|
463
479
|
"/products/:id",
|
|
464
|
-
|
|
465
|
-
{ name: "product", ppr: { ttl: 600, swr: 120 } },
|
|
480
|
+
ProductPageLive,
|
|
481
|
+
{ name: "product", ppr: { ttl: 600, swr: 120 } }, // or ppr: true (ttl 300s)
|
|
466
482
|
() => [loader(LivePriceLoader), loading(<PriceSkeleton />)],
|
|
467
483
|
),
|
|
468
484
|
```
|
|
469
485
|
|
|
470
486
|
Differences that matter during migration:
|
|
471
487
|
|
|
472
|
-
- **
|
|
473
|
-
promise
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
488
|
+
- **Handler output bakes; loader data is the hole.** Unlike Next, a pending
|
|
489
|
+
promise the handler hands to a component under `<Suspense>` does NOT make a
|
|
490
|
+
hole. Everything a handler produces — that promise, an async server
|
|
491
|
+
component, a nested promise in a handle it pushes, a loader it awaits — is
|
|
492
|
+
awaited at capture (bounded by `ppr.captureTimeout`) and served frozen for
|
|
493
|
+
the shell's lifetime; a shell HIT never runs a handler. Keep the parts that
|
|
494
|
+
must be fresh in loaders without `ssr: false`, read with `useLoader` under
|
|
495
|
+
`loading()` or an inline `<Suspense>`; inside an `ssr: false` loader, return
|
|
496
|
+
them as nested promises. For loaders, `ssr: false` (not `loading()`) selects
|
|
497
|
+
the lane — see `/ppr` → The loader lane rule.
|
|
481
498
|
- **Shell freshness is explicit.** Next's PPR shell is fixed until the next
|
|
482
499
|
build; Rango's has `ttl`/`swr`/`tags` per route, and `updateTag()` /
|
|
483
500
|
`revalidateTag()` drop the shell (`revalidate()` does not — it is a data
|
|
484
|
-
lever and never touches shell HTML).
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
501
|
+
lever and never touches shell HTML). Under a route `cache()`, the shell
|
|
502
|
+
lives no longer than that entry: its ttl/swr are capped by the entry's (dev
|
|
503
|
+
warns when an explicit `ppr` window is reduced). A route `cache({ key })`
|
|
504
|
+
gives one shell per key value, and `cache(false)` or a false `condition()`
|
|
505
|
+
means no shell.
|
|
506
|
+
- **Request-scoped reads in shell material refuse the capture** (in Next they
|
|
507
|
+
silently force dynamic rendering). `cookies()`, `headers()`, a
|
|
508
|
+
`{ cache: false }` variable, and `ctx.dynamic()` refuse it anywhere the
|
|
509
|
+
capture waits: a handler, a promise it passes or pushes, an async component,
|
|
510
|
+
a loader it awaits. Per-user reads must move into a live loader (no
|
|
511
|
+
`ssr: false`; a nested promise does not help). A normal `ctx.get()` value is
|
|
512
|
+
not guarded and bakes as the capturing request's value. The refusal surfaces
|
|
513
|
+
at migration time, which is the point.
|
|
489
514
|
- **A store is required.** PPR needs the app-level `createRouter({ cache })`
|
|
490
515
|
store to implement the shell family (`MemorySegmentCacheStore`,
|
|
491
516
|
`CFCacheStore`, `VercelCacheStore`). Without one the route quietly stays
|
|
@@ -506,19 +531,25 @@ tag-based cache invalidation now maps directly.
|
|
|
506
531
|
with `cache({ tags })` or runtime `cacheTag(...tags)`. `cacheTag()` works inside a
|
|
507
532
|
`"use cache"` function (tags that entry) AND render-callable in a plain server
|
|
508
533
|
component (no `"use cache"` needed — it tags the document / PPR shell the component
|
|
509
|
-
renders into). Then invalidate by tag:
|
|
534
|
+
renders into, and the route `cache()` entry it renders inside). Then invalidate by tag:
|
|
510
535
|
|
|
511
536
|
```typescript
|
|
512
537
|
// Next.js Rango
|
|
513
538
|
// revalidateTag("products") → await updateTag("products") // in a server action: awaitable,
|
|
514
539
|
// // read-your-own-writes (next render is fresh)
|
|
515
540
|
// or revalidateTag("products") // in a route handler / webhook:
|
|
516
|
-
// // background, non-blocking (
|
|
541
|
+
// // background, non-blocking (evicts)
|
|
517
542
|
```
|
|
518
543
|
|
|
519
544
|
`updateTag` is awaitable and immediate; `revalidateTag` is fire-and-forget. Both
|
|
520
|
-
|
|
521
|
-
|
|
545
|
+
evict (despite the Next.js name, `revalidateTag` here is NOT
|
|
546
|
+
stale-while-revalidate), and the request that calls either reads its own
|
|
547
|
+
writes. `updateTag()` also waits for the durable write and rejects when it
|
|
548
|
+
fails; `revalidateTag()` runs it in the background. The mutating user's next
|
|
549
|
+
requests skip the stores' per-isolate PPR shell memos via the fresh-reads
|
|
550
|
+
cookie (`rango-state-fresh`); other users, and other locations on
|
|
551
|
+
`CFCacheStore`, see the invalidation once the memo refreshes and KV
|
|
552
|
+
propagates (`/caching` → "The fresh-reads cookie").
|
|
522
553
|
Built-in stores (`MemorySegmentCacheStore`, `CFCacheStore`, `VercelCacheStore`)
|
|
523
554
|
index by tag. Next's
|
|
524
555
|
`revalidatePath` has no path-based equivalent — tag the relevant entries instead.
|
|
@@ -756,7 +787,7 @@ Server actions work the same way — `"use server"` directive, `useActionState`,
|
|
|
756
787
|
|
|
757
788
|
Key difference: in Rango, route middleware does NOT wrap action execution. Actions only see global middleware context. Use `getRequestContext()` in actions to access `ctx.set()`/`ctx.get()`.
|
|
758
789
|
|
|
759
|
-
Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data; `revalidateTag(tag)` is a background (non-blocking) hard-purge
|
|
790
|
+
Next.js's `revalidateTag()` maps directly: tag entries via `cache({ tags })` / `cacheTag(...)`, then invalidate. **In a server action use `await updateTag(tag)`** — it is read-your-own-writes, so the action's own re-render sees fresh data, and it rejects when the store's durable write fails; `revalidateTag(tag)` is a background (non-blocking) hard-purge for route handlers / webhooks. The request that calls `revalidateTag()` also reads its own writes, but a failed durable write only reaches `onError`. `revalidatePath()` has no path-based equivalent — tag the route's entries instead. Separately, the route, the segments inside its `path()`, and all loaders already re-run after every action; layouts and parallels above the route are skipped. To re-render one of those skipped segments after an action, attach `revalidate((ctx) => ctx.isAction(updateBlog) || undefined)` to it; to narrow a loader or route segment to one action, use `revalidate((ctx) => (ctx.isAction() ? ctx.isAction(updateBlog) : undefined))` (match the imported action by reference, as in §3). See `/server-actions` for the full pattern (validation, error handling, file uploads), `/caching` for tag invalidation, and `/loader` for revalidation rule semantics.
|
|
760
791
|
|
|
761
792
|
## 8. Metadata / Head
|
|
762
793
|
|
|
@@ -33,7 +33,8 @@ Common reasons to migrate:
|
|
|
33
33
|
See: `/middleware`, `/loader`
|
|
34
34
|
- **Live data layer** — `createLoader()` and `loader()` keep data fresh
|
|
35
35
|
independently of cached UI. A route can serve cached segments while loaders
|
|
36
|
-
still resolve live on every request
|
|
36
|
+
still resolve live on every request (on a `ppr` route, a loader registered
|
|
37
|
+
with `ssr: false` bakes into the cached HTML shell instead; see `/ppr`).
|
|
37
38
|
See: `/loader`, `/caching`, `/cache-guide`
|
|
38
39
|
- **Explicit caching model** — `cache()` DSL, `revalidate()`, `use cache`, and
|
|
39
40
|
custom cache stores make data and render caching a first-class part of the router.
|
|
@@ -154,7 +154,9 @@ const HomePage: Handler<"home"> = (ctx) => {
|
|
|
154
154
|
RR's data-derived `meta({ data })` maps to the same push from the LOADER that
|
|
155
155
|
owns the data — `ctx.use(Meta)({ title: data.name })` in the loader body, with
|
|
156
156
|
`loader(Def, { ssr: false })` when the title must be in the SSR'd
|
|
157
|
-
head. See `/loader` → "Writing Handles from Loaders".
|
|
157
|
+
head. See `/loader` → "Writing Handles from Loaders". The loader flag is
|
|
158
|
+
unrelated to RR7's `ssr: false` (SPA mode), which has no route-wide Rango
|
|
159
|
+
equivalent: it makes the server settle that loader before the first flush.
|
|
158
160
|
|
|
159
161
|
Add `<Html.Meta />` in the Document component's `<head>`:
|
|
160
162
|
|
|
@@ -48,7 +48,9 @@ Use it only when you need capabilities beyond what the handler provides:
|
|
|
48
48
|
document loads, never an HTTP 302 — pre-stream 302s move to middleware)
|
|
49
49
|
- **`meta({ data })` / `handle` exports** — data-derived page metadata becomes
|
|
50
50
|
a handle push from the loader body (`ctx.use(Meta)({ title: data.name })`),
|
|
51
|
-
with `loader(L, { ssr: false })` when it must be in the SSR'd head
|
|
51
|
+
with `loader(L, { ssr: false })` when it must be in the SSR'd head (not
|
|
52
|
+
RR7's `ssr: false` SPA mode, which has no route-wide Rango equivalent: the
|
|
53
|
+
loader flag makes the server settle that loader before the first flush)
|
|
52
54
|
|
|
53
55
|
If the React Router loader just fetches data for its page component AND the
|
|
54
56
|
component can become a server component, merge it into the handler. If the
|
|
@@ -142,10 +142,13 @@ the loader carried authority. A `createLoader()` body can throw `notFound()`
|
|
|
142
142
|
for the missing product AND push the data-derived meta itself
|
|
143
143
|
(`ctx.use(Meta)({ title: product.name })`); register it with
|
|
144
144
|
`loader(ProductLoader, { ssr: false })` when the 404 status and
|
|
145
|
-
title must be in the document deterministically
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
title must be in the document deterministically (the flag makes the server
|
|
146
|
+
settle that loader before the first flush; it is unrelated to RR7's
|
|
147
|
+
`ssr: false` SPA mode, which has no route-wide Rango equivalent). See
|
|
148
|
+
`/loader` → "Loader Authority" and "Writing Handles from Loaders". (One RR
|
|
149
|
+
habit that does NOT carry over: a loader `throw redirect()` is a client-side
|
|
150
|
+
navigate on document loads, never an HTTP 302 — pre-stream 302s belong in
|
|
151
|
+
middleware.)
|
|
149
152
|
|
|
150
153
|
### RR7 file routing → urls() DSL
|
|
151
154
|
|
|
@@ -63,6 +63,70 @@ Read the timeline as intervals:
|
|
|
63
63
|
actions (`action:<id>`), cache, RSC serialization, and SSR setup appear as
|
|
64
64
|
separate rows, so the slow phase is visible without guessing.
|
|
65
65
|
|
|
66
|
+
### Reading a PPR shell HIT
|
|
67
|
+
|
|
68
|
+
A `ppr` route serving from its shell cache commits the response as soon as the
|
|
69
|
+
stored prelude is read, so its waterfall ends at `ppr:shell-commit`; what runs
|
|
70
|
+
after the commit (the capture snapshot, the seeded tail, the resumed holes)
|
|
71
|
+
prints as one `shell tail` line when it finishes, and rides the NEXT request's
|
|
72
|
+
`Server-Timing` as `ppr-tail`. The store-read rows under `ppr:shell-read` come
|
|
73
|
+
from the built-in stores (`CFCacheStore`'s prelude-first read shown here;
|
|
74
|
+
`VercelCacheStore` reports `hit store`, one `ppr:shell-match (store)` row, and
|
|
75
|
+
a `serial` marker read); other stores show `ppr:shell-read`,
|
|
76
|
+
`ppr:shell-open`, and `ppr:shell-commit` only. From `vite preview` of
|
|
77
|
+
`tests/cloudflare-basic` `/ppr-large/holes` (614 KB prelude, 1.0 MB capture
|
|
78
|
+
snapshot), with the timeline column dropped:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
[RSC Perf] GET /ppr-large/holes (4.00ms)
|
|
82
|
+
start dur span
|
|
83
|
+
1.00ms 0.00ms middleware:*#2
|
|
84
|
+
1.00ms 3.00ms ppr:shell-read (hit l1)
|
|
85
|
+
1.00ms 1.00ms ppr:shell-match (l1)
|
|
86
|
+
1.00ms 3.00ms render:total:pprLargeHoles
|
|
87
|
+
2.00ms 0.00ms ppr:shell-head (bytes=27943)
|
|
88
|
+
2.00ms 2.00ms ppr:shell-prelude (bytes=628949)
|
|
89
|
+
2.00ms 0.00ms ppr:shell-marker (tags=0 parallel commit-wait=0.00ms)
|
|
90
|
+
4.00ms 0.00ms ppr:shell-open (cpu raw prelude=628949b)
|
|
91
|
+
4.00ms 0.00ms ppr:shell-commit (cpu chunks=20 prelude=628949b)
|
|
92
|
+
0.00ms 4.00ms handler:total
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
and, when the tail finishes, one console line (wrapped here):
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
[RSC Perf] GET /ppr-large/holes shell tail: complete snapshot=4ms
|
|
99
|
+
snapshot-read=2ms snapshot-bytes=1019145b snapshot-parse-cpu=2ms
|
|
100
|
+
records=segment:1 pruned=item:5 seed=4ms seed-cpu=0ms match=4ms handover=4ms
|
|
101
|
+
first-html=5ms complete=55ms prelude=628949b tail=1033762b
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- `ppr:shell-match` then `head` then `prelude`: the store read. Only the head
|
|
105
|
+
and the prelude are read before the first byte; the snapshot is read off the
|
|
106
|
+
commit path (`snapshot=` in the tail line). A KV hit after a Cache API miss
|
|
107
|
+
starts with a `ppr:shell-l1-miss` row (the L1 attempt and why it missed). A
|
|
108
|
+
`ppr:shell-memo (hit …)` row means the shell came from the isolate's memo:
|
|
109
|
+
no store read, only the marker check.
|
|
110
|
+
- `ppr:shell-marker` is the tag-marker check, on every `CFCacheStore` read; it
|
|
111
|
+
runs alongside `ppr:shell-prelude`, and `commit-wait` is how much it held the
|
|
112
|
+
commit back (with `tags=0` it resolves at once). For a tagged shell it adds
|
|
113
|
+
`memo=` (how the per-isolate marker memo answered: `fresh`, `stale` served
|
|
114
|
+
while it refreshes, `read` from the store, `bypass` for a request carrying
|
|
115
|
+
the fresh-reads cookie), `hint=<shell tags hinted>/<tags hinted>` with `lead=` (how long the
|
|
116
|
+
hinted marker reads ran before the entry named its tags), and `fresh-reads`
|
|
117
|
+
when the request carried the cookie.
|
|
118
|
+
- `records=` counts the snapshot records the tail was seeded with, by
|
|
119
|
+
family; `pruned=` counts the ones the capture dropped because no reader of
|
|
120
|
+
the entry consumes them. Here every HIT tail replays the handler layer from
|
|
121
|
+
the one segment record, so the five `"use cache"` item records that produced
|
|
122
|
+
it were not stored.
|
|
123
|
+
- `snapshot=`, `seed=`, `match=`, `first-html=` are offsets from the commit,
|
|
124
|
+
not durations; `snapshot-read=`, `snapshot-parse-cpu=` and `seed-cpu=` are
|
|
125
|
+
durations.
|
|
126
|
+
- On a deployed worker the clock only moves on I/O, so the CPU-only numbers
|
|
127
|
+
(`ppr:shell-open`, `ppr:shell-commit`, `snapshot-parse-cpu`, `seed-cpu`)
|
|
128
|
+
read 0 there; the byte counts are the cost to watch.
|
|
129
|
+
|
|
66
130
|
The console waterfall uses these labels as written. In the `Server-Timing`
|
|
67
131
|
header, colons become hyphens and other non-alphanumeric characters are
|
|
68
132
|
dropped (`ssr:render-html` → `ssr-render-html`, `handler:total` →
|
package/skills/parallel/SKILL.md
CHANGED
|
@@ -148,13 +148,18 @@ route overrides via its own `@meta` slot. The route's slot pushes after the
|
|
|
148
148
|
layout, and `Meta` keeps the last value per key, so the override wins.
|
|
149
149
|
|
|
150
150
|
> **Loader-derived metadata: push from the loader instead.** This slot's
|
|
151
|
-
> `await ctx.use(ProductLoader)` routes the data through HANDLER consumption
|
|
152
|
-
>
|
|
153
|
-
>
|
|
154
|
-
>
|
|
155
|
-
> `
|
|
156
|
-
>
|
|
157
|
-
>
|
|
151
|
+
> `await ctx.use(ProductLoader)` routes the data through HANDLER consumption
|
|
152
|
+
> just to reach a `ctx.use(Meta)` call: under `cache()` the slot's output is
|
|
153
|
+
> stored with the segment, and on a `ppr` route it bakes into the shell and a
|
|
154
|
+
> HIT replays it without running the slot. The loader can push `Meta` itself
|
|
155
|
+
> (`/loader` → "Writing Handles from Loaders"). Without `ssr: false` the
|
|
156
|
+
> loader runs per request, but on a `ppr` route its push is not in the
|
|
157
|
+
> shell's `<head>` (it applies after hydration). Add
|
|
158
|
+
> `loader(Def, { ssr: false })` when the meta must be in the SSR'd head; on a
|
|
159
|
+
> `ppr` route that bakes the push into the shell, and a HIT serves it without
|
|
160
|
+
> running the loader when its value has no promises. Keep the `@meta` slot for
|
|
161
|
+
> metadata that is NOT loader-derived (templates, static descriptions,
|
|
162
|
+
> structured data with independent inputs).
|
|
158
163
|
|
|
159
164
|
```typescript
|
|
160
165
|
// Layout sets defaults
|
|
@@ -275,7 +280,7 @@ layout(<AccountLayout />, () => [
|
|
|
275
280
|
|
|
276
281
|
A slot's `loading()` (whether from `handler.use` or explicit) makes that slot an independent streaming unit, exactly as in the **Streaming Behavior** section above.
|
|
277
282
|
|
|
278
|
-
Under a shared artifact (`cache()`, `"use cache"`, a PPR shell), the server-side `await ctx.use(CartLoader)` above is the BAKED lane — the capture-time value (identity
|
|
283
|
+
Under a shared artifact (`cache()`, `"use cache"`, a PPR shell), the server-side `await ctx.use(CartLoader)` above is the BAKED lane — the capture-time value freezes into the artifact. Under `cache()`/`"use cache"` an identity read (`cookies()`, `headers()`) inside the loader bakes too; under a PPR shell it refuses the capture, so the route stays uncached. Consume the loader client-side (`useLoader` in a `"use client"` component, the slot's `loading()` as its boundary) to keep the slot live per request. One rule, stated once: `/rango` → Invariants ("the consumption-lane rule").
|
|
279
284
|
|
|
280
285
|
The `parallel` mount site has the narrowest allow-list for `handler.use` items — slots cannot bring their own middleware or layout, only `revalidate`, `loader`, `loading`, `errorBoundary`, `notFoundBoundary`, and `transition`. See [skills/handler-use](../handler-use/SKILL.md) for the full table and merge rules.
|
|
281
286
|
|