@rangojs/router 0.20.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 +39 -15
- package/dist/types/browser/event-controller.d.ts +49 -0
- package/dist/types/browser/history-state.d.ts +16 -4
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/navigation-transaction.d.ts +7 -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/rango-state.d.ts +3 -1
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +25 -0
- package/dist/types/browser/react/location-state-shared.d.ts +84 -10
- package/dist/types/browser/react/location-state.d.ts +7 -2
- 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 +5 -6
- package/dist/types/cache/cf/cf-cache-store.d.ts +34 -7
- package/dist/types/cache/cf/cf-cache-types.d.ts +12 -4
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +59 -50
- package/dist/types/cache/index.d.ts +3 -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 +29 -4
- 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 +84 -24
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +35 -17
- package/dist/types/client-urls/navigation.d.ts +7 -1
- package/dist/types/client-urls/optimistic-location.d.ts +10 -4
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +18 -1
- 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/build-shell-capture.d.ts +8 -6
- 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 +15 -1
- package/dist/types/router/navigation-snapshot.d.ts +8 -1
- 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 +32 -1
- package/dist/types/router/router-options.d.ts +39 -5
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +23 -10
- 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/router-versions.d.ts +39 -0
- package/dist/types/rsc/handler-context.d.ts +5 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +9 -6
- 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 +12 -6
- 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 +53 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +89 -46
- package/dist/types/server/request-context.d.ts +84 -9
- 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/build-versions.d.ts +32 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +3 -1
- package/dist/types/testing/index.d.ts +6 -0
- package/dist/types/testing/internal/seed-vars.d.ts +7 -2
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/location-state-key.d.ts +5 -1
- package/dist/types/testing/render-route.d.ts +77 -6
- package/dist/types/testing/serve-shell-request.d.ts +55 -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/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +15 -5
- 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/build-versions.d.ts +393 -0
- package/dist/types/vite/discovery/installed-packages.d.ts +26 -0
- package/dist/types/vite/discovery/router-versions-phase.d.ts +52 -0
- package/dist/types/vite/discovery/shell-prerender-phase.d.ts +7 -17
- package/dist/types/vite/discovery/state.d.ts +25 -0
- package/dist/types/vite/encryption-key.d.ts +18 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/plugin-types.d.ts +27 -0
- package/dist/types/vite/plugins/{version-injector.d.ts → entry-bootstrap-injector.d.ts} +4 -6
- package/dist/types/vite/plugins/expose-ids/export-analysis.d.ts +1 -0
- package/dist/types/vite/plugins/expose-ids/router-transform.d.ts +14 -0
- package/dist/types/vite/plugins/stable-rsc-output.d.ts +51 -0
- package/dist/types/vite/plugins/version-plugin.d.ts +9 -11
- package/dist/types/vite/plugins/virtual-entries.d.ts +18 -1
- package/dist/types/vite/router-discovery.d.ts +6 -0
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/types/vite/utils/client-chunks.d.ts +2 -0
- package/dist/types/vite/utils/compare-strings.d.ts +7 -0
- package/dist/types/vite/utils/package-resolution.d.ts +8 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +2 -0
- package/dist/vite/index.js +1707 -931
- package/package.json +12 -3
- package/skills/cache-guide/SKILL.md +102 -37
- package/skills/caching/SKILL.md +56 -9
- package/skills/catalog.json +1 -1
- package/skills/client-urls/SKILL.md +16 -0
- package/skills/cloudflare/SKILL.md +133 -1
- package/skills/comparison/references/framework-comparison.md +17 -13
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +25 -0
- package/skills/hooks/handle-and-actions.md +5 -0
- package/skills/hooks/navigation.md +38 -0
- package/skills/hooks/state.md +213 -11
- package/skills/hooks/urls.md +40 -1
- package/skills/host-router/SKILL.md +31 -0
- package/skills/intercept/SKILL.md +6 -0
- package/skills/loader/SKILL.md +25 -5
- package/skills/middleware/SKILL.md +2 -0
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +150 -48
- package/skills/prerender/SKILL.md +651 -10
- package/skills/rango/SKILL.md +9 -7
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/router-setup/SKILL.md +15 -1
- package/skills/testing/SKILL.md +32 -27
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/client-components.md +193 -8
- package/skills/testing/handles.md +2 -0
- package/skills/testing/loader.md +1 -1
- package/skills/testing/middleware.md +1 -1
- package/skills/testing/render-handler.md +18 -18
- package/skills/testing/serve-shell-request.md +167 -22
- package/skills/testing/server-actions.md +17 -17
- package/skills/testing/server-tree.md +1 -1
- package/skills/typesafety/generated-files-and-cli.md +1 -1
- package/skills/use-cache/SKILL.md +25 -10
- package/skills/vercel/SKILL.md +5 -3
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/bin/rango.ts +1 -1
- package/src/browser/event-controller.ts +119 -2
- package/src/browser/history-state.ts +39 -17
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +104 -41
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +24 -12
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +58 -14
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/rango-state.ts +3 -1
- package/src/browser/react/Link.tsx +37 -43
- package/src/browser/react/NavigationProvider.tsx +85 -1
- package/src/browser/react/context.ts +30 -0
- package/src/browser/react/location-state-shared.ts +200 -34
- package/src/browser/react/location-state.ts +28 -77
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +3 -0
- 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 +93 -31
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +43 -7
- 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 +48 -36
- package/src/cache/cf/cf-cache-store.ts +91 -66
- package/src/cache/cf/cf-cache-types.ts +12 -4
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +118 -86
- package/src/cache/index.ts +5 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +52 -5
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +90 -24
- package/src/cache/vercel/vercel-cache-store.ts +68 -26
- package/src/client-urls/client-root.tsx +1 -0
- package/src/client-urls/navigation.ts +10 -0
- package/src/client-urls/optimistic-location.ts +10 -4
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +36 -0
- 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/build-shell-capture.ts +11 -9
- 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/manifest.ts +2 -0
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +239 -54
- package/src/router/navigation-snapshot.ts +25 -6
- 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/request-classification.ts +15 -12
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +35 -1
- package/src/router/router-options.ts +45 -5
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +110 -77
- 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-versions.ts +60 -0
- package/src/router.ts +107 -1
- package/src/rsc/full-payload.ts +6 -2
- package/src/rsc/handler-context.ts +5 -0
- package/src/rsc/handler.ts +64 -7
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +65 -12
- package/src/rsc/shell-build-manifest.ts +17 -12
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +144 -45
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +12 -6
- 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 +130 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +357 -127
- package/src/server/request-context.ts +149 -12
- package/src/ssr/ssr-root.tsx +32 -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/build-versions.ts +64 -0
- package/src/testing/dispatch.ts +6 -0
- package/src/testing/dom.entry.ts +1 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +4 -0
- package/src/testing/index.ts +22 -0
- package/src/testing/internal/context.ts +2 -1
- package/src/testing/internal/seed-vars.ts +14 -2
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/location-state-key.ts +18 -8
- package/src/testing/render-handler.ts +2 -1
- package/src/testing/render-route.tsx +293 -46
- package/src/testing/serve-shell-request.ts +262 -37
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/testing/vitest-stubs/version.ts +4 -1
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +15 -5
- 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/build-versions.ts +1281 -0
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/installed-packages.ts +159 -0
- package/src/vite/discovery/prerender-collection.ts +38 -1
- package/src/vite/discovery/router-versions-phase.ts +336 -0
- package/src/vite/discovery/shell-prerender-phase.ts +33 -43
- package/src/vite/discovery/state.ts +36 -1
- package/src/vite/discovery/virtual-module-codegen.ts +7 -2
- package/src/vite/encryption-key.ts +101 -9
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugin-types.ts +28 -0
- package/src/vite/plugins/{version-injector.ts → entry-bootstrap-injector.ts} +7 -22
- package/src/vite/plugins/expose-ids/export-analysis.ts +1 -1
- package/src/vite/plugins/expose-ids/router-transform.ts +36 -15
- package/src/vite/plugins/expose-internal-ids.ts +12 -3
- package/src/vite/plugins/stable-rsc-output.ts +147 -0
- package/src/vite/plugins/version-plugin.ts +19 -24
- package/src/vite/plugins/version.d.ts +17 -2
- package/src/vite/plugins/virtual-entries.ts +39 -6
- package/src/vite/rango.ts +28 -3
- package/src/vite/router-discovery.ts +96 -13
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/src/vite/utils/client-chunks.ts +5 -1
- package/src/vite/utils/compare-strings.ts +9 -0
- package/src/vite/utils/package-resolution.ts +9 -0
- package/src/vite/utils/prerender-utils.ts +4 -1
- package/src/vite/utils/shared-utils.ts +1 -1
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/segment-content-promise.ts +0 -67
|
@@ -33,11 +33,22 @@ import type {
|
|
|
33
33
|
SegmentHandleData,
|
|
34
34
|
} from "../cache/types.js";
|
|
35
35
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
36
|
+
import type { RouterVersions } from "../router-versions.js";
|
|
37
|
+
import type { OnDemandRouteConfig } from "../prerender/on-demand.js";
|
|
38
|
+
import { isPrerenderPersonalizationError } from "../prerender/producer-guard.js";
|
|
39
|
+
import { isDataNotFoundError } from "../errors.js";
|
|
36
40
|
|
|
37
41
|
export interface PrerenderMatchDeps<TEnv = any> {
|
|
38
42
|
/** Owning router id; scopes bake-time root-scope/search-schema lookups the
|
|
39
43
|
* same way reqCtx._routerId does at runtime (issue #762). */
|
|
40
44
|
routerId?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The owning router's cache versions. Set on the requestless on-demand
|
|
47
|
+
* producer's context as `_versions`, so cache stores built inside the render
|
|
48
|
+
* key under the router's pair like a live request does instead of the
|
|
49
|
+
* whole-build fallback (server/build-version-table.ts getCacheVersions).
|
|
50
|
+
*/
|
|
51
|
+
versions?: RouterVersions;
|
|
41
52
|
findMatch: (
|
|
42
53
|
pathname: string,
|
|
43
54
|
) => RouteMatchResult<TEnv> | null | Promise<RouteMatchResult<TEnv> | null>;
|
|
@@ -66,6 +77,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
66
77
|
buildEnv?: TEnv,
|
|
67
78
|
/** Dev-only: check getParams() for passthrough routes to skip unknown params. */
|
|
68
79
|
devMode?: boolean,
|
|
80
|
+
/**
|
|
81
|
+
* Requestless on-demand refresh (router.prerender()). Arms the personalization
|
|
82
|
+
* guard: cookies()/headers()/response mutations throw a
|
|
83
|
+
* PrerenderPersonalizationError so the caller maps a personalized render to
|
|
84
|
+
* skipped-personalized instead of baking a shared payload. `buildEnv` here is
|
|
85
|
+
* the live trigger env, not the shared build env.
|
|
86
|
+
*/
|
|
87
|
+
onDemand?: boolean,
|
|
69
88
|
): Promise<{
|
|
70
89
|
segments: SerializedSegmentData[];
|
|
71
90
|
/** RSC-encoded handle map ("" when none) — see handle-snapshot.ts. Encoded in
|
|
@@ -79,6 +98,11 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
79
98
|
* the sinks store it as-is (no longer merge raw records). */
|
|
80
99
|
interceptHandles?: string;
|
|
81
100
|
passthrough?: true;
|
|
101
|
+
/** On-demand only: the render hit `notFound()` and nothing else failed. */
|
|
102
|
+
notFound?: true;
|
|
103
|
+
/** Route-level onDemand config (ttl/tags) read off the route entry; only set
|
|
104
|
+
* when `onDemand` and the route declared an onDemand object literal. */
|
|
105
|
+
onDemandConfig?: OnDemandRouteConfig;
|
|
82
106
|
/**
|
|
83
107
|
* The matched route entry's `ppr` path option, surfaced so the build
|
|
84
108
|
* collection can flag Prerender+ppr routes as build-time shell candidates
|
|
@@ -109,383 +133,467 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
109
133
|
passthrough: true as const,
|
|
110
134
|
});
|
|
111
135
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const manifestEntry = await loadManifest(
|
|
115
|
-
matched.entry,
|
|
116
|
-
matched.routeKey,
|
|
117
|
-
pathname,
|
|
118
|
-
undefined,
|
|
119
|
-
false,
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
// 3. Build ancestor chain [root, ..., route]
|
|
123
|
-
const entries: EntryData[] = [];
|
|
124
|
-
for (const entry of traverseBack(manifestEntry)) {
|
|
125
|
-
entries.push(entry);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// 3b. Dev-mode passthrough shortcut: if the route is a Passthrough route
|
|
129
|
-
// and has getParams(), check if the matched params are in the known list.
|
|
130
|
-
// In production, only known params are pre-rendered; unknown params fall
|
|
131
|
-
// through to the live handler. Mirror that behavior in dev mode to avoid
|
|
132
|
-
// rendering unknown params with build: true.
|
|
133
|
-
// Vars collected from getParams() probe — merged into render context below.
|
|
134
|
-
let devProbeBuildVars: Record<string, any> | undefined;
|
|
135
|
-
|
|
136
|
-
if (devMode && matchedPassthroughRoute) {
|
|
137
|
-
const routeEntry = entries.find(
|
|
138
|
-
(
|
|
139
|
-
e,
|
|
140
|
-
): e is EntryData & {
|
|
141
|
-
type: "route";
|
|
142
|
-
prerenderDef: { getParams: (ctx: any) => Promise<any[]> | any[] };
|
|
143
|
-
} =>
|
|
144
|
-
e.type === "route" &&
|
|
145
|
-
!!(e as any).isPassthrough &&
|
|
146
|
-
!!(e as any).prerenderDef?.getParams,
|
|
147
|
-
);
|
|
148
|
-
if (routeEntry) {
|
|
149
|
-
try {
|
|
150
|
-
const probeBuildVars: Record<string, any> = {};
|
|
151
|
-
const knownParamsList = await routeEntry.prerenderDef.getParams({
|
|
152
|
-
build: true as const,
|
|
153
|
-
dev: true,
|
|
154
|
-
set: ((keyOrVar: any, value: any) => {
|
|
155
|
-
contextSet(probeBuildVars, keyOrVar, value);
|
|
156
|
-
}) as any,
|
|
157
|
-
reverse: createReverseFunction(deps.mergedRouteMap),
|
|
158
|
-
get env() {
|
|
159
|
-
if (buildEnv !== undefined) return buildEnv;
|
|
160
|
-
throw new Error(
|
|
161
|
-
"[rango] ctx.env is not available during dev-mode getParams(). " +
|
|
162
|
-
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
163
|
-
);
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
// Compare only the keys returned by getParams — ignore mount params
|
|
167
|
-
// from include() prefixes that aren't part of the handler's params.
|
|
168
|
-
const isKnown = knownParamsList.some((known: Record<string, any>) => {
|
|
169
|
-
const knownKeys = Object.keys(known);
|
|
170
|
-
return knownKeys.every(
|
|
171
|
-
(k) => String(known[k]) === String(matchedParams[k]),
|
|
172
|
-
);
|
|
173
|
-
});
|
|
174
|
-
if (!isKnown) {
|
|
175
|
-
return passthroughResult();
|
|
176
|
-
}
|
|
177
|
-
// Preserve vars set by getParams() for the render context
|
|
178
|
-
if (hasContextVars(probeBuildVars)) {
|
|
179
|
-
devProbeBuildVars = probeBuildVars;
|
|
180
|
-
}
|
|
181
|
-
} catch (err: any) {
|
|
182
|
-
// Mirror production semantics (prerender-collection.ts):
|
|
183
|
-
// Skip errors are intentional — treat as passthrough.
|
|
184
|
-
// All other errors propagate so dev surfaces them.
|
|
185
|
-
if (err?.name === "Skip") {
|
|
186
|
-
return passthroughResult();
|
|
187
|
-
}
|
|
188
|
-
throw err;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
136
|
+
// Set inside the render; out here for the notFound() outcome at the end.
|
|
137
|
+
let onDemandConfig: OnDemandRouteConfig | undefined;
|
|
192
138
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// In dev mode, merge vars from the getParams() probe if the caller
|
|
199
|
-
// didn't provide buildVars (production passes them from expandPrerenderRoutes).
|
|
200
|
-
const effectiveBuildVars = buildVars ?? devProbeBuildVars;
|
|
201
|
-
const variables: Record<string, any> = effectiveBuildVars
|
|
202
|
-
? { ...effectiveBuildVars }
|
|
203
|
-
: {};
|
|
204
|
-
const stubRes = new Response(null, { status: 200 });
|
|
205
|
-
const minimalRequestContext: RequestContext<TEnv> = {
|
|
206
|
-
env: buildEnv ?? ({} as TEnv),
|
|
207
|
-
request: new Request("http://prerender" + pathname),
|
|
208
|
-
url: new URL("http://prerender" + pathname),
|
|
209
|
-
originalUrl: new URL("http://prerender" + pathname),
|
|
210
|
-
pathname,
|
|
211
|
-
searchParams: new URLSearchParams(),
|
|
212
|
-
_variables: variables,
|
|
213
|
-
build: true,
|
|
214
|
-
// Inert here: the prerender-collect / static-render pass has no live
|
|
215
|
-
// PPR-shell decision to gate; dynamic() reaches the shell axis only on a
|
|
216
|
-
// live request or a shell capture.
|
|
217
|
-
dynamic: () => {},
|
|
218
|
-
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
219
|
-
set: ((keyOrVar: any, value: any) => {
|
|
220
|
-
contextSet(variables, keyOrVar, value);
|
|
221
|
-
}) as any,
|
|
222
|
-
params: matchedParams,
|
|
223
|
-
res: stubRes,
|
|
224
|
-
cookie: () => undefined,
|
|
225
|
-
cookies: () => ({}),
|
|
226
|
-
_readCookie: () => undefined,
|
|
227
|
-
_readCookies: () => ({}),
|
|
228
|
-
setCookie: () => {},
|
|
229
|
-
deleteCookie: () => {},
|
|
230
|
-
header: () => {},
|
|
231
|
-
setStatus: () => {},
|
|
232
|
-
_setStatus: () => {},
|
|
233
|
-
_rotateStateCookie: () => {},
|
|
234
|
-
_setKeepCacheDirective: () => {},
|
|
235
|
-
_setFreshReadsCookie: () => {},
|
|
236
|
-
use: (() => {
|
|
237
|
-
throw new Error("use() not available during pre-rendering");
|
|
238
|
-
}) as any,
|
|
239
|
-
method: "GET",
|
|
240
|
-
_handleStore: handleStore,
|
|
241
|
-
_requestTags: new Set<string>(),
|
|
242
|
-
waitUntil: () => {},
|
|
243
|
-
onResponse: () => {},
|
|
244
|
-
_onResponseCallbacks: [],
|
|
245
|
-
// No theme at build time: no request cookie.
|
|
246
|
-
_readTheme: () => undefined,
|
|
247
|
-
setLocationState() {},
|
|
248
|
-
_locationState: undefined,
|
|
249
|
-
_renderBarrier: Promise.resolve(),
|
|
250
|
-
_resolveRenderBarrier: () => {},
|
|
251
|
-
_reportedErrors: new WeakSet<object>(),
|
|
252
|
-
reverse: createReverseFunction(
|
|
253
|
-
deps.mergedRouteMap,
|
|
139
|
+
const rendered = runWithRouterContext(routerCtx, () =>
|
|
140
|
+
routerCtx.getContext().runIsolated(matched.routeKey, async () => {
|
|
141
|
+
// 2. Load the manifest entry tree
|
|
142
|
+
const manifestEntry = await loadManifest(
|
|
143
|
+
matched.entry,
|
|
254
144
|
matched.routeKey,
|
|
255
|
-
matchedParams,
|
|
256
|
-
matched.routeKey
|
|
257
|
-
? isRouteRootScoped(matched.routeKey, deps.routerId)
|
|
258
|
-
: undefined,
|
|
259
|
-
),
|
|
260
|
-
// Scope the bake context's own lookups (createPrerenderContext reads it
|
|
261
|
-
// through the request ALS) per router as well.
|
|
262
|
-
_routerId: deps.routerId,
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
return runWithRequestContext(minimalRequestContext, async () => {
|
|
266
|
-
// 6. Create prerender context with synthetic URL.
|
|
267
|
-
// Prerender handlers get params, pathname, url, searchParams, search,
|
|
268
|
-
// reverse, use(handle), and optionally env (when buildEnv is configured).
|
|
269
|
-
const buildCtx = createPrerenderContext<TEnv>(
|
|
270
|
-
matchedParams,
|
|
271
145
|
pathname,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
variables,
|
|
275
|
-
matchedPassthroughRoute,
|
|
276
|
-
buildEnv,
|
|
277
|
-
devMode,
|
|
146
|
+
undefined,
|
|
147
|
+
false,
|
|
278
148
|
);
|
|
279
149
|
|
|
280
|
-
//
|
|
281
|
-
|
|
150
|
+
// 3. Build ancestor chain [root, ..., route]
|
|
151
|
+
const entries: EntryData[] = [];
|
|
152
|
+
for (const entry of traverseBack(manifestEntry)) {
|
|
153
|
+
entries.push(entry);
|
|
154
|
+
}
|
|
282
155
|
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
156
|
+
// On-demand refresh: read the route's onDemand config (ttl/tags) off the
|
|
157
|
+
// retained route entry. `tags` is a function, so it can't ride the trie; the
|
|
158
|
+
// trigger resolves it from here. Only an object literal carries config;
|
|
159
|
+
// `onDemand: true` uses router-level defaults (config stays undefined).
|
|
160
|
+
if (onDemand) {
|
|
161
|
+
const routeEntry = entries.find(
|
|
162
|
+
(e) =>
|
|
163
|
+
e.type === "route" &&
|
|
164
|
+
!!(e as { prerenderDef?: unknown }).prerenderDef,
|
|
165
|
+
) as
|
|
166
|
+
| { prerenderDef?: { options?: { onDemand?: unknown } } }
|
|
167
|
+
| undefined;
|
|
168
|
+
const od = routeEntry?.prerenderDef?.options?.onDemand;
|
|
169
|
+
if (od && typeof od === "object") {
|
|
170
|
+
onDemandConfig = od as OnDemandRouteConfig;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
295
173
|
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
174
|
+
// 3b. Dev-mode passthrough shortcut: if the route is a Passthrough route
|
|
175
|
+
// and has getParams(), check if the matched params are in the known list.
|
|
176
|
+
// In production, only known params are pre-rendered; unknown params fall
|
|
177
|
+
// through to the live handler. Mirror that behavior in dev mode to avoid
|
|
178
|
+
// rendering unknown params with build: true.
|
|
179
|
+
// Vars collected from getParams() probe — merged into render context below.
|
|
180
|
+
let devProbeBuildVars: Record<string, any> | undefined;
|
|
181
|
+
|
|
182
|
+
// On-demand refresh renders exactly the requested param set, even one not
|
|
183
|
+
// returned by getParams() (that is the point of an explicit refresh), so the
|
|
184
|
+
// dev getParams() passthrough probe is skipped for onDemand.
|
|
185
|
+
if (devMode && matchedPassthroughRoute && !onDemand) {
|
|
186
|
+
const routeEntry = entries.find(
|
|
187
|
+
(
|
|
188
|
+
e,
|
|
189
|
+
): e is EntryData & {
|
|
190
|
+
type: "route";
|
|
191
|
+
prerenderDef: { getParams: (ctx: any) => Promise<any[]> | any[] };
|
|
192
|
+
} =>
|
|
193
|
+
e.type === "route" &&
|
|
194
|
+
!!(e as any).isPassthrough &&
|
|
195
|
+
!!(e as any).prerenderDef?.getParams,
|
|
196
|
+
);
|
|
197
|
+
if (routeEntry) {
|
|
198
|
+
try {
|
|
199
|
+
const probeBuildVars: Record<string, any> = {};
|
|
200
|
+
const knownParamsList = await routeEntry.prerenderDef.getParams({
|
|
201
|
+
build: true as const,
|
|
202
|
+
dev: true,
|
|
203
|
+
set: ((keyOrVar: any, value: any) => {
|
|
204
|
+
contextSet(probeBuildVars, keyOrVar, value);
|
|
205
|
+
}) as any,
|
|
206
|
+
reverse: createReverseFunction(deps.mergedRouteMap),
|
|
207
|
+
get env() {
|
|
208
|
+
if (buildEnv !== undefined) return buildEnv;
|
|
209
|
+
throw new Error(
|
|
210
|
+
"[rango] ctx.env is not available during dev-mode getParams(). " +
|
|
211
|
+
"Configure buildEnv in your rango() plugin options to enable build-time env access.",
|
|
212
|
+
);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
// Compare only the keys returned by getParams — ignore mount params
|
|
216
|
+
// from include() prefixes that aren't part of the handler's params.
|
|
217
|
+
const isKnown = knownParamsList.some(
|
|
218
|
+
(known: Record<string, any>) => {
|
|
219
|
+
const knownKeys = Object.keys(known);
|
|
220
|
+
return knownKeys.every(
|
|
221
|
+
(k) => String(known[k]) === String(matchedParams[k]),
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
if (!isKnown) {
|
|
226
|
+
return passthroughResult();
|
|
227
|
+
}
|
|
228
|
+
// Preserve vars set by getParams() for the render context
|
|
229
|
+
if (hasContextVars(probeBuildVars)) {
|
|
230
|
+
devProbeBuildVars = probeBuildVars;
|
|
231
|
+
}
|
|
232
|
+
} catch (err: any) {
|
|
233
|
+
// Mirror production semantics (prerender-collection.ts):
|
|
234
|
+
// Skip errors are intentional — treat as passthrough.
|
|
235
|
+
// All other errors propagate so dev surfaces them.
|
|
236
|
+
if (err?.name === "Skip") {
|
|
237
|
+
return passthroughResult();
|
|
238
|
+
}
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
303
242
|
}
|
|
304
243
|
|
|
305
|
-
//
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
244
|
+
// 4. Create handle store for collecting handle data
|
|
245
|
+
const handleStore = createHandleStore();
|
|
246
|
+
|
|
247
|
+
// 5. Create a minimal request context with the handle store
|
|
248
|
+
// Shallow-copy getParams vars so each param set is independent.
|
|
249
|
+
// In dev mode, merge vars from the getParams() probe if the caller
|
|
250
|
+
// didn't provide buildVars (production passes them from expandPrerenderRoutes).
|
|
251
|
+
const effectiveBuildVars = buildVars ?? devProbeBuildVars;
|
|
252
|
+
const variables: Record<string, any> = effectiveBuildVars
|
|
253
|
+
? { ...effectiveBuildVars }
|
|
254
|
+
: {};
|
|
255
|
+
const stubRes = new Response(null, { status: 200 });
|
|
256
|
+
const minimalRequestContext: RequestContext<TEnv> = {
|
|
257
|
+
env: buildEnv ?? ({} as TEnv),
|
|
258
|
+
// Requestless refresh: arm the personalization guard so standalone
|
|
259
|
+
// cookies()/headers() (and the cache-directive helpers) throw a
|
|
260
|
+
// PrerenderPersonalizationError, which the producer maps to
|
|
261
|
+
// skipped-personalized. Build/dev prerender leave this unset.
|
|
262
|
+
...(onDemand ? { _onDemandProducer: true as const } : {}),
|
|
263
|
+
...(onDemand && deps.versions ? { _versions: deps.versions } : {}),
|
|
264
|
+
request: new Request("http://prerender" + pathname),
|
|
265
|
+
url: new URL("http://prerender" + pathname),
|
|
266
|
+
originalUrl: new URL("http://prerender" + pathname),
|
|
267
|
+
pathname,
|
|
268
|
+
searchParams: new URLSearchParams(),
|
|
269
|
+
_variables: variables,
|
|
270
|
+
build: true,
|
|
271
|
+
// Inert here: the prerender-collect / static-render pass has no live
|
|
272
|
+
// PPR-shell decision to gate; dynamic() reaches the shell axis only on a
|
|
273
|
+
// live request or a shell capture.
|
|
274
|
+
dynamic: () => {},
|
|
275
|
+
get: ((keyOrVar: any) => contextGet(variables, keyOrVar)) as any,
|
|
276
|
+
set: ((keyOrVar: any, value: any) => {
|
|
277
|
+
contextSet(variables, keyOrVar, value);
|
|
278
|
+
}) as any,
|
|
279
|
+
params: matchedParams,
|
|
280
|
+
res: stubRes,
|
|
281
|
+
cookie: () => undefined,
|
|
282
|
+
cookies: () => ({}),
|
|
283
|
+
_readCookie: () => undefined,
|
|
284
|
+
_readCookies: () => ({}),
|
|
285
|
+
setCookie: () => {},
|
|
286
|
+
deleteCookie: () => {},
|
|
287
|
+
header: () => {},
|
|
288
|
+
setStatus: () => {},
|
|
289
|
+
_setStatus: () => {},
|
|
290
|
+
_rotateStateCookie: () => {},
|
|
291
|
+
_setKeepCacheDirective: () => {},
|
|
292
|
+
_setFreshReadsCookie: () => {},
|
|
293
|
+
use: (() => {
|
|
294
|
+
throw new Error("use() not available during pre-rendering");
|
|
295
|
+
}) as any,
|
|
296
|
+
method: "GET",
|
|
297
|
+
_handleStore: handleStore,
|
|
298
|
+
_requestTags: new Set<string>(),
|
|
299
|
+
waitUntil: () => {},
|
|
300
|
+
onResponse: () => {},
|
|
301
|
+
_onResponseCallbacks: [],
|
|
302
|
+
// No theme at build time: no request cookie.
|
|
303
|
+
_readTheme: () => undefined,
|
|
304
|
+
setLocationState() {},
|
|
305
|
+
_locationState: undefined,
|
|
306
|
+
_renderBarrier: Promise.resolve(),
|
|
307
|
+
_resolveRenderBarrier: () => {},
|
|
308
|
+
_reportedErrors: new WeakSet<object>(),
|
|
309
|
+
reverse: createReverseFunction(
|
|
310
|
+
deps.mergedRouteMap,
|
|
311
|
+
matched.routeKey,
|
|
312
|
+
matchedParams,
|
|
313
|
+
matched.routeKey
|
|
314
|
+
? isRouteRootScoped(matched.routeKey, deps.routerId)
|
|
315
|
+
: undefined,
|
|
316
|
+
),
|
|
317
|
+
// Scope the bake context's own lookups (createPrerenderContext reads it
|
|
318
|
+
// through the request ALS) per router as well.
|
|
319
|
+
_routerId: deps.routerId,
|
|
322
320
|
};
|
|
323
|
-
const serializedSegments = await serializeSegments(
|
|
324
|
-
nonLoaderSegments,
|
|
325
|
-
onFlightError,
|
|
326
|
-
);
|
|
327
321
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
322
|
+
return runWithRequestContext(minimalRequestContext, async () => {
|
|
323
|
+
// 6. Create prerender context with synthetic URL.
|
|
324
|
+
// Prerender handlers get params, pathname, url, searchParams, search,
|
|
325
|
+
// reverse, use(handle), and optionally env (when buildEnv is configured).
|
|
326
|
+
const buildCtx = createPrerenderContext<TEnv>(
|
|
327
|
+
matchedParams,
|
|
328
|
+
pathname,
|
|
329
|
+
deps.mergedRouteMap,
|
|
330
|
+
matched.routeKey,
|
|
331
|
+
variables,
|
|
332
|
+
matchedPassthroughRoute,
|
|
333
|
+
buildEnv,
|
|
334
|
+
devMode,
|
|
335
|
+
onDemand,
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// 7. Wire use() for handles only (loaders throw)
|
|
339
|
+
setupBuildUse(buildCtx);
|
|
340
|
+
|
|
341
|
+
// 8. Resolve all segments with skipLoaders
|
|
342
|
+
const loaderPromises = new Map<string, Promise<any>>();
|
|
343
|
+
const allSegments = await deps.resolveAllSegments(
|
|
344
|
+
entries,
|
|
345
|
+
matched.routeKey,
|
|
346
|
+
matchedParams,
|
|
347
|
+
buildCtx,
|
|
348
|
+
loaderPromises,
|
|
349
|
+
// throwOnError: a render failure (or `throw new Skip()`) must reach the
|
|
350
|
+
// build/dev caller, not be baked into a frozen error page (issue #587).
|
|
351
|
+
{ skipLoaders: true, throwOnError: true },
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// 9. Detect passthrough sentinel: handler returned ctx.passthrough().
|
|
355
|
+
// When the route declares loading(), the handler result is deferred so the
|
|
356
|
+
// component is a thenable resolving to the sentinel — detectPrerenderPassthrough
|
|
357
|
+
// resolves thenables before testing (a sync check would miss it and bake a
|
|
358
|
+
// corrupt artifact).
|
|
359
|
+
if (await detectPrerenderPassthrough(allSegments)) {
|
|
360
|
+
return { ...passthroughResult(), onDemandConfig };
|
|
340
361
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
362
|
+
|
|
363
|
+
// 10. Filter out any loader segments (belt-and-suspenders)
|
|
364
|
+
const nonLoaderSegments = allSegments.filter(
|
|
365
|
+
(s) => s.type !== "loader",
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
// 11. Wait for handles to settle
|
|
369
|
+
handleStore.seal();
|
|
370
|
+
await handleStore.settled;
|
|
371
|
+
|
|
372
|
+
// 12. Serialize segments using the cache serializer.
|
|
373
|
+
// Flight encodes a component that throws (an async server component in
|
|
374
|
+
// the tree) as an error row and completes normally. Rethrown below like
|
|
375
|
+
// a handler throw, so the caller's policy decides instead of baking the
|
|
376
|
+
// error (#914). On-demand refresh: a deep async child calling
|
|
377
|
+
// cookies()/headers() throws here, not in resolveAllSegments; a
|
|
378
|
+
// personalization error is thrown in preference to any other so the
|
|
379
|
+
// trigger maps it to skipped-personalized and keeps the old entry.
|
|
380
|
+
const { serializeSegments } = await import("../cache/segment-codec.js");
|
|
381
|
+
const { encodeHandles } = await import("../cache/handle-snapshot.js");
|
|
382
|
+
const flightErrors: unknown[] = [];
|
|
383
|
+
const onFlightError = (error: unknown): void => {
|
|
384
|
+
flightErrors.push(error);
|
|
385
|
+
};
|
|
386
|
+
// The collected error to rethrow. On an on-demand refresh a failure
|
|
387
|
+
// goes ahead of a notFound(): a render that hit a missing item AND a
|
|
388
|
+
// failing data source must keep the page (render-failed), not remove
|
|
389
|
+
// it because the notFound() happened to be reported first.
|
|
390
|
+
const firstFlightError = (): unknown => {
|
|
391
|
+
const failure = onDemand
|
|
392
|
+
? flightErrors.findIndex((e) => !isDataNotFoundError(e))
|
|
393
|
+
: -1;
|
|
394
|
+
return flightErrors[failure === -1 ? 0 : failure];
|
|
395
|
+
};
|
|
396
|
+
const serializedSegments = await serializeSegments(
|
|
397
|
+
nonLoaderSegments,
|
|
398
|
+
onFlightError,
|
|
399
|
+
);
|
|
400
|
+
const personalized = flightErrors.find((e) =>
|
|
401
|
+
isPrerenderPersonalizationError(e),
|
|
402
|
+
);
|
|
403
|
+
if (onDemand && personalized) {
|
|
404
|
+
throw personalized;
|
|
358
405
|
}
|
|
359
|
-
}
|
|
360
406
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
// innermost) and skips when().
|
|
375
|
-
const collect = (source: EntryData): undefined => {
|
|
376
|
-
for (const ic of source.intercept) {
|
|
377
|
-
if (ic.routeName === matched.routeKey) {
|
|
378
|
-
foundIntercepts.push({ intercept: ic, entry: source });
|
|
407
|
+
// 13. Collect handle data per segment (skip segments with no handle data).
|
|
408
|
+
// Encoded through the Flight codec (not stored raw) so Promise/ReactNode
|
|
409
|
+
// handle values survive the JSON-serialized build artifact / dev wire —
|
|
410
|
+
// the same fix the runtime cache uses. Encode happens here, in the RSC
|
|
411
|
+
// environment where the codec resolves; the node-side sinks only persist.
|
|
412
|
+
const handlesRecord: Record<string, SegmentHandleData> = {};
|
|
413
|
+
for (const seg of nonLoaderSegments) {
|
|
414
|
+
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
415
|
+
if (Object.keys(segHandles).length > 0) {
|
|
416
|
+
// Resolve deferred values before encoding so the baked artifact holds
|
|
417
|
+
// resolved data (prerender = build-time cache).
|
|
418
|
+
handlesRecord[seg.id] =
|
|
419
|
+
await resolveSegmentHandleValues(segHandles);
|
|
379
420
|
}
|
|
380
421
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// bucket and getDataForSegment(seg.id) below drops them from the baked
|
|
401
|
-
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
402
|
-
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
403
|
-
// pinning).
|
|
404
|
-
const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
|
|
405
|
-
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
406
|
-
interceptSegmentId;
|
|
407
|
-
|
|
408
|
-
// Resolve handler
|
|
409
|
-
const handlerRaw =
|
|
410
|
-
typeof intercept.handler === "function"
|
|
411
|
-
? intercept.handler(buildCtx)
|
|
412
|
-
: intercept.handler;
|
|
413
|
-
const handlerResolved =
|
|
414
|
-
handlerRaw instanceof Promise ? await handlerRaw : handlerRaw;
|
|
415
|
-
if (handlerResolved instanceof Response) {
|
|
416
|
-
// Handler returned a redirect/response -- skip this intercept
|
|
417
|
-
continue;
|
|
422
|
+
const handles = await encodeHandles(handlesRecord, onFlightError);
|
|
423
|
+
// Before intercept resolution, as a handler throw would be.
|
|
424
|
+
if (flightErrors.length > 0) throw firstFlightError();
|
|
425
|
+
|
|
426
|
+
// Use the trie-level route key (e.g., "docs", "docs.article")
|
|
427
|
+
const routeName = matched.routeKey;
|
|
428
|
+
|
|
429
|
+
// Surface the matched route entry's ppr option for the build collection
|
|
430
|
+
// (leaf-first: the deepest type:"route" entry in the ancestor chain is
|
|
431
|
+
// the matched page route; ancestors are layouts/includes).
|
|
432
|
+
let routePpr: boolean | PartialPrerenderProps | undefined;
|
|
433
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
434
|
+
const e = entries[i]!;
|
|
435
|
+
if (e.type === "route") {
|
|
436
|
+
// A durable segment refresh cannot atomically replace the separate
|
|
437
|
+
// document shell. Keep on-demand routes on axis 1 so a refreshed
|
|
438
|
+
// payload cannot be paired with a stale shell prelude.
|
|
439
|
+
routePpr = e.isOnDemand === true ? undefined : e.ppr;
|
|
440
|
+
break;
|
|
418
441
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// 14. Resolve intercept segments for this route (if any ancestor defines
|
|
445
|
+
// an intercept targeting this route). At build time we skip when()
|
|
446
|
+
// evaluation -- we pre-render all intercepts unconditionally and let
|
|
447
|
+
// runtime matching decide which to serve.
|
|
448
|
+
let interceptSegments: SerializedSegmentData[] | undefined;
|
|
449
|
+
let interceptHandles: string | undefined;
|
|
450
|
+
|
|
451
|
+
const foundIntercepts: {
|
|
452
|
+
intercept: InterceptEntry;
|
|
453
|
+
entry: EntryData;
|
|
454
|
+
}[] = [];
|
|
455
|
+
// The entry, then its orphans (nested ones included): the same traversal
|
|
456
|
+
// findInterceptForRoute uses; the build keeps ALL matches (not just the
|
|
457
|
+
// innermost) and skips when().
|
|
458
|
+
const collect = (source: EntryData): undefined => {
|
|
459
|
+
for (const ic of source.intercept) {
|
|
460
|
+
if (ic.routeName === matched.routeKey) {
|
|
461
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
430
462
|
}
|
|
431
463
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
params: matchedParams,
|
|
442
|
-
slot: intercept.slotName,
|
|
443
|
-
belongsToRoute: true,
|
|
444
|
-
parallelName: `intercept:${intercept.routeName}.${intercept.slotName}`,
|
|
445
|
-
});
|
|
464
|
+
return undefined;
|
|
465
|
+
};
|
|
466
|
+
let current: EntryData | null = manifestEntry;
|
|
467
|
+
let prev: EntryData | null = null;
|
|
468
|
+
while (current) {
|
|
469
|
+
collect(current);
|
|
470
|
+
findInOrphans(current, collect, prev);
|
|
471
|
+
prev = current;
|
|
472
|
+
current = current.parent;
|
|
446
473
|
}
|
|
447
474
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
475
|
+
// V1 writes only the main variant. Do not execute intercept producers
|
|
476
|
+
// whose output would be discarded (and whose personalization/errors
|
|
477
|
+
// must not block an otherwise valid main refresh).
|
|
478
|
+
if (!onDemand && foundIntercepts.length > 0) {
|
|
479
|
+
const interceptResolvedSegments: typeof nonLoaderSegments = [];
|
|
480
|
+
|
|
481
|
+
for (const { intercept, entry: parentEntry } of foundIntercepts) {
|
|
482
|
+
// setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
|
|
483
|
+
// resolveAllSegments pass left it on the route's segment id, so pin it
|
|
484
|
+
// to THIS intercept's slot id before resolving the handler/layout --
|
|
485
|
+
// otherwise the intercept's ctx.use() handle pushes land in the wrong
|
|
486
|
+
// bucket and getDataForSegment(seg.id) below drops them from the baked
|
|
487
|
+
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
488
|
+
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
489
|
+
// pinning).
|
|
490
|
+
const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
|
|
491
|
+
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
492
|
+
interceptSegmentId;
|
|
493
|
+
|
|
494
|
+
// Resolve handler
|
|
495
|
+
const handlerRaw =
|
|
496
|
+
typeof intercept.handler === "function"
|
|
497
|
+
? intercept.handler(buildCtx)
|
|
498
|
+
: intercept.handler;
|
|
499
|
+
const handlerResolved =
|
|
500
|
+
handlerRaw instanceof Promise ? await handlerRaw : handlerRaw;
|
|
501
|
+
if (handlerResolved instanceof Response) {
|
|
502
|
+
// Handler returned a redirect/response -- skip this intercept
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const component: ReactNode = handlerResolved;
|
|
506
|
+
|
|
507
|
+
// Resolve layout (if any)
|
|
508
|
+
let layoutElement: ReactNode | undefined;
|
|
509
|
+
if (intercept.layout) {
|
|
510
|
+
if (typeof intercept.layout === "function") {
|
|
511
|
+
const layoutResult = await intercept.layout(buildCtx);
|
|
512
|
+
if (layoutResult instanceof Response) continue;
|
|
513
|
+
layoutElement = layoutResult;
|
|
514
|
+
} else {
|
|
515
|
+
layoutElement = intercept.layout;
|
|
516
|
+
}
|
|
461
517
|
}
|
|
518
|
+
|
|
519
|
+
interceptResolvedSegments.push({
|
|
520
|
+
id: interceptSegmentId,
|
|
521
|
+
namespace: `intercept:${intercept.routeName}`,
|
|
522
|
+
type: "parallel" as const,
|
|
523
|
+
index: 0,
|
|
524
|
+
component,
|
|
525
|
+
loading: intercept.loading === false ? null : intercept.loading,
|
|
526
|
+
layout: layoutElement,
|
|
527
|
+
params: matchedParams,
|
|
528
|
+
slot: intercept.slotName,
|
|
529
|
+
belongsToRoute: true,
|
|
530
|
+
parallelName: `intercept:${intercept.routeName}.${intercept.slotName}`,
|
|
531
|
+
});
|
|
462
532
|
}
|
|
463
|
-
// The intercept artifact serves main + intercept segments together, so
|
|
464
|
-
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
465
|
-
// records — they store this pre-encoded string as-is).
|
|
466
|
-
interceptHandles = await encodeHandles(
|
|
467
|
-
{
|
|
468
|
-
...handlesRecord,
|
|
469
|
-
...interceptHandlesRecord,
|
|
470
|
-
},
|
|
471
|
-
onFlightError,
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
533
|
|
|
476
|
-
|
|
534
|
+
if (interceptResolvedSegments.length > 0) {
|
|
535
|
+
// Wait for handles again (intercept handlers may have called use())
|
|
536
|
+
await handleStore.settled;
|
|
537
|
+
interceptSegments = await serializeSegments(
|
|
538
|
+
interceptResolvedSegments,
|
|
539
|
+
onFlightError,
|
|
540
|
+
);
|
|
541
|
+
const interceptHandlesRecord: Record<string, SegmentHandleData> =
|
|
542
|
+
{};
|
|
543
|
+
for (const seg of interceptResolvedSegments) {
|
|
544
|
+
const segHandles = handleStore.getDataForSegment(seg.id);
|
|
545
|
+
if (Object.keys(segHandles).length > 0) {
|
|
546
|
+
interceptHandlesRecord[seg.id] =
|
|
547
|
+
await resolveSegmentHandleValues(segHandles);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// The intercept artifact serves main + intercept segments together, so
|
|
551
|
+
// encode the MERGED handle map here (the sinks no longer merge raw
|
|
552
|
+
// records — they store this pre-encoded string as-is).
|
|
553
|
+
interceptHandles = await encodeHandles(
|
|
554
|
+
{
|
|
555
|
+
...handlesRecord,
|
|
556
|
+
...interceptHandlesRecord,
|
|
557
|
+
},
|
|
558
|
+
onFlightError,
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
477
562
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
563
|
+
if (flightErrors.length > 0) throw firstFlightError();
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
segments: serializedSegments,
|
|
567
|
+
handles,
|
|
568
|
+
routeName,
|
|
569
|
+
params: matchedParams,
|
|
570
|
+
interceptSegments,
|
|
571
|
+
interceptHandles,
|
|
572
|
+
onDemandConfig,
|
|
573
|
+
ppr: routePpr,
|
|
574
|
+
};
|
|
575
|
+
});
|
|
576
|
+
}),
|
|
577
|
+
);
|
|
578
|
+
// A build or dev render keeps the throw (the caller's onError policy).
|
|
579
|
+
if (!onDemand) return rendered;
|
|
580
|
+
// An on-demand refresh reports a notFound() anywhere in the render (a
|
|
581
|
+
// handler's throw, or the only kind of error Flight collected) as an
|
|
582
|
+
// outcome, with the route's onDemand config: the trigger stores the
|
|
583
|
+
// "removed" marker stamped like a page (create-prerender-trigger.ts).
|
|
584
|
+
try {
|
|
585
|
+
return await rendered;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (!isDataNotFoundError(error)) throw error;
|
|
588
|
+
return {
|
|
589
|
+
segments: [],
|
|
590
|
+
handles: "",
|
|
591
|
+
routeName: matched.routeKey,
|
|
592
|
+
params: matchedParams,
|
|
593
|
+
notFound: true,
|
|
594
|
+
onDemandConfig,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
489
597
|
}
|
|
490
598
|
|
|
491
599
|
/**
|