@rangojs/router 0.24.4 → 0.26.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 +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-cache-store.d.ts +0 -2
- package/dist/types/cache/cf/cf-cache-types.d.ts +0 -16
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/handle-snapshot.d.ts +2 -2
- package/dist/types/cache/types.d.ts +16 -6
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +1 -15
- package/dist/types/client-urls/client-urls.d.ts +6 -2
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +9 -5
- package/dist/types/client-urls/types.d.ts +29 -16
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/handles/deferred-resolution.d.ts +3 -2
- package/dist/types/handles/meta.d.ts +5 -0
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/prerender/build-shell-capture.d.ts +1 -2
- package/dist/types/prerender/writable-store.d.ts +2 -3
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -11
- package/dist/types/router/router-options.d.ts +4 -27
- package/dist/types/router/segment-resolution/mask-nested.d.ts +5 -3
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +3 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/router-versions.d.ts +8 -6
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +14 -4
- package/dist/types/rsc/shell-capture.d.ts +23 -2
- package/dist/types/rsc/types.d.ts +5 -8
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/build-version-table.d.ts +10 -13
- package/dist/types/server/context.d.ts +90 -6
- package/dist/types/server/handle-store.d.ts +17 -6
- package/dist/types/server/request-context.d.ts +45 -7
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/build-versions.d.ts +0 -3
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/internal/seed-vars.d.ts +4 -4
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +42 -5
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +10 -4
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6819 -6555
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/cache-guide/SKILL.md +4 -8
- package/skills/caching/SKILL.md +6 -6
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +185 -32
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/host-router/SKILL.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +42 -19
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +103 -52
- package/skills/prerender/SKILL.md +4 -5
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/router-setup/SKILL.md +0 -2
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/render-handler.md +17 -17
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +86 -22
- package/skills/testing/server-actions.md +16 -16
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +112 -84
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +6 -7
- package/src/cache/cf/cf-cache-types.ts +0 -17
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/handle-snapshot.ts +2 -2
- package/src/cache/types.ts +16 -6
- package/src/cache/vercel/vercel-cache-store.ts +8 -19
- package/src/client-urls/client-urls.ts +202 -153
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +137 -66
- package/src/client-urls/types.ts +30 -15
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/handles/deferred-resolution.ts +3 -2
- package/src/handles/meta.ts +5 -0
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/prerender/build-shell-capture.ts +1 -2
- package/src/prerender/writable-store.ts +2 -3
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +41 -21
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +15 -10
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -13
- package/src/router/router-options.ts +4 -28
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/loader-cache.ts +4 -3
- package/src/router/segment-resolution/mask-nested.ts +5 -3
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +3 -2
- package/src/router/tracing.ts +2 -2
- package/src/router-versions.ts +7 -7
- package/src/router.ts +5 -17
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +86 -48
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +26 -3
- package/src/rsc/shell-capture.ts +137 -51
- package/src/rsc/types.ts +5 -9
- package/src/segment-system.tsx +125 -119
- package/src/server/build-version-table.ts +12 -24
- package/src/server/context.ts +160 -7
- package/src/server/handle-store.ts +80 -12
- package/src/server/request-context.ts +46 -6
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/build-versions.ts +0 -3
- package/src/testing/dispatch.ts +5 -8
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/internal/seed-vars.ts +4 -4
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +79 -9
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +10 -4
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/shell-prerender-phase.ts +1 -5
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
|
@@ -59,7 +59,9 @@ only between a route handler and its child/orphan layouts and parallels (so
|
|
|
59
59
|
their latency overlaps rendering rather than blocking it. See `/loader` →
|
|
60
60
|
"Parallel and streaming".
|
|
61
61
|
|
|
62
|
-
The contract is: **route middleware wraps rendering regardless of transport** (JS-enabled RSC stream or no-JS HTML). During PE re-render, route middleware observes action-set state (cookies, context variables) the same way it does during JS-enabled post-action revalidation.
|
|
62
|
+
The contract is: **route middleware wraps rendering regardless of transport** (JS-enabled RSC stream or no-JS HTML). During PE re-render, route middleware observes action-set state (cookies, context variables) the same way it does during JS-enabled post-action revalidation. When the action throws, the `errorBoundary()` render is wrapped too, in both transports: the boundary reads the middleware's `ctx.set()` values and the response carries its headers.
|
|
63
|
+
|
|
64
|
+
Around the render after an action, route middleware's `ctx.request` is the page's GET: `method` is `"GET"`, with no body, and the headers are the POST's without `content-type` and `content-length`. It decides as it would for a GET of the page. Global middleware, which wraps the action, sees the POST, and so does `getRequestContext().request`. So a method check in route middleware never sees the mutation: it cannot block or answer one, since the action already ran. A header its GET branch sets also lands on the action's response. If a header must stay off mutation responses (a public `Cache-Control`), set it from global middleware.
|
|
63
65
|
|
|
64
66
|
Revalidation is still partial. Route middleware wraps the render pass that
|
|
65
67
|
does happen, but it does not force unrelated outer segments to recompute.
|
|
@@ -256,7 +258,7 @@ export const myMiddleware: Middleware = async (ctx, next) => {
|
|
|
256
258
|
ctx.header("X-Frame-Options", "DENY"); // set one response header
|
|
257
259
|
ctx.headers; // response Headers (stub before next(), the real response after)
|
|
258
260
|
ctx.setLocationState(FlashMessage({ text: "Saved" })); // history state for the client
|
|
259
|
-
ctx.reverse("home"); // URL for a global route name (no ".name", no param auto-fill)
|
|
261
|
+
ctx.reverse("home"); // URL for a global route name (no ".name", no param auto-fill; a missing required param throws)
|
|
260
262
|
ctx.rewrittenFrom; // the visitor's URL when this request was rewritten, else undefined
|
|
261
263
|
ctx.isRewrite(error); // true for a thrown rewrite() (rethrow it from a try/catch)
|
|
262
264
|
|
|
@@ -442,8 +444,10 @@ import type { Middleware } from "@rangojs/router";
|
|
|
442
444
|
|
|
443
445
|
export const maintenance: Middleware = async (ctx, next) => {
|
|
444
446
|
if (!locked || ctx.pathname === "/maintenance") return next();
|
|
445
|
-
//
|
|
446
|
-
|
|
447
|
+
// A rewrite moves a server action or form post to its target, where the
|
|
448
|
+
// action runs. A lock answers mutations itself, so none runs under
|
|
449
|
+
// maintenance. HEAD rewrites like GET, as under .disallowMutations().
|
|
450
|
+
if (ctx.request.method !== "GET" && ctx.request.method !== "HEAD") {
|
|
447
451
|
return new Response("Maintenance", {
|
|
448
452
|
status: 503,
|
|
449
453
|
headers: { "Retry-After": "600" },
|
|
@@ -463,6 +467,16 @@ path("/maintenance", MaintenancePage, { name: "maintenance" }, () => [
|
|
|
463
467
|
]);
|
|
464
468
|
```
|
|
465
469
|
|
|
470
|
+
Without the `Response` branch, `.disallowMutations()` keeps a lock from
|
|
471
|
+
running mutations: a server action or form post under it is an error before
|
|
472
|
+
the action runs (a 500 the client shows as an error, reported through
|
|
473
|
+
`onError` with phase `"routing"`), and GET and HEAD rewrite as usual. The
|
|
474
|
+
`Response` above gives the caller a proper 503 instead.
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
throw rewrite("maintenance").disallowMutations();
|
|
478
|
+
```
|
|
479
|
+
|
|
466
480
|
Targets, like `ctx.reverse` with a path form:
|
|
467
481
|
|
|
468
482
|
```typescript
|
|
@@ -519,7 +533,9 @@ carries `x-rango-rewrite: <target pathname>`.
|
|
|
519
533
|
inside a `cache()` boundary or a `"use cache"` function throws (pass what
|
|
520
534
|
you need in from middleware), and a render other visitors may be served (a
|
|
521
535
|
PPR shell capture, a page the document cache stores) reads `undefined`, as a
|
|
522
|
-
direct visit does.
|
|
536
|
+
direct visit does. A shell keyed by the visitor's URL
|
|
537
|
+
(`rewrite(...).shell("visitor")`, see Caches) reads it, since its key covers
|
|
538
|
+
that URL.
|
|
523
539
|
|
|
524
540
|
**Middleware only, by design.** `rewrite()` is a middleware tool. A route
|
|
525
541
|
handler or a loader (any loader, `ssr: false` included) cannot rewrite: its
|
|
@@ -540,12 +556,59 @@ path("/product/:slug", ProductPage, { name: "product" }, () => [
|
|
|
540
556
|
]);
|
|
541
557
|
```
|
|
542
558
|
|
|
559
|
+
**Mutations: where the middleware sits decides.** A server action or a no-JS
|
|
560
|
+
form post from a rewritten page is posted to the visitor's URL, so the same
|
|
561
|
+
middleware sees it:
|
|
562
|
+
|
|
563
|
+
- **Global middleware** on the visitor's URL runs before the action. A
|
|
564
|
+
rewrite there moves the mutation to the target first, with its body: the
|
|
565
|
+
target's global middleware, then the action, once, then the target's
|
|
566
|
+
route middleware and render, as a direct post to the target. So a
|
|
567
|
+
routing rewrite (a multi-tenant host, an A/B split, a visitor URL with no
|
|
568
|
+
route) keeps its page's forms working, in JS and no-JS. Rewrite every
|
|
569
|
+
method: a rewrite written for GETs only leaves the mutation on the
|
|
570
|
+
visitor's URL, so the action runs on the visitor's route and its render
|
|
571
|
+
replaces the page. A lock must not run the mutation: return a `Response`
|
|
572
|
+
for mutations, or throw `rewrite(...).disallowMutations()`, which refuses
|
|
573
|
+
it with an error before the action runs. It chains with `.shell()` in
|
|
574
|
+
either order.
|
|
575
|
+
- **Route middleware** runs after the action, around the re-render, and
|
|
576
|
+
sees it as the page's GET. A routing rewrite there (A/B, URL override,
|
|
577
|
+
the `discontinued` example above) fires as on a GET, including one
|
|
578
|
+
written for GETs only (`if (ctx.request.method !== "GET") return next()`).
|
|
579
|
+
The action runs once, then the re-render runs as the target (its global
|
|
580
|
+
and route middleware, its render, or its error boundary when the action
|
|
581
|
+
threw), and the page keeps its layout and state. Route middleware cannot
|
|
582
|
+
refuse the action, so `.disallowMutations()` has no effect there: the
|
|
583
|
+
render moves to the target, dev logs a warning on each throw, and
|
|
584
|
+
production logs one `console.error`. Put a lock in global middleware.
|
|
585
|
+
- Global middleware on the target's pass sees the POST, with its headers.
|
|
586
|
+
A no-JS form post keeps its body, so a form-field check (a CSRF token
|
|
587
|
+
read from `ctx.request.clone().formData()`) passes on every pass, and so
|
|
588
|
+
does a mutation a global rewrite moved before its action, whose action
|
|
589
|
+
runs on the target's pass: do not skip a body check when
|
|
590
|
+
`ctx.rewrittenFrom` is set. Always read a clone; a rewrite after middleware
|
|
591
|
+
read the body itself is an error, since the action would get no
|
|
592
|
+
arguments. After a route-middleware rewrite, a JS action's body is gone
|
|
593
|
+
(the action read it), and that pass drops the `content-type` too, so a
|
|
594
|
+
body check gated on the content-type passes over it; that pass never runs
|
|
595
|
+
the action. Global middleware runs once per pass, as for any rewrite, so a
|
|
596
|
+
one-time token belongs in the action. An action's target must be a page
|
|
597
|
+
route: a rewrite that sends a server action or form post that already ran,
|
|
598
|
+
or a JS action before it runs, to a response route (`path.json()`) or a
|
|
599
|
+
path that redirects is an error. A plain POST a global rewrite moves
|
|
600
|
+
reaches a response route with its body, as a direct post would, and so
|
|
601
|
+
does a no-JS form post, whose action the router finds only on a page
|
|
602
|
+
route.
|
|
603
|
+
|
|
543
604
|
**Throw it, don't return it.** A returned rewrite is an error. Where a
|
|
544
605
|
rewrite cannot be honored it is an error that names the rule: a route
|
|
545
606
|
handler or a loader (see above), a server action (use `redirect()`),
|
|
546
|
-
|
|
547
|
-
rejects with the rule) or its middleware
|
|
548
|
-
|
|
607
|
+
a loader fetch (`useFetchLoader`, `load()`: it
|
|
608
|
+
rejects with the rule) or its middleware. A `.disallowMutations()` rewrite
|
|
609
|
+
from global middleware handling a non-GET, non-HEAD request (a JS action or
|
|
610
|
+
a no-JS form post) is an error too, by your choice: it reads
|
|
611
|
+
`rewrite("…") refused a mutation: .disallowMutations() was set`.
|
|
549
612
|
|
|
550
613
|
**try/catch around next().** A thrown rewrite passes through your
|
|
551
614
|
middleware's `catch`. Rethrow it:
|
|
@@ -570,12 +633,26 @@ the trail, `/a -> /b -> /a`) through `onError` with phase `"routing"`.
|
|
|
570
633
|
- The document cache keys a rewritten request by the target's URL, as a
|
|
571
634
|
direct visit. Register the rewriting middleware **before**
|
|
572
635
|
`createDocumentCacheMiddleware()`: a cache hit for the visitor's URL would
|
|
573
|
-
be served before your middleware runs. A page the document cache stores
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
`
|
|
577
|
-
|
|
578
|
-
|
|
636
|
+
be served before your middleware runs. A page the document cache stores
|
|
637
|
+
is rendered as a direct visit to the target: it carries the target's
|
|
638
|
+
pathname and `ctx.rewrittenFrom` is `undefined` there, so a component that
|
|
639
|
+
renders `usePathname()` can mismatch on hydration at the visitor's URL
|
|
640
|
+
when such a page is served to a rewritten request.
|
|
641
|
+
- A PPR shell (`ppr` target) is read and written only by requests on its
|
|
642
|
+
URL. A rewritten request renders live at the visitor's URL, and a direct
|
|
643
|
+
visit captures the target's shell, so values you set before the throw
|
|
644
|
+
never reach it. Pick a shell for the rewritten request with `.shell()` on
|
|
645
|
+
the thrown value: `"visitor"` keys a shell by the visitor's URL, captured
|
|
646
|
+
with the visitor's location and `ctx.rewrittenFrom` (one shell per public
|
|
647
|
+
URL, the right choice for a routing catch-all); `"target"` reads and
|
|
648
|
+
captures the target's shared shell, which shows the target's URL to the
|
|
649
|
+
visitor on a HIT and bakes in what the rewriting middleware set, so keep it
|
|
650
|
+
for a lock or promo page that renders neither:
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
throw rewrite("item", { id }).shell("visitor");
|
|
654
|
+
```
|
|
655
|
+
|
|
579
656
|
- The client's prefetch and history caches keep a rewritten page under the
|
|
580
657
|
visitor's URL, and nothing tells them about the rewrite: a page prefetched
|
|
581
658
|
during a lock shows the lock page for up to `prefetchCacheTTL` after the
|
|
@@ -600,10 +677,29 @@ the trail, `/a -> /b -> /a`) through `onError` with phase `"routing"`.
|
|
|
600
677
|
};
|
|
601
678
|
```
|
|
602
679
|
|
|
680
|
+
**Navigating from a rewritten page.** A navigation, prefetch or action from
|
|
681
|
+
the page diffs against the route that rendered it, as one from a direct
|
|
682
|
+
visit does: the payload carries the target (`metadata.rewriteTarget`), and
|
|
683
|
+
the browser sends it back with the next request from that page. A
|
|
684
|
+
`revalidate()` sees the target's params and URL (`currentParams.id` against
|
|
685
|
+
`nextParams.id` holds a loader on a sort change), and a navigation to the
|
|
686
|
+
page already shown re-renders nothing. A move to another visitor URL with
|
|
687
|
+
the same target (`/promo/a` to `/promo/b`, both rewritten to
|
|
688
|
+
`/promo/landing`) re-runs the route and its loaders, which may read
|
|
689
|
+
`ctx.rewrittenFrom`. The browser only names the page being left: what
|
|
690
|
+
renders still comes from the request's URL and your middleware.
|
|
691
|
+
|
|
603
692
|
**Testing.** `runMiddleware` reports a thrown rewrite as `result.rewrite`
|
|
604
|
-
(`{ pathname, search, routeName? }
|
|
693
|
+
(`{ pathname, search, routeName?, mutationsDisallowed? }`, the last `true`
|
|
694
|
+
only for `.disallowMutations()`). `serveShellRequest` takes a rewritten
|
|
605
695
|
page request through the full production pipeline (document or `partial`
|
|
606
696
|
navigation): the target's middleware, render, status and `x-rango-rewrite`.
|
|
697
|
+
Pass the page's `metadata.rewriteTarget` as `partial.rewriteTarget` (or
|
|
698
|
+
`action.rewriteTarget`) to send a request from it as the browser does.
|
|
699
|
+
With `action` or `form` (from `createActionForm`) it posts a mutation to the
|
|
700
|
+
visitor's URL, so a global rewrite's move, a `.disallowMutations()` refusal
|
|
701
|
+
(it rejects with the refusal) and a route-middleware rewrite's re-render are
|
|
702
|
+
testable too.
|
|
607
703
|
`dispatch` re-dispatches too, for response routes only: it matches the
|
|
608
704
|
visitor's URL first and throws on a component (RSC) route.
|
|
609
705
|
`renderRoute(..., { request: "/shop", rewrite: "/maintenance" })` renders
|
|
@@ -302,6 +302,17 @@ Then inspect:
|
|
|
302
302
|
first paint.
|
|
303
303
|
- the `Server-Timing` header to compare local logs with browser-network timing.
|
|
304
304
|
|
|
305
|
+
## Telling a page is a Rango app
|
|
306
|
+
|
|
307
|
+
Every page sets `window.__rango = { version }` (frozen, one key) from the
|
|
308
|
+
bundled client entry before hydration. `version` is the exact npm semver of
|
|
309
|
+
`@rangojs/router`, nothing else: no build hash, router id or config. It is for
|
|
310
|
+
devtools and support; the router never reads it and no option turns it off.
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
window.__rango; // { version: "0.24.2" }
|
|
314
|
+
```
|
|
315
|
+
|
|
305
316
|
## Zero-overhead defaults
|
|
306
317
|
|
|
307
318
|
`debugPerformance` is off by default, `telemetry` emits nothing unless a sink
|
package/skills/parallel/SKILL.md
CHANGED
|
@@ -206,9 +206,9 @@ parallel(
|
|
|
206
206
|
() => [
|
|
207
207
|
// The loader keeps its own revalidation: by default it re-runs after
|
|
208
208
|
// every action. Scope it here to stop that.
|
|
209
|
-
loader(CategoriesLoader, () => [revalidate(
|
|
209
|
+
loader(CategoriesLoader, () => [revalidate(false)]),
|
|
210
210
|
loading(<SidebarSkeleton />),
|
|
211
|
-
revalidate(
|
|
211
|
+
revalidate(false), // the slot component renders once, then is kept
|
|
212
212
|
]
|
|
213
213
|
)
|
|
214
214
|
```
|
|
@@ -430,7 +430,7 @@ boundary above it: the route's `loading()`, replacing the entire route content
|
|
|
430
430
|
with the route skeleton while one slot refetches. Either scope the slot's
|
|
431
431
|
`revalidate()` (share the route's named contract, as above) or give the slot
|
|
432
432
|
its own `loading()` so the fallback stays local. Don't reach for a bare
|
|
433
|
-
`revalidate(
|
|
433
|
+
`revalidate(false)`: the slot then never refreshes on param changes
|
|
434
434
|
either, and keeps the previous param's content on e.g. a product-to-product
|
|
435
435
|
navigation.
|
|
436
436
|
|
|
@@ -438,7 +438,7 @@ navigation.
|
|
|
438
438
|
it the first time. A slot the browser has not rendered yet has nothing to keep
|
|
439
439
|
showing, so on that first render the decision is clamped to `true` and your
|
|
440
440
|
callback's `false` is ignored; from then on it is honored and the browser keeps
|
|
441
|
-
the copy it already has. So `revalidate(
|
|
441
|
+
the copy it already has. So `revalidate(false)` means "render once, then
|
|
442
442
|
never re-render", never "don't appear".
|
|
443
443
|
|
|
444
444
|
Revalidate callbacks must be synchronous. A callback that returns a Promise is
|
|
@@ -565,7 +565,7 @@ export const shopPatterns = urls(({
|
|
|
565
565
|
{ "@sidebar": () => <CategorySidebar /> },
|
|
566
566
|
() => [
|
|
567
567
|
loader(CategoriesLoader),
|
|
568
|
-
revalidate(
|
|
568
|
+
revalidate(false),
|
|
569
569
|
]
|
|
570
570
|
),
|
|
571
571
|
|
package/skills/ppr/SKILL.md
CHANGED
|
@@ -151,12 +151,42 @@ cache miss (no shell).
|
|
|
151
151
|
|
|
152
152
|
### A request middleware rewrote
|
|
153
153
|
|
|
154
|
-
A
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
A URL's shared shell is read and written only by requests on that URL. A
|
|
155
|
+
request middleware rewrote (`throw rewrite()`, `/middleware`) to a `ppr` route
|
|
156
|
+
neither reads the target's shell nor captures it: it renders live, at the
|
|
157
|
+
visitor's location (`usePathname()`, `ctx.rewrittenFrom`), on every request.
|
|
158
|
+
The target's shell is captured by a direct visit or a `router.prerender()` warm
|
|
159
|
+
of the target's URL, so nothing a rewriting middleware set (a variable, the
|
|
160
|
+
visitor's URL) ends up in the shell every direct visitor gets.
|
|
161
|
+
|
|
162
|
+
The rewrite chooses a shell for the rewritten request with `.shell()`:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// A routing catch-all: each public URL gets a shell of its own.
|
|
166
|
+
throw rewrite("item", { id }).shell("visitor");
|
|
167
|
+
|
|
168
|
+
// A lock or promo page that renders nothing of the visitor's URL. A lock
|
|
169
|
+
// also refuses mutations, which a rewrite otherwise moves to the target.
|
|
170
|
+
throw rewrite("maintenance").shell("target").disallowMutations();
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- `.shell("visitor")` keys the shell by the visitor's URL and the target
|
|
174
|
+
(`visitorShellCacheKey(router, "/summer", "/item/1")` in a test; the target
|
|
175
|
+
route's `cache.searchParams` filter and its own partition apply). The
|
|
176
|
+
capture renders the visitor's location and `ctx.rewrittenFrom`, and a
|
|
177
|
+
variable set before the throw may be baked in, since the key covers the URL
|
|
178
|
+
it came from. Navigation replay uses the same key. A build shell of the
|
|
179
|
+
target is never served to it. Store growth is one shell per public URL;
|
|
180
|
+
`updateTag()` reaches every one of them through the shell's tags.
|
|
181
|
+
- `.shell("target")` reads and captures the target's shared shell from the
|
|
182
|
+
rewritten request. The shell then shows the target's URL to a rewritten
|
|
183
|
+
visitor on a HIT (`usePathname()` hydrates against the address bar, so a
|
|
184
|
+
component that renders it mismatches), and a variable set before the throw
|
|
185
|
+
is baked into the shell direct visitors get. Use it only for a page that
|
|
186
|
+
renders neither.
|
|
187
|
+
|
|
188
|
+
A rewritten navigation without `.shell()` reports
|
|
189
|
+
`x-rango-ppr-replay: BYPASS; reason=rewritten`.
|
|
160
190
|
|
|
161
191
|
### Request-partitioned shells
|
|
162
192
|
|
|
@@ -806,14 +836,17 @@ that settled inside the capture window pinned its capture-time value into the
|
|
|
806
836
|
shared shell; found live as a storefront basket — with the capturing session's
|
|
807
837
|
identifiers — served to anonymous visitors.)
|
|
808
838
|
|
|
809
|
-
### Handles: handler pushes bake; loader pushes keep "nesting = liveness"
|
|
839
|
+
### Handles: handler and bake-lane pushes bake; loader pushes keep "nesting = liveness"
|
|
810
840
|
|
|
811
841
|
- `ctx.use(H)(promise)` from a handler — a TOP-LEVEL pushed promise is awaited
|
|
812
842
|
at capture and BAKED into the shell (bounded by `ppr.captureTimeout`, 15s by
|
|
813
843
|
default).
|
|
814
|
-
-
|
|
815
|
-
|
|
816
|
-
|
|
844
|
+
- `ctx.use(H)(promise)` (or `.defer()`) from an `ssr: false` LOADER, or a
|
|
845
|
+
loader it awaits — awaited at capture and BAKED the same way, judged by the
|
|
846
|
+
value it settles to, as `push(await promise)` would be (#1182). One that
|
|
847
|
+
rejects, or does not settle within `ppr.captureTimeout`, refuses the
|
|
848
|
+
capture, as a handler's does. A loader without `ssr: false` never runs at
|
|
849
|
+
capture, so its pushes are never in the shell.
|
|
817
850
|
- `ctx.use(H)({ x: promise })` from a HANDLER — also baked. The capture waits
|
|
818
851
|
for every promise nested in a handler's push, at any depth of plain objects,
|
|
819
852
|
arrays and JSX props, before it records the handles every HIT replays. Keep
|
|
@@ -967,60 +1000,73 @@ pushes stay as the prelude rendered them, even where a live loader also
|
|
|
967
1000
|
awaits it, while its data is fresh. (They follow the run instead once the
|
|
968
1001
|
entry has lost a pin, because the loader that awaits it then runs.) To keep
|
|
969
1002
|
its pushes live, declare it as its own `loader()` on the route. A push the
|
|
970
|
-
capture cannot record (
|
|
971
|
-
loader record of the page to run: those bodies then still run on each
|
|
972
|
-
in the background, and that push reaches the page (from the loader's
|
|
973
|
-
`cache()` entry when it hits) with the run's value, on a document HIT and
|
|
974
|
-
a client navigation alike. On a document HIT it arrives after hydration.
|
|
975
|
-
|
|
976
|
-
**Handles on a shell HIT.**
|
|
977
|
-
|
|
978
|
-
(
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1003
|
+
capture cannot record (one whose value, once settled, holds a promise) marks
|
|
1004
|
+
every loader record of the page to run: those bodies then still run on each
|
|
1005
|
+
replay, in the background, and that push reaches the page (from the loader's
|
|
1006
|
+
own `cache()` entry when it hits) with the run's value, on a document HIT and
|
|
1007
|
+
on a client navigation alike. On a document HIT it arrives after hydration.
|
|
1008
|
+
|
|
1009
|
+
**Handles on a shell HIT.** The shell holds what its capture's document
|
|
1010
|
+
rendered: the handlers' pushes and the `ssr: false` loaders' pushes, a
|
|
1011
|
+
promise (`push(promise)`, `.defer()`) resolved. A pushed promise is judged by
|
|
1012
|
+
the value it settles to, as `push(await promise)` would be (#1182). A
|
|
1013
|
+
live-lane loader's push is never in a shell.
|
|
1014
|
+
|
|
1015
|
+
Four things follow, in this order of scope:
|
|
1016
|
+
|
|
1017
|
+
1. **What is in a shell's HTML.** The capture awaits a promise an
|
|
1018
|
+
`ssr: false` loader (or a loader it awaits) pushes, under
|
|
1019
|
+
`ppr.captureTimeout`, and bakes the value. One that settles to a value
|
|
1020
|
+
holding a promise is live: the shell renders no element for it. A loader
|
|
1021
|
+
without `ssr: false` never runs at capture, so nothing of it is ever in
|
|
1022
|
+
the shell.
|
|
1023
|
+
2. **A pushed promise that fails refuses the capture.** One that rejects, or
|
|
1024
|
+
does not settle within `ppr.captureTimeout`, keeps the route on MISS, as
|
|
1025
|
+
a handler's does, and the warning names the loader and the handle. Catch
|
|
1026
|
+
it and push a fallback, or push it from a live-lane loader.
|
|
1027
|
+
3. **On a shell HIT every loader push made during the request arrives after
|
|
988
1028
|
hydration**, a live-lane loader's included. The document hydrates with
|
|
989
1029
|
exactly the handle data the shell's HTML was rendered from (the handlers'
|
|
990
|
-
pushes and the
|
|
991
|
-
|
|
992
|
-
|
|
1030
|
+
pushes and the pushes of the loaders that ran at capture), and the
|
|
1031
|
+
request's own pushes follow as a normal `useHandle` update.
|
|
1032
|
+
4. **`useHandle` on every document, `ppr` or not:** a reader that React is
|
|
993
1033
|
hydrating reads the handle data its HTML was rendered from, wherever its
|
|
994
1034
|
boundary hydrates, and takes the newer data right after. So a reader
|
|
995
1035
|
inside the `loading()` or `<Suspense>` of a loader that pushes after an
|
|
996
1036
|
`await` hydrates clean.
|
|
997
1037
|
|
|
998
|
-
| On the HIT's request
|
|
999
|
-
|
|
|
1000
|
-
| a handler's push, a pinned `ssr: false` loader's
|
|
1001
|
-
| a
|
|
1002
|
-
| a live-lane loader's push
|
|
1003
|
-
| the run of an `ssr: false` loader the shell has no pin for
|
|
1038
|
+
| On the HIT's request | In the HIT's HTML | The client |
|
|
1039
|
+
| ------------------------------------------------------------------------------------------ | ---------------------- | ------------------------------------------------- |
|
|
1040
|
+
| a handler's push, a pinned `ssr: false` loader's push (`push(promise)` and `.defer()` too) | yes (the shell has it) | hydrates with it |
|
|
1041
|
+
| a push by an `ssr: false` loader whose value holds a promise | no | gets it right after hydration |
|
|
1042
|
+
| a live-lane loader's push | no | gets it right after hydration |
|
|
1043
|
+
| the run of an `ssr: false` loader the shell has no pin for | the capture's push | hydrates with the capture's, then shows the run's |
|
|
1004
1044
|
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1045
|
+
So a title an `ssr: false` loader pushes to `Meta`, promise or not, is in
|
|
1046
|
+
every HIT's `<head>`: a crawler and a link preview see it. It bakes with the
|
|
1047
|
+
shell and is invalidated with it (its TTL, its tags, `updateTag()`). A value
|
|
1048
|
+
that must stay per request comes from a loader without `ssr: false`, which
|
|
1049
|
+
nothing has to read:
|
|
1009
1050
|
|
|
1010
1051
|
```ts
|
|
1011
|
-
|
|
1012
|
-
ctx.use(Notes)(
|
|
1052
|
+
const StockNote = createLoader(async (ctx) => {
|
|
1053
|
+
ctx.use(Notes)(db.stockNote(ctx.params.id)); // live: after hydration on a HIT
|
|
1054
|
+
return null;
|
|
1055
|
+
});
|
|
1056
|
+
path("/product/:id", ProductPage, { name: "product", ppr: true }, () => [
|
|
1057
|
+
loader(Product, { ssr: false }), // bakes: its data and its pushes
|
|
1058
|
+
loader(StockNote), // live lane: runs on every request
|
|
1059
|
+
]);
|
|
1013
1060
|
```
|
|
1014
1061
|
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
says "per request", the same as a promise in the loader's data.
|
|
1062
|
+
Or nest the value in a settled push (`{ label, count: promise }`): the whole
|
|
1063
|
+
push then stays live, and every pinned body of the page runs on each HIT.
|
|
1018
1064
|
|
|
1019
1065
|
A pinned loader's pushes are exactly the ones its capture made. If its body
|
|
1020
|
-
pushes a
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
delivered on every replay.
|
|
1066
|
+
pushes a value only on some runs (behind a condition), a replay that runs it
|
|
1067
|
+
shows the capture's answer, a push or none, never a later run's next to the
|
|
1068
|
+
pinned data. A push whose value holds a promise is the exception by design:
|
|
1069
|
+
it is delivered on every replay.
|
|
1024
1070
|
|
|
1025
1071
|
One arrangement mixes sources by design. A loader that reads a
|
|
1026
1072
|
`cache()`-bound loader with `ctx.use()` before that binding starts (a loader
|
|
@@ -1299,7 +1345,10 @@ shell-cache only public/shared pages, keep per-user content in live-lane
|
|
|
1299
1345
|
loaders (no `ssr: false`) or nested promises in bake-lane loader data — NOT in
|
|
1300
1346
|
handler output or plain bake-lane container data — mark the variable
|
|
1301
1347
|
`{ cache: false }` so a stray shell read refuses the capture, or key per
|
|
1302
|
-
variant at the CDN tier.
|
|
1348
|
+
variant at the CDN tier. A variable a middleware sets before `throw rewrite()`
|
|
1349
|
+
never reaches the target's shared shell: a rewritten request captures none
|
|
1350
|
+
unless its rewrite opts in with `.shell("target")` (see "A request middleware
|
|
1351
|
+
rewrote"); with `.shell("visitor")` it lands only in that URL's own shell.
|
|
1303
1352
|
|
|
1304
1353
|
## What always renders without a shell
|
|
1305
1354
|
|
|
@@ -1307,7 +1356,9 @@ Non-GET, non-partial RSC/action/loader fetches, partial requests without an
|
|
|
1307
1356
|
eligible captured segment snapshot, per-request CSP nonce, `streamMode:
|
|
1308
1357
|
"allReady"`, redirects, 404s, error renders, routes without `ppr`, a document
|
|
1309
1358
|
request the route's own `cache()` scope refuses (`cache(false)`, or a
|
|
1310
|
-
`condition()` returning false for it),
|
|
1359
|
+
`condition()` returning false for it), a request middleware rewrote whose
|
|
1360
|
+
rewrite chose no shell (`rewrite(...).shell()`), and any store without the
|
|
1361
|
+
shell family.
|
|
1311
1362
|
A stored shell is invalidated when
|
|
1312
1363
|
`React.version` changes (postponed state is build-coupled) or when the router's
|
|
1313
1364
|
document version changes, so a deploy that changes the router self-heals via
|
|
@@ -1020,8 +1020,7 @@ in dev. A removed page is the exception: its marker answers 404 in dev too.
|
|
|
1020
1020
|
that only a refresh produced.
|
|
1021
1021
|
- **Connected clients can lag.** A refresh has no response to reach open tabs:
|
|
1022
1022
|
a client that prefetched the page keeps its copy for `prefetchCacheTTL`
|
|
1023
|
-
(default 300s)
|
|
1024
|
-
them if the window matters.
|
|
1023
|
+
(default 300s, in memory only). Lower it if the window matters.
|
|
1025
1024
|
- **Entries are per deploy.** Keys carry the router's data version, so a deploy
|
|
1026
1025
|
that changes server code starts from the build entries again; a client-only
|
|
1027
1026
|
deploy keeps refreshed pages.
|
|
@@ -1330,9 +1329,9 @@ write that started before an invalidation of one of its tags is refused.
|
|
|
1330
1329
|
### Limits worth knowing
|
|
1331
1330
|
|
|
1332
1331
|
- **Only the router's own handler.** A warm goes through `router.fetch`, the
|
|
1333
|
-
handler `createRouter`'s `cache
|
|
1334
|
-
entry that builds its own `createRSCHandler({ cache
|
|
1335
|
-
|
|
1332
|
+
handler `createRouter`'s `cache` and `nonce` configure. An app
|
|
1333
|
+
entry that builds its own `createRSCHandler({ cache })` is not what a warm
|
|
1334
|
+
runs: put the option on `createRouter`.
|
|
1336
1335
|
- **Document records only.** Navigation (`partial:`) and intercept records
|
|
1337
1336
|
fill on the first navigation.
|
|
1338
1337
|
- **One location at a time.** A warm does not reach a copy another location
|
package/skills/rango/SKILL.md
CHANGED
|
@@ -111,6 +111,7 @@ stated, greppable contract.
|
|
|
111
111
|
| debug a slow request | `debugPerformance` / telemetry | /observability |
|
|
112
112
|
| share config across routes | factory returning a helper array | /composability |
|
|
113
113
|
| compose a sub-app / module | `include()` | /route |
|
|
114
|
+
| lay out a whole app | section modules, guards, groups | /app-structure |
|
|
114
115
|
| modal / soft navigation | `intercept()` | /intercept |
|
|
115
116
|
| route group of client components | `clientUrls()` in `"use client"` | /client-urls |
|
|
116
117
|
| set meta/breadcrumbs from loader data | `ctx.use(Handle)` in the loader | /loader |
|
|
@@ -306,8 +307,10 @@ Grouped by concern — read when you need to…
|
|
|
306
307
|
| Skill | Description |
|
|
307
308
|
| ------------------------- | -------------------------------------------------------------------------- |
|
|
308
309
|
| `/router-setup` | Create and configure the RSC router |
|
|
310
|
+
| `/app-structure` | Whole-app recipes: large app with admin, multi-app, content site, shop |
|
|
309
311
|
| `/route` | Define routes with `urls()`, `path()`, and `include()` |
|
|
310
312
|
| `/client-urls` | Client-component route groups with `clientUrls()` — no handlers |
|
|
313
|
+
| `/ux-architecture` | Recipes for fast, stable navigation with `urls()` and `clientUrls()` |
|
|
311
314
|
| `/layout` | Layouts that wrap child routes |
|
|
312
315
|
| `/parallel` | Multi-column layouts and sidebars |
|
|
313
316
|
| `/intercept` | Modal/slide-over patterns for soft navigation |
|
|
@@ -617,7 +617,10 @@ path.json(
|
|
|
617
617
|
|
|
618
618
|
A dot-local name (`ctx.reverse(".comments")`) is a type error here. The
|
|
619
619
|
runtime reverse has no scope to resolve it against, so untyped (JS) code that
|
|
620
|
-
calls it throws `Unknown route` and the route answers 500.
|
|
620
|
+
calls it throws `Unknown route` and the route answers 500. Leaving out a
|
|
621
|
+
required param (`ctx.reverse("blog.api.comments")`) throws
|
|
622
|
+
`Missing param "slug" for route "blog.api.comments"`, with or without a params
|
|
623
|
+
object; it never returns the `/:slug` pattern as a URL.
|
|
621
624
|
|
|
622
625
|
## Content Negotiation
|
|
623
626
|
|
|
@@ -266,8 +266,6 @@ Notes:
|
|
|
266
266
|
a cleared cache) on every build; the build prints one note when that applies.
|
|
267
267
|
A router that renders no such action keeps its cache without one (`/use-cache`,
|
|
268
268
|
`/cache-guide`).
|
|
269
|
-
- `createRouter({ version })` takes effect on every preset, including `node`
|
|
270
|
-
and `vercel`. It replaces both computed cache versions with that exact value.
|
|
271
269
|
- `@rangojs/router/vite` also exports `poke()`, a dev-server plugin: type `e`
|
|
272
270
|
then Enter (or Ctrl+R where the terminal passes it through) to full-reload
|
|
273
271
|
the browser.
|
package/skills/scripts/SKILL.md
CHANGED
|
@@ -133,6 +133,17 @@ re-pushed inline script. The GTM demo does exactly this: a root-layout `Script`
|
|
|
133
133
|
bootstrap fires the first page_view on document load, and a `"use client"`
|
|
134
134
|
`<GtmPageViews>` component fires a page_view on every subsequent soft navigation.
|
|
135
135
|
|
|
136
|
+
**Key that component on a handle value that carries the page's path, not on the
|
|
137
|
+
URL.** Handle values arrive on their own channel, by design: on a page React
|
|
138
|
+
holds in a transition (`transition()`, a same-route search change, a prefetched
|
|
139
|
+
click) the URL changes before the destination's `content_group` and title land,
|
|
140
|
+
so an effect keyed on `usePathname()` sends the previous page's tagging. Push
|
|
141
|
+
the navigation's `pathname + search` into your `Gtm` handle (collected as `page.path`) from a root
|
|
142
|
+
`parallel()` slot with `revalidate(true)`, read the tagging with `useHandle(Gtm)`,
|
|
143
|
+
and send the page_view when `page.path` differs from the last path sent (seed it
|
|
144
|
+
with the landing path, which the bootstrap already sent). The pattern, its
|
|
145
|
+
trade-offs and the code are in [hooks](../hooks/handle-and-actions.md#when-handle-values-change-by-design).
|
|
146
|
+
|
|
136
147
|
## The inline-self-inject rule (GTM/GA4/Segment)
|
|
137
148
|
|
|
138
149
|
If an inline bootstrap must run **before** an external loader, do NOT push the
|
|
@@ -202,6 +213,6 @@ warns once per key. See `/ppr` → "What always renders without a shell".
|
|
|
202
213
|
- **Partytown / web-worker offloading** — push the worker config with
|
|
203
214
|
`type: "text/partytown"` and wire Partytown's own nonce config manually.
|
|
204
215
|
|
|
205
|
-
A full GTM + GA4-style integration (page_view on first render + soft nav, nonce,
|
|
216
|
+
A full GTM + GA4-style integration (page_view on first render + soft nav keyed on a path-carrying handle value, nonce,
|
|
206
217
|
ecommerce events) lives in the router repository's `tests/vite-rsc-demo` app
|
|
207
218
|
(not shipped in this package).
|