vinext 1.4.0 → 1.4.2
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 +21 -21
- package/dist/build/action-owner-manifest.js +1 -0
- package/dist/build/prerender.js +3 -2
- package/dist/check.js +4 -7
- package/dist/client/dev-error-overlay-store.d.ts +1 -0
- package/dist/client/dev-error-overlay.d.ts +2 -0
- package/dist/client/dev-error-overlay.js +19 -3
- package/dist/config/config-matchers.js +4 -2
- package/dist/config/destination-params.d.ts +2 -1
- package/dist/config/destination-params.js +55 -2
- package/dist/config/next-config.d.ts +22 -1
- package/dist/config/next-config.js +49 -4
- package/dist/entries/app-rsc-entry.js +41 -19
- package/dist/entries/app-rsc-manifest.js +4 -0
- package/dist/entries/pages-server-entry.js +5 -1
- package/dist/index.js +44 -32
- package/dist/init-cloudflare.js +18 -2
- package/dist/plugins/css-target.d.ts +18 -0
- package/dist/plugins/css-target.js +93 -0
- package/dist/plugins/strip-server-exports.js +2 -1
- package/dist/routing/app-route-graph.d.ts +10 -0
- package/dist/routing/app-route-graph.js +15 -1
- package/dist/server/api-handler.d.ts +2 -0
- package/dist/server/api-handler.js +5 -6
- package/dist/server/app-action-forwarding.js +3 -1
- package/dist/server/app-browser-entry.js +28 -7
- package/dist/server/app-browser-rsc-redirect.js +3 -3
- package/dist/server/app-browser-server-action-client.js +2 -2
- package/dist/server/app-browser-visible-commit.js +2 -2
- package/dist/server/app-elements.d.ts +3 -0
- package/dist/server/app-elements.js +14 -1
- package/dist/server/app-fallback-renderer.js +2 -2
- package/dist/server/app-optimistic-routing.d.ts +12 -1
- package/dist/server/app-optimistic-routing.js +40 -19
- package/dist/server/app-page-boundary-render.js +7 -12
- package/dist/server/app-page-cache-render.js +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -2
- package/dist/server/app-page-dispatch.js +73 -31
- package/dist/server/app-page-element-builder.d.ts +9 -20
- package/dist/server/app-page-element-builder.js +40 -52
- package/dist/server/app-page-execution.js +9 -6
- package/dist/server/app-page-intercept-segment-config.d.ts +56 -0
- package/dist/server/app-page-intercept-segment-config.js +90 -0
- package/dist/server/app-page-navigation-params.d.ts +23 -0
- package/dist/server/app-page-navigation-params.js +49 -0
- package/dist/server/app-page-render.d.ts +5 -3
- package/dist/server/app-page-render.js +31 -17
- package/dist/server/app-page-request.d.ts +24 -2
- package/dist/server/app-page-request.js +10 -10
- package/dist/server/app-page-response.js +1 -1
- package/dist/server/app-page-route-wiring.d.ts +10 -0
- package/dist/server/app-page-route-wiring.js +34 -13
- package/dist/server/app-page-segment-plan.js +9 -1
- package/dist/server/app-page-segment-state.d.ts +9 -0
- package/dist/server/app-page-segment-state.js +54 -1
- package/dist/server/app-page-stream.d.ts +10 -8
- package/dist/server/app-page-stream.js +2 -1
- package/dist/server/app-request-stage-independent-entry.js +1 -1
- package/dist/server/app-response-stage-entry.js +1 -0
- package/dist/server/app-route-handler-execution.js +3 -2
- package/dist/server/app-route-handler-runtime.d.ts +1 -1
- package/dist/server/app-route-handler-runtime.js +11 -7
- package/dist/server/app-route-module-loader.d.ts +2 -0
- package/dist/server/app-route-module-loader.js +1 -0
- package/dist/server/app-router-entry.js +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +10 -10
- package/dist/server/app-rsc-cache-busting.js +27 -68
- package/dist/server/app-rsc-error-handler.js +1 -1
- package/dist/server/app-rsc-errors.js +4 -1
- package/dist/server/app-rsc-handler.d.ts +10 -1
- package/dist/server/app-rsc-handler.js +126 -103
- package/dist/server/app-rsc-request-normalization.d.ts +10 -12
- package/dist/server/app-rsc-request-normalization.js +11 -14
- package/dist/server/app-rsc-response-finalizer.d.ts +3 -0
- package/dist/server/app-rsc-response-finalizer.js +11 -1
- package/dist/server/app-rsc-response-stage.js +4 -12
- package/dist/server/app-rsc-route-matching.d.ts +14 -0
- package/dist/server/app-rsc-route-matching.js +42 -18
- package/dist/server/app-server-action-execution.js +24 -14
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +13 -8
- package/dist/server/app-worker-stages.d.ts +12 -3
- package/dist/server/app-worker-stages.js +37 -4
- package/dist/server/bfcache-identity.d.ts +7 -0
- package/dist/server/bfcache-identity.js +10 -1
- package/dist/server/cache-control.d.ts +2 -0
- package/dist/server/cache-control.js +3 -1
- package/dist/server/cacheability-manifest.js +1 -2
- package/dist/server/dev-origin-check.d.ts +2 -1
- package/dist/server/dev-origin-check.js +2 -1
- package/dist/server/dev-server.js +2 -1
- package/dist/server/image-local-patterns.d.ts +21 -0
- package/dist/server/image-local-patterns.js +59 -0
- package/dist/server/image-optimization.d.ts +28 -22
- package/dist/server/image-optimization.js +17 -2
- package/dist/server/isr-cache.js +4 -1
- package/dist/server/metadata-route-response.js +25 -7
- package/dist/server/middleware-matcher.js +19 -6
- package/dist/server/middleware-runtime.js +6 -2
- package/dist/server/multi-stage.d.ts +7 -3
- package/dist/server/nitro-public-files-plugin.js +20 -8
- package/dist/server/pages-data-route.d.ts +5 -1
- package/dist/server/pages-data-route.js +14 -6
- package/dist/server/pages-node-compat.d.ts +1 -1
- package/dist/server/pages-node-compat.js +2 -0
- package/dist/server/pages-page-data.js +2 -1
- package/dist/server/pages-page-handler.js +17 -6
- package/dist/server/pages-page-response.d.ts +9 -0
- package/dist/server/pages-page-response.js +18 -4
- package/dist/server/pages-request-pipeline.js +6 -5
- package/dist/server/pages-request-stage-entry.js +25 -4
- package/dist/server/pages-router-entry.js +3 -2
- package/dist/server/prod-server.d.ts +2 -2
- package/dist/server/prod-server.js +59 -17
- package/dist/server/request-error-log.d.ts +9 -0
- package/dist/server/request-error-log.js +13 -0
- package/dist/server/request-pipeline.d.ts +14 -4
- package/dist/server/request-pipeline.js +33 -15
- package/dist/server/response-stage-cacheability.d.ts +7 -0
- package/dist/server/response-stage-cacheability.js +6 -5
- package/dist/server/rsc-completion-metadata.js +3 -0
- package/dist/server/static-file-cache.d.ts +9 -0
- package/dist/server/static-file-cache.js +13 -2
- package/dist/server/worker-image-optimization.d.ts +2 -2
- package/dist/server/worker-image-optimization.js +18 -3
- package/dist/server/worker-utils.d.ts +8 -0
- package/dist/server/worker-utils.js +19 -2
- package/dist/shims/cache-request-state.d.ts +6 -6
- package/dist/shims/cache-request-state.js +6 -36
- package/dist/shims/cache-runtime.d.ts +3 -1
- package/dist/shims/cache-runtime.js +77 -47
- package/dist/shims/cache.d.ts +10 -5
- package/dist/shims/cache.js +35 -22
- package/dist/shims/client-hook-error.d.ts +2 -0
- package/dist/shims/client-hook-error.js +16 -1
- package/dist/shims/fetch-cache.js +6 -2
- package/dist/shims/headers.d.ts +2 -1
- package/dist/shims/headers.js +5 -3
- package/dist/shims/image-props.d.ts +6 -0
- package/dist/shims/image-props.js +39 -3
- package/dist/shims/image.js +3 -3
- package/dist/shims/internal/cache-context-accessor.d.ts +1 -1
- package/dist/shims/link.js +5 -10
- package/dist/shims/navigation.js +2 -3
- package/dist/shims/request-state-types.d.ts +2 -1
- package/dist/shims/slot.d.ts +9 -2
- package/dist/shims/slot.js +76 -11
- package/dist/shims/unified-request-context.d.ts +6 -0
- package/dist/shims/unified-request-context.js +1 -0
- package/dist/utils/base-path.d.ts +7 -0
- package/dist/utils/base-path.js +11 -1
- package/dist/utils/cache-life-profiles.d.ts +33 -0
- package/dist/utils/cache-life-profiles.js +111 -0
- package/dist/utils/middleware-request-headers.d.ts +14 -3
- package/dist/utils/middleware-request-headers.js +54 -7
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { matchRoutePattern } from "../routing/route-pattern.js";
|
|
2
|
+
import "./app-rsc-route-matching.js";
|
|
3
|
+
//#region src/server/app-page-navigation-params.ts
|
|
4
|
+
function resolveSlotParamOverrides(route, routePath) {
|
|
5
|
+
const overrides = {};
|
|
6
|
+
const slots = route.slots;
|
|
7
|
+
if (slots) {
|
|
8
|
+
let urlParts = null;
|
|
9
|
+
const routeParamSet = collectParamNameSet(route.params);
|
|
10
|
+
for (const [slotKey, slot] of Object.entries(slots)) {
|
|
11
|
+
const patternParts = slot.slotPatternParts;
|
|
12
|
+
const paramNames = slot.slotParamNames;
|
|
13
|
+
if (!patternParts || patternParts.length === 0) continue;
|
|
14
|
+
if (paramNames && paramNames.every((name) => routeParamSet.has(name))) continue;
|
|
15
|
+
if (urlParts === null) urlParts = routePath.split("/").filter(Boolean);
|
|
16
|
+
const matched = matchRoutePattern(urlParts, patternParts);
|
|
17
|
+
if (!matched) continue;
|
|
18
|
+
overrides[slotKey] = matched;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return Object.keys(overrides).length > 0 ? overrides : null;
|
|
22
|
+
}
|
|
23
|
+
function mergeAppPageParams(target, source) {
|
|
24
|
+
for (const [key, value] of Object.entries(source)) target[key] = value;
|
|
25
|
+
}
|
|
26
|
+
function isDefaultExportModule(module) {
|
|
27
|
+
return typeof module === "object" && module !== null;
|
|
28
|
+
}
|
|
29
|
+
function hasDefaultExport(module) {
|
|
30
|
+
if (!isDefaultExportModule(module)) return false;
|
|
31
|
+
return module?.default !== null && module?.default !== void 0;
|
|
32
|
+
}
|
|
33
|
+
function resolveAppPageNavigationParams(route, routeParams, routePath, opts) {
|
|
34
|
+
const navigationParams = { ...routeParams };
|
|
35
|
+
const slotParamOverrides = resolveSlotParamOverrides(route, routePath);
|
|
36
|
+
for (const [slotKey, slot] of Object.entries(route.slots ?? {})) {
|
|
37
|
+
const isInterceptedSlot = opts?.interceptSlotKey === slotKey && opts.interceptSlotKey !== "__vinext_page_intercept" && hasDefaultExport(opts.interceptPage);
|
|
38
|
+
if (!isInterceptedSlot && !hasDefaultExport(slot.page) && !hasDefaultExport(slot.default) && !slot.__loadPage && !slot.__loadDefault) continue;
|
|
39
|
+
mergeAppPageParams(navigationParams, isInterceptedSlot ? opts?.interceptParams ?? routeParams : slotParamOverrides?.[slotKey] ?? routeParams);
|
|
40
|
+
}
|
|
41
|
+
return navigationParams;
|
|
42
|
+
}
|
|
43
|
+
function collectParamNameSet(params) {
|
|
44
|
+
const set = /* @__PURE__ */ new Set();
|
|
45
|
+
if (params) for (const name of params) set.add(name);
|
|
46
|
+
return set;
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { resolveAppPageNavigationParams, resolveSlotParamOverrides };
|
|
@@ -72,7 +72,6 @@ type RenderAppPageLifecycleOptionsBase = {
|
|
|
72
72
|
peekRequestCacheLife?: () => AppPageRequestCacheLife | null;
|
|
73
73
|
getDraftModeCookieHeader: () => string | null | undefined;
|
|
74
74
|
handlerStart: number;
|
|
75
|
-
hasCustomGlobalError?: boolean;
|
|
76
75
|
dynamicStaleTimeSeconds?: number;
|
|
77
76
|
isDynamicError: boolean;
|
|
78
77
|
isDraftMode: boolean;
|
|
@@ -151,9 +150,12 @@ type RenderAppPageLifecycleOptions = RenderAppPageLifecycleOptionsBase & ({
|
|
|
151
150
|
* static, draft mode, `force-dynamic`, `revalidate = 0`, or a dynamic API read
|
|
152
151
|
* before the response left the render. Responses that leave the render before
|
|
153
152
|
* its response policy, such as error boundaries and special errors, get the
|
|
154
|
-
* same header as the normal render.
|
|
153
|
+
* same header as the normal render. One not yet known dynamic is never stored
|
|
154
|
+
* either, and its body can still reach a dynamic API after its headers leave,
|
|
155
|
+
* so it is sent like a dynamic render (see resolveUnstoredEarlyResponseCacheControl
|
|
156
|
+
* for the responses that aren't).
|
|
155
157
|
*/
|
|
156
|
-
export declare function applyIneligibleRouteCachePolicy(response: Response, options: Pick<RenderAppPageLifecycleOptions, "dynamicStaleTimeSeconds" | "isDraftMode" | "isDynamicError" | "isForceDynamic" | "isForceStatic" | "isProduction" | "isProgressiveActionRender" | "isRscRequest" | "isStaticEligible" | "middlewareContext" | "peekDynamicUsage" | "revalidateSeconds" | "scriptNonce">, requestCacheLife?: AppPageRequestCacheLife | null): Response;
|
|
158
|
+
export declare function applyIneligibleRouteCachePolicy(response: Response, options: Pick<RenderAppPageLifecycleOptions, "dynamicStaleTimeSeconds" | "isDraftMode" | "isDynamicError" | "isForceDynamic" | "isForceStatic" | "isPrerender" | "isProduction" | "isProgressiveActionRender" | "isRscRequest" | "isStaticEligible" | "middlewareContext" | "peekDynamicUsage" | "revalidateSeconds" | "scriptNonce">, requestCacheLife?: AppPageRequestCacheLife | null): Response;
|
|
157
159
|
export declare function renderAppPageLifecycle(options: RenderAppPageLifecycleOptions): Promise<Response>;
|
|
158
160
|
//#endregion
|
|
159
161
|
export {};
|
|
@@ -13,6 +13,7 @@ import { isRenderDynamicLatched } from "../shims/internal/headers-state.js";
|
|
|
13
13
|
import { peekDynamicUsage } from "../shims/headers.js";
|
|
14
14
|
import { runWithFetchDedupe } from "../shims/fetch-cache.js";
|
|
15
15
|
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
16
|
+
import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
|
|
16
17
|
import { copyLinkHeaderProvenance, getFrameworkLinkHeader } from "./app-response-header-provenance.js";
|
|
17
18
|
import { traceResponseStartWithCompletion } from "./response-start-tracing.js";
|
|
18
19
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
@@ -21,13 +22,12 @@ import { AppElementsWire, isAppElementsRecord } from "./app-elements-wire.js";
|
|
|
21
22
|
import "./app-elements.js";
|
|
22
23
|
import { appendRscCompletionMetadata } from "./rsc-completion-metadata.js";
|
|
23
24
|
import { isBailoutToCSRError } from "../shims/navigation-errors.js";
|
|
24
|
-
import { applyPrerenderCacheLifeHeader, applyPrerenderCacheTagsHeader } from "./prerender-cache-life-header.js";
|
|
25
25
|
import { applyClientStaleTimeHeader, applyEdgeRuntimeHeader, buildAppPageHtmlResponse, buildAppPageRscResponse, resolveAppPageHtmlResponsePolicy, resolveAppPageRscResponsePolicy, resolveUncacheableCacheControl } from "./app-page-response.js";
|
|
26
26
|
import { buildAppPageFontLinkHeader, readAppPageBinaryStream, resolveAppPageCaughtNotFoundBoundaries, resolveAppPageShellSpecialError, resolveAppPageSpecialError, resolveAppPageSpecialErrorStoredHeaders, teeAppPageRscStreamForCapture } from "./app-page-execution.js";
|
|
27
|
+
import { buildAppPageHtmlDocumentResponse, buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery } from "./app-page-stream.js";
|
|
27
28
|
import { getAppPageLayoutGate } from "./app-page-layout-gate.js";
|
|
28
29
|
import { hasDigest } from "./app-rsc-errors.js";
|
|
29
30
|
import { recordAppPageRenderError, traceAppPageRender } from "./app-page-tracing.js";
|
|
30
|
-
import { buildAppPageHtmlDocumentResponse, buildAppPageLinkHeader, createAppPageFontData, createAppPageRscErrorTracker, createAppPageSsrErrorHandler, renderAppPageHtmlStream, renderAppPageHtmlStreamWithRecovery } from "./app-page-stream.js";
|
|
31
31
|
import { createAppPageHtmlOutputScope, createAppPageRenderObservation, createAppPageRscOutputScope } from "./app-page-render-observation.js";
|
|
32
32
|
import { createAppPageRscRenderStatusResolver } from "./app-page-rsc-render-status.js";
|
|
33
33
|
import { finalizeAppPageCacheabilityEvaluationResponse, finalizeAppPageHtmlCacheResponse, finalizeAppPageRscCacheResponse } from "./app-page-cache-finalizer.js";
|
|
@@ -70,11 +70,14 @@ function applyRequestCacheLife(options) {
|
|
|
70
70
|
* static, draft mode, `force-dynamic`, `revalidate = 0`, or a dynamic API read
|
|
71
71
|
* before the response left the render. Responses that leave the render before
|
|
72
72
|
* its response policy, such as error boundaries and special errors, get the
|
|
73
|
-
* same header as the normal render.
|
|
73
|
+
* same header as the normal render. One not yet known dynamic is never stored
|
|
74
|
+
* either, and its body can still reach a dynamic API after its headers leave,
|
|
75
|
+
* so it is sent like a dynamic render (see resolveUnstoredEarlyResponseCacheControl
|
|
76
|
+
* for the responses that aren't).
|
|
74
77
|
*/
|
|
75
78
|
function applyIneligibleRouteCachePolicy(response, options, requestCacheLife = null) {
|
|
76
|
-
const cacheControl = resolveEarlyResponseCacheControl(options);
|
|
77
79
|
const isRscRedirect = options.isRscRequest && response.headers.has("X-Vinext-Rsc-Redirect");
|
|
80
|
+
const cacheControl = resolveEarlyResponseCacheControl(options) ?? (isRscRedirect ? null : resolveUnstoredEarlyResponseCacheControl(response, options));
|
|
78
81
|
if (!cacheControl) {
|
|
79
82
|
const staleTimeSeconds = isRscRedirect ? resolveClientStaleTimeSeconds(requestCacheLife) : void 0;
|
|
80
83
|
if (staleTimeSeconds === void 0) return response;
|
|
@@ -108,6 +111,17 @@ function resolveEarlyResponseCacheControl(options) {
|
|
|
108
111
|
const ignoresDynamicUsage = (options.isForceStatic || options.isDynamicError) && (options.revalidateSeconds === null || options.revalidateSeconds === Infinity);
|
|
109
112
|
return options.revalidateSeconds === 0 || !ignoresDynamicUsage && (options.peekDynamicUsage?.() ?? peekDynamicUsage()) ? uncacheable : null;
|
|
110
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* The normal render can still become a cache entry, so it sends its pending
|
|
116
|
+
* policy until the stream proves it static. An early response never does, and
|
|
117
|
+
* nothing checks it again, so one with a body takes the never-cache header
|
|
118
|
+
* unless it already carries a policy, such as middleware's. A force-static or
|
|
119
|
+
* dynamic = "error" render can't read the request, and dev and the build's
|
|
120
|
+
* prerender, which reads `no-store` as a dynamic render, keep no policy.
|
|
121
|
+
*/
|
|
122
|
+
function resolveUnstoredEarlyResponseCacheControl(response, options) {
|
|
123
|
+
return options.isProduction && options.isPrerender !== true && !options.isForceStatic && !options.isDynamicError && response.body !== null && !response.headers.has("cache-control") ? NEVER_CACHE_CONTROL : null;
|
|
124
|
+
}
|
|
111
125
|
function resolveAppPageCacheWriteRevalidateSeconds(options) {
|
|
112
126
|
if (!options.isStaticEligible) return null;
|
|
113
127
|
if (options.revalidateSeconds === null && (options.isForceStatic || options.isDynamicError)) return Infinity;
|
|
@@ -230,7 +244,6 @@ async function prepareAppPageElement(options) {
|
|
|
230
244
|
}
|
|
231
245
|
async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
232
246
|
let dynamicUsageObserved = false;
|
|
233
|
-
let dynamicUsageFinalized = false;
|
|
234
247
|
const isCacheCandidateHtmlRender = options.isCacheCandidate === true && options.isPrerender !== true && !options.isRscRequest;
|
|
235
248
|
const readsRenderDynamicLatch = isCacheCandidateHtmlRender || !options.isRscRequest && options.pprFallbackShellSignal === void 0 && (options.isPrerender === true || isRouteCacheabilityEvaluation());
|
|
236
249
|
const readDynamicUsage = bindRequestContext(() => options.consumeDynamicUsage() || readsRenderDynamicLatch && isRenderDynamicLatched());
|
|
@@ -240,13 +253,6 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
240
253
|
if (!dynamicUsageObserved) dynamicUsageObserved = readDynamicUsage();
|
|
241
254
|
return dynamicUsageObserved;
|
|
242
255
|
};
|
|
243
|
-
const finalizeRenderDynamicUsage = () => {
|
|
244
|
-
if (!dynamicUsageFinalized) {
|
|
245
|
-
consumeRenderDynamicUsage();
|
|
246
|
-
dynamicUsageFinalized = true;
|
|
247
|
-
}
|
|
248
|
-
return dynamicUsageObserved;
|
|
249
|
-
};
|
|
250
256
|
const artifactCompatibility = createAppPageArtifactCompatibility(options.element, options.routePattern);
|
|
251
257
|
const rootBoundaryId = artifactCompatibility?.rootBoundaryId ?? null;
|
|
252
258
|
const renderEpoch = artifactCompatibility?.renderEpoch ?? null;
|
|
@@ -429,7 +435,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
429
435
|
const completionHeaders = new Headers(rscResponse.headers);
|
|
430
436
|
completionHeaders.set(VINEXT_RSC_COMPLETION_METADATA_HEADER, "1");
|
|
431
437
|
const completionResponse = dynamicStaleTimeSeconds !== void 0 && options.isPrerender !== true && !options.isForceStatic && rscResponse.body ? new Response(appendRscCompletionMetadata(rscResponse.body, () => {
|
|
432
|
-
if (!
|
|
438
|
+
if (!consumeRenderDynamicUsage()) return void 0;
|
|
433
439
|
const completedServerStaleTimeSeconds = resolveClientStaleTimeSeconds(options.peekRequestCacheLife?.());
|
|
434
440
|
if (shouldEmitDynamicStaleTime && completedServerStaleTimeSeconds === void 0) return;
|
|
435
441
|
return {
|
|
@@ -447,7 +453,7 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
447
453
|
bypassInterceptionContextCache: options.bypassInterceptionContextCache,
|
|
448
454
|
captureAdmittedDocument,
|
|
449
455
|
cleanPathname: options.cleanPathname,
|
|
450
|
-
consumeDynamicUsage:
|
|
456
|
+
consumeDynamicUsage: consumeRenderDynamicUsage,
|
|
451
457
|
consumeRenderObservationState,
|
|
452
458
|
createHtmlRenderObservation(input) {
|
|
453
459
|
return createAppPageRenderObservation({
|
|
@@ -630,6 +636,12 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
630
636
|
copyLinkHeaderProvenance(response.headers, prerenderResponse.headers);
|
|
631
637
|
return prerenderResponse;
|
|
632
638
|
};
|
|
639
|
+
const markPrerenderedDynamicShellSpecialError = (response, specialError) => {
|
|
640
|
+
if (options.isPrerender === true && response.status === specialError.statusCode && resolveEarlyResponseCacheControl(options) !== null && response.headers.get("cache-control")?.includes("no-store")) getRequestExecutionContext()?.recordPrerenderSpecialError?.({
|
|
641
|
+
headers: resolveAppPageSpecialErrorStoredHeaders(specialError, options.basePath) ?? {},
|
|
642
|
+
status: response.status
|
|
643
|
+
});
|
|
644
|
+
};
|
|
633
645
|
const finalizeShellSpecialErrorResponse = (response, specialError) => {
|
|
634
646
|
if (response.status !== specialError.statusCode) return applyIneligibleRouteCachePolicy(response, options);
|
|
635
647
|
const dynamicUsedDuringRender = consumeRenderDynamicUsage();
|
|
@@ -687,7 +699,6 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
687
699
|
capturedRscDataRef,
|
|
688
700
|
getInitialNavigationCacheMetadata: () => resolveInitialNavigationCacheMetadata(dynamicUsedDuringHtmlRender || peekDynamicUsage(), options.isPrerender === true ? requestCacheLifeForPrerender ?? options.peekRequestCacheLife?.() : options.peekRequestCacheLife?.()),
|
|
689
701
|
fontData,
|
|
690
|
-
hasCustomGlobalError: options.hasCustomGlobalError,
|
|
691
702
|
navigationContext: options.getNavigationContext(),
|
|
692
703
|
basePath: options.basePath,
|
|
693
704
|
clientTraceMetadata: options.clientTraceMetadata,
|
|
@@ -701,7 +712,8 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
701
712
|
scriptNonce: options.scriptNonce,
|
|
702
713
|
sideStream: rscCapture.sideStream,
|
|
703
714
|
ssrHandler,
|
|
704
|
-
fallbackToErrorDocumentOnShellError: options.isPrerender === true && options.isSpeculativePrerender === true
|
|
715
|
+
fallbackToErrorDocumentOnShellError: !(options.isPrerender === true && options.isSpeculativePrerender === true),
|
|
716
|
+
isServerComponentError: rscErrorTracker.isCapturedError,
|
|
705
717
|
waitForAllReady: shouldWaitForAllReady,
|
|
706
718
|
isStaticGeneration: options.isPrerender === true,
|
|
707
719
|
isForceStatic: options.isForceStatic,
|
|
@@ -740,7 +752,9 @@ async function renderAppPageLifecycleImpl(options, renderSpan) {
|
|
|
740
752
|
if (htmlRender.response) {
|
|
741
753
|
if (shellSpecialError && options.isProduction && options.isPrerender !== true && resolveEarlyResponseCacheControl(options) === null) return finalizeShellSpecialErrorResponse(htmlRender.response, shellSpecialError);
|
|
742
754
|
if (shellSpecialError && prerenderedSpecialError) return finalizePrerenderedShellSpecialErrorResponse(htmlRender.response, shellSpecialError, prerenderedSpecialError);
|
|
743
|
-
|
|
755
|
+
const response = applyIneligibleRouteCachePolicy(htmlRender.response, options);
|
|
756
|
+
if (shellSpecialError) markPrerenderedDynamicShellSpecialError(response, shellSpecialError);
|
|
757
|
+
return response;
|
|
744
758
|
}
|
|
745
759
|
let htmlStream = htmlRender.htmlStream;
|
|
746
760
|
if (!htmlStream) throw new Error("[vinext] Expected an HTML stream when no fallback response was returned");
|
|
@@ -100,6 +100,25 @@ type AppPageInterceptMatch<TPage = unknown> = {
|
|
|
100
100
|
targetPatternParts?: readonly string[];
|
|
101
101
|
targetRouteGraphId?: string | null;
|
|
102
102
|
};
|
|
103
|
+
export type AppPageInterceptState<TRoute, TPage> = {
|
|
104
|
+
kind: "none";
|
|
105
|
+
} | {
|
|
106
|
+
kind: "current-route";
|
|
107
|
+
intercept: AppPageInterceptMatch<TPage>;
|
|
108
|
+
} | {
|
|
109
|
+
kind: "source-route";
|
|
110
|
+
intercept: AppPageInterceptMatch<TPage>;
|
|
111
|
+
sourceRoute: TRoute;
|
|
112
|
+
};
|
|
113
|
+
type AppPageInterceptStateLookup<TRoute, TPage> = {
|
|
114
|
+
cleanPathname: string;
|
|
115
|
+
currentRoute: TRoute;
|
|
116
|
+
findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
|
|
117
|
+
getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
|
|
118
|
+
/** The state this request already resolved, which is then reused. */
|
|
119
|
+
interceptState?: AppPageInterceptState<TRoute, TPage>;
|
|
120
|
+
isRscRequest: boolean;
|
|
121
|
+
};
|
|
103
122
|
type ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts> = {
|
|
104
123
|
cleanPathname: string;
|
|
105
124
|
currentParams: AppPageParams;
|
|
@@ -107,6 +126,7 @@ type ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOp
|
|
|
107
126
|
findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
|
|
108
127
|
getRouteParamNames: (route: TRoute) => readonly string[];
|
|
109
128
|
getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
|
|
129
|
+
interceptState?: AppPageInterceptState<TRoute, TPage>;
|
|
110
130
|
isRscRequest: boolean;
|
|
111
131
|
toInterceptOpts: (intercept: AppPageInterceptMatch<TPage>) => TInterceptOpts;
|
|
112
132
|
};
|
|
@@ -128,10 +148,11 @@ type ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement> = {
|
|
|
128
148
|
findIntercept: (pathname: string) => AppPageInterceptMatch<TPage> | null;
|
|
129
149
|
getRouteParamNames: (route: TRoute) => readonly string[];
|
|
130
150
|
getSourceRoute: (sourceRouteIndex: number) => Awaitable<TRoute | undefined>;
|
|
151
|
+
interceptState?: AppPageInterceptState<TRoute, TPage>;
|
|
131
152
|
isRscRequest: boolean;
|
|
132
153
|
resolveNavigationParams: (route: TRoute, params: AppPageParams, pathname: string, interceptOpts: TInterceptOpts) => AppPageParams;
|
|
133
|
-
renderInterceptResponse: (route: TRoute, element: TElement) => Promise<Response> | Response;
|
|
134
|
-
resolveSearchParams?: (route: TRoute, searchParams: URLSearchParams) => Awaitable<URLSearchParams>;
|
|
154
|
+
renderInterceptResponse: (route: TRoute, element: TElement, navigationParams: AppPageParams) => Promise<Response> | Response;
|
|
155
|
+
resolveSearchParams?: (route: TRoute, searchParams: URLSearchParams, interceptOpts: TInterceptOpts) => Awaitable<URLSearchParams>;
|
|
135
156
|
searchParams: URLSearchParams;
|
|
136
157
|
setNavigationContext: (context: {
|
|
137
158
|
params: AppPageParams;
|
|
@@ -146,6 +167,7 @@ type ResolveAppPageInterceptResult<TInterceptOpts> = {
|
|
|
146
167
|
};
|
|
147
168
|
export declare function resolveAppPageGenerateStaticParamsSources(options: ResolveAppPageGenerateStaticParamsSourcesOptions): GenerateStaticParamsSource[];
|
|
148
169
|
export declare function validateAppPageDynamicParams(options: ValidateAppPageDynamicParamsOptions): Promise<Response | null>;
|
|
170
|
+
export declare function resolveAppPageInterceptState<TRoute, TPage>(options: AppPageInterceptStateLookup<TRoute, TPage>): Promise<AppPageInterceptState<TRoute, TPage>>;
|
|
149
171
|
export declare function resolveAppPageInterceptionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageInterceptionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageInterceptionRerenderTargetResult<TRoute, TInterceptOpts>>;
|
|
150
172
|
export declare function resolveAppPageActionRerenderTarget<TRoute, TPage, TInterceptOpts>(options: ResolveAppPageActionRerenderTargetOptions<TRoute, TPage, TInterceptOpts>): Promise<ResolveAppPageActionRerenderTargetResult<TRoute, TInterceptOpts>>;
|
|
151
173
|
export declare function resolveAppPageIntercept<TRoute, TPage, TInterceptOpts, TElement>(options: ResolveAppPageInterceptOptions<TRoute, TPage, TInterceptOpts, TElement>): Promise<ResolveAppPageInterceptResult<TInterceptOpts>>;
|
|
@@ -278,6 +278,7 @@ async function validateAppPageDynamicParams(options) {
|
|
|
278
278
|
return null;
|
|
279
279
|
}
|
|
280
280
|
async function resolveAppPageInterceptState(options) {
|
|
281
|
+
if (options.interceptState) return options.interceptState;
|
|
281
282
|
if (!options.isRscRequest) return { kind: "none" };
|
|
282
283
|
const intercept = options.findIntercept(options.cleanPathname);
|
|
283
284
|
if (!intercept) return { kind: "none" };
|
|
@@ -301,10 +302,9 @@ async function resolveAppPageInterceptionRerenderTarget(options) {
|
|
|
301
302
|
cleanPathname: options.cleanPathname,
|
|
302
303
|
currentRoute: options.currentRoute,
|
|
303
304
|
findIntercept: options.findIntercept,
|
|
304
|
-
getRouteParamNames: options.getRouteParamNames,
|
|
305
305
|
getSourceRoute: options.getSourceRoute,
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
interceptState: options.interceptState,
|
|
307
|
+
isRscRequest: options.isRscRequest
|
|
308
308
|
});
|
|
309
309
|
if (interceptState.kind === "source-route") {
|
|
310
310
|
const sourceMatchedParams = interceptState.intercept.sourceMatchedParams ?? interceptState.intercept.matchedParams;
|
|
@@ -333,10 +333,9 @@ async function resolveAppPageIntercept(options) {
|
|
|
333
333
|
cleanPathname: options.cleanPathname,
|
|
334
334
|
currentRoute: options.currentRoute,
|
|
335
335
|
findIntercept: options.findIntercept,
|
|
336
|
-
getRouteParamNames: options.getRouteParamNames,
|
|
337
336
|
getSourceRoute: options.getSourceRoute,
|
|
338
|
-
|
|
339
|
-
|
|
337
|
+
interceptState: options.interceptState,
|
|
338
|
+
isRscRequest: options.isRscRequest
|
|
340
339
|
});
|
|
341
340
|
if (interceptState.kind === "source-route") {
|
|
342
341
|
const renderRoute = interceptState.sourceRoute;
|
|
@@ -346,17 +345,18 @@ async function resolveAppPageIntercept(options) {
|
|
|
346
345
|
...sourceMatchedParams,
|
|
347
346
|
...interceptState.intercept.matchedParams
|
|
348
347
|
};
|
|
349
|
-
const renderSearchParams = options.resolveSearchParams ? await options.resolveSearchParams(renderRoute, options.searchParams) : options.searchParams;
|
|
348
|
+
const renderSearchParams = options.resolveSearchParams ? await options.resolveSearchParams(renderRoute, options.searchParams, interceptOpts) : options.searchParams;
|
|
350
349
|
const renderParams = pickRouteParams(sourceMatchedParams, options.getRouteParamNames(interceptState.sourceRoute));
|
|
350
|
+
const resolvedNavigationParams = options.resolveNavigationParams(renderRoute, navigationParams, options.cleanPathname, interceptOpts);
|
|
351
351
|
options.setNavigationContext({
|
|
352
|
-
params:
|
|
352
|
+
params: resolvedNavigationParams,
|
|
353
353
|
pathname: options.cleanPathname,
|
|
354
354
|
searchParams: renderSearchParams
|
|
355
355
|
});
|
|
356
356
|
const interceptElement = await options.buildPageElement(renderRoute, renderParams, interceptOpts, renderSearchParams);
|
|
357
357
|
return {
|
|
358
358
|
interceptOpts: void 0,
|
|
359
|
-
response: await options.renderInterceptResponse(renderRoute, interceptElement)
|
|
359
|
+
response: await options.renderInterceptResponse(renderRoute, interceptElement, resolvedNavigationParams)
|
|
360
360
|
};
|
|
361
361
|
}
|
|
362
362
|
return {
|
|
@@ -385,4 +385,4 @@ async function buildAppPageElement(options) {
|
|
|
385
385
|
}
|
|
386
386
|
}
|
|
387
387
|
//#endregion
|
|
388
|
-
export { buildAppPageElement, resolveAppPageActionRerenderTarget, resolveAppPageGenerateStaticParamsSources, resolveAppPageIntercept, resolveAppPageInterceptionRerenderTarget, validateAppPageDynamicParams };
|
|
388
|
+
export { buildAppPageElement, resolveAppPageActionRerenderTarget, resolveAppPageGenerateStaticParamsSources, resolveAppPageIntercept, resolveAppPageInterceptState, resolveAppPageInterceptionRerenderTarget, validateAppPageDynamicParams };
|
|
@@ -2,10 +2,10 @@ import { VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_RSC_VARY_HEADER } from "./app-rsc-v
|
|
|
2
2
|
import { NEXT_CACHE_TAGS_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_STALE_TIME_PENDING_HEADER, VINEXT_TIMING_HEADER } from "./headers.js";
|
|
3
3
|
import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, STATIC_CACHE_CONTROL, buildRevalidateCacheControl } from "./cache-control.js";
|
|
4
4
|
import { setCacheStateHeaders } from "./cache-headers.js";
|
|
5
|
+
import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
|
|
5
6
|
import { markFrameworkLinkHeaders } from "./app-response-header-provenance.js";
|
|
6
7
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
7
8
|
import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, applyRscDeploymentIdHeader } from "./app-rsc-cache-busting.js";
|
|
8
|
-
import { applyPrerenderCacheLifeHeader } from "./prerender-cache-life-header.js";
|
|
9
9
|
//#region src/server/app-page-response.ts
|
|
10
10
|
function applyTimingHeader(headers, timing) {
|
|
11
11
|
if (!timing) return;
|
|
@@ -35,6 +35,8 @@ export type AppPageRouteWiringSlot<TModule extends AppPageModule = AppPageModule
|
|
|
35
35
|
configLayouts?: readonly (TModule | null | undefined)[] | null;
|
|
36
36
|
configLayoutTreePositions?: readonly number[] | null;
|
|
37
37
|
error?: TErrorModule | null;
|
|
38
|
+
errors?: readonly (TErrorModule | null | undefined)[] | null;
|
|
39
|
+
errorTreePositions?: readonly number[] | null;
|
|
38
40
|
layout?: TModule | null;
|
|
39
41
|
layoutIndex: number;
|
|
40
42
|
loading?: TModule | null;
|
|
@@ -99,6 +101,8 @@ export type AppPageRouteWiringRoute<TModule extends AppPageModule = AppPageModul
|
|
|
99
101
|
id: string;
|
|
100
102
|
ownerTreePath: string;
|
|
101
103
|
state: AppElementsSlotBinding["state"];
|
|
104
|
+
/** A slot sub-route whose children its route segments don't describe. */
|
|
105
|
+
synthetic?: boolean;
|
|
102
106
|
} | null;
|
|
103
107
|
/**
|
|
104
108
|
* Static sibling segment names at each dynamic URL level for this route. Used
|
|
@@ -241,6 +245,12 @@ type BuildAppPageElementsOptions<TModule extends AppPageModule = AppPageModule,
|
|
|
241
245
|
sourceRouteGraphId: string;
|
|
242
246
|
}> | null;
|
|
243
247
|
semanticInterceptionTargetRouteId?: string | null;
|
|
248
|
+
/**
|
|
249
|
+
* A sibling intercept's segment state keys, app root first, down to the
|
|
250
|
+
* segment its page element renders. Its BFCache segment levels follow these
|
|
251
|
+
* rather than the source route's segments.
|
|
252
|
+
*/
|
|
253
|
+
siblingInterceptSegmentKeys?: readonly string[] | null;
|
|
244
254
|
sourcePageSegments?: readonly string[] | null;
|
|
245
255
|
};
|
|
246
256
|
export type AppPageTemplateEntry<TModule extends AppPageModule = AppPageModule> = {
|
|
@@ -3,6 +3,7 @@ import { fnv1a64 } from "../utils/hash.js";
|
|
|
3
3
|
import { createInlineScriptTag, escapeHtmlAttr } from "./html.js";
|
|
4
4
|
import { APP_LAYOUT_IDS_KEY, APP_ROOT_LAYOUT_KEY, APP_SKIPPED_LAYOUT_IDS_KEY, APP_STATIC_SIBLINGS_KEY, AppElementsWire, normalizeAppElementsSlotBindings } from "./app-elements-wire.js";
|
|
5
5
|
import { APP_PREFETCH_LOADING_SHELL_MARKER_KEY, APP_STREAMING_METADATA_BODY_ID_PREFIX } from "./app-elements.js";
|
|
6
|
+
import { createBfcacheChildSegmentStateKey } from "./bfcache-identity.js";
|
|
6
7
|
import { AppRouterScrollTarget } from "../shims/app-router-scroll.js";
|
|
7
8
|
import { hashMountedLayoutIdentity } from "./app-mounted-layouts.js";
|
|
8
9
|
import { BfcacheChildrenBoundary, BfcacheSegmentBoundary, Children as Children$1, ParallelSlot, Slot } from "../shims/slot.js";
|
|
@@ -668,9 +669,11 @@ function buildAppPageElements(options) {
|
|
|
668
669
|
branchAccessBoundaries.set(treePosition, entries);
|
|
669
670
|
}
|
|
670
671
|
const branchErrors = /* @__PURE__ */ new Map();
|
|
671
|
-
|
|
672
|
+
const slotErrorModules = !overrideOrPageComponent ? void 0 : hasSlotTreeOverride ? slotOverride?.errorModules : slot.errors;
|
|
673
|
+
const slotErrorTreePositions = hasSlotTreeOverride ? slotOverride?.errorTreePositions : slot.errorTreePositions;
|
|
674
|
+
for (const [index, errorModule] of (slotErrorModules ?? []).entries()) {
|
|
672
675
|
const component = getErrorBoundaryExport(errorModule);
|
|
673
|
-
const treePosition =
|
|
676
|
+
const treePosition = slotErrorTreePositions?.[index];
|
|
674
677
|
if (component && treePosition !== void 0) branchErrors.set(treePosition, component);
|
|
675
678
|
}
|
|
676
679
|
const resolveSlotNotFoundBoundary = (treePosition) => {
|
|
@@ -752,12 +755,6 @@ function buildAppPageElements(options) {
|
|
|
752
755
|
])).filter((treePosition) => !prefetchSlotLoadingEntry || !isOwnedAtRoutePrefetchCutoff && treePosition <= prefetchSlotLoadingEntry.treePosition).sort((left, right) => left - right);
|
|
753
756
|
for (let index = branchTreePositions.length - 1; index >= 0; index--) {
|
|
754
757
|
const treePosition = branchTreePositions[index];
|
|
755
|
-
const nestedBfcacheSegment = nestedBfcacheSegments.find((segment) => segment.treePosition === treePosition);
|
|
756
|
-
if (nestedBfcacheSegment) slotElement = /* @__PURE__ */ jsx(BfcacheSegmentBoundary, {
|
|
757
|
-
id: nestedBfcacheSegment.id,
|
|
758
|
-
stateKey: nestedBfcacheSegment.stateKey,
|
|
759
|
-
children: slotElement
|
|
760
|
-
});
|
|
761
758
|
const loadingComponents = prefetchSlotLoadingEntry ? [] : branchLoadings.get(treePosition) ?? [];
|
|
762
759
|
for (let loadingIndex = loadingComponents.length - 1; loadingIndex >= 0; loadingIndex--) {
|
|
763
760
|
const LoadingComponent = loadingComponents[loadingIndex];
|
|
@@ -785,6 +782,12 @@ function buildAppPageElements(options) {
|
|
|
785
782
|
fallback: SegmentErrorComponent,
|
|
786
783
|
children: slotElement
|
|
787
784
|
});
|
|
785
|
+
const nestedBfcacheSegment = nestedBfcacheSegments.find((segment) => segment.treePosition === treePosition);
|
|
786
|
+
if (nestedBfcacheSegment) slotElement = /* @__PURE__ */ jsx(BfcacheSegmentBoundary, {
|
|
787
|
+
id: nestedBfcacheSegment.id,
|
|
788
|
+
stateKey: nestedBfcacheSegment.stateKey,
|
|
789
|
+
children: slotElement
|
|
790
|
+
});
|
|
788
791
|
const layoutEntriesAtPosition = branchLayouts.get(treePosition) ?? [];
|
|
789
792
|
for (let layoutIndex = layoutEntriesAtPosition.length - 1; layoutIndex >= 0; layoutIndex--) {
|
|
790
793
|
const layoutEntry = layoutEntriesAtPosition[layoutIndex];
|
|
@@ -876,8 +879,22 @@ function buildAppPageElements(options) {
|
|
|
876
879
|
fallback: pageErrorComponent,
|
|
877
880
|
children: routeChildren
|
|
878
881
|
});
|
|
879
|
-
const
|
|
880
|
-
|
|
882
|
+
const firstLayoutWithElementTreePosition = layoutEntries.find((entry) => getDefaultExport(entry.layoutModule) !== null)?.treePosition;
|
|
883
|
+
const siblingInterceptSegmentKeys = options.siblingInterceptSegmentKeys ?? null;
|
|
884
|
+
const keepsSegmentLevelLists = String(process.env.__NEXT_CACHE_COMPONENTS) === "true" && firstLayoutWithElementTreePosition !== void 0 && !options.route.childrenSlot?.synthetic && (options.semanticPageIdentity === void 0 || siblingInterceptSegmentKeys !== null);
|
|
885
|
+
const spineSegmentCount = siblingInterceptSegmentKeys?.length ?? routeSegments.length;
|
|
886
|
+
const spineTreePositions = keepsSegmentLevelLists ? Array.from(/* @__PURE__ */ new Set([...orderedTreePositions, ...Array.from({ length: Math.max(0, spineSegmentCount - firstLayoutWithElementTreePosition) }, (_, offset) => firstLayoutWithElementTreePosition + 1 + offset)])).sort((left, right) => left - right) : orderedTreePositions;
|
|
887
|
+
const renderedTreePositions = isPrefetchLoadingShell ? spineTreePositions.filter(includesPrefetchTreePosition) : spineTreePositions;
|
|
888
|
+
const firstLayoutBelowShell = keepsSegmentLevelLists && isPrefetchLoadingShell ? layoutEntries.find((entry) => !includesPrefetchTreePosition(entry.treePosition) && getDefaultExport(entry.layoutModule) !== null) : void 0;
|
|
889
|
+
let childSegmentElementId = firstLayoutBelowShell?.id ?? pageElementId;
|
|
890
|
+
let childSegmentTreePosition = firstLayoutBelowShell?.treePosition ?? spineSegmentCount + 1;
|
|
891
|
+
const retainChildSegment = (treePosition, children) => treePosition === childSegmentTreePosition - 1 ? /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
|
|
892
|
+
id: childSegmentElementId,
|
|
893
|
+
children
|
|
894
|
+
}) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
|
|
895
|
+
stateKey: createBfcacheChildSegmentStateKey(siblingInterceptSegmentKeys ? JSON.stringify(siblingInterceptSegmentKeys.slice(0, treePosition + 1)) : resolveAppPageTemplateStateKey(routeSegments, treePosition, options.matchedParams)),
|
|
896
|
+
children
|
|
897
|
+
});
|
|
881
898
|
for (let index = renderedTreePositions.length - 1; index >= 0; index--) {
|
|
882
899
|
const treePosition = renderedTreePositions[index];
|
|
883
900
|
const segmentResetKey = resolveRouteSegmentResetKey(treePosition);
|
|
@@ -927,11 +944,12 @@ function buildAppPageElements(options) {
|
|
|
927
944
|
children: segmentChildren
|
|
928
945
|
}, String(process.env.__NEXT_CACHE_COMPONENTS) === "true" ? void 0 : templateStateKey);
|
|
929
946
|
}
|
|
947
|
+
const layoutHasElement = layoutEntry !== void 0 && getDefaultExport(layoutEntry.layoutModule) !== null;
|
|
948
|
+
if (keepsSegmentLevelLists && !layoutHasElement && treePosition > firstLayoutWithElementTreePosition && treePosition < childSegmentTreePosition) segmentChildren = retainChildSegment(treePosition, segmentChildren);
|
|
930
949
|
if (!layoutEntry) {
|
|
931
950
|
routeChildren = segmentChildren;
|
|
932
951
|
continue;
|
|
933
952
|
}
|
|
934
|
-
const layoutHasElement = getDefaultExport(layoutEntry.layoutModule) !== null;
|
|
935
953
|
const layoutIndex = layoutIndicesByTreePosition.get(treePosition) ?? -1;
|
|
936
954
|
const segmentMap = { children: resolveAppPageLayoutSegmentProviderSegments(options.route.childrenRouteSegments ?? routeSegments, layoutEntry.treePosition, options.matchedParams) };
|
|
937
955
|
for (const [slotKey, slot] of Object.entries(options.route.slots ?? {})) {
|
|
@@ -949,13 +967,16 @@ function buildAppPageElements(options) {
|
|
|
949
967
|
children: layoutHasElement ? /* @__PURE__ */ jsx(Slot, {
|
|
950
968
|
id: layoutEntry.id,
|
|
951
969
|
parallelSlots: createAppPageParallelSlotEntries(layoutIndex, layoutEntries, options.route, getEffectiveSlotParams, resolveSlotOverride),
|
|
952
|
-
children: /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
|
|
970
|
+
children: keepsSegmentLevelLists ? retainChildSegment(treePosition, segmentChildren) : /* @__PURE__ */ jsx(BfcacheChildrenBoundary, {
|
|
953
971
|
id: childSegmentElementId,
|
|
954
972
|
children: segmentChildren
|
|
955
973
|
})
|
|
956
974
|
}) : segmentChildren
|
|
957
975
|
});
|
|
958
|
-
if (layoutHasElement)
|
|
976
|
+
if (layoutHasElement) {
|
|
977
|
+
childSegmentElementId = layoutEntry.id;
|
|
978
|
+
childSegmentTreePosition = treePosition;
|
|
979
|
+
}
|
|
959
980
|
}
|
|
960
981
|
const globalErrorComponent = getErrorBoundaryExport(options.globalErrorModule);
|
|
961
982
|
routeChildren = /* @__PURE__ */ jsx(GlobalErrorBoundary, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AppElementsWire } from "./app-elements-wire.js";
|
|
2
2
|
import "./app-elements.js";
|
|
3
3
|
import { createNestedBfcacheSlotSegmentId, deriveBfcacheSegmentIdentity } from "./bfcache-identity.js";
|
|
4
|
-
import { resolveAppPageRouteStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey } from "./app-page-segment-state.js";
|
|
4
|
+
import { APP_PAGE_DEFAULT_SEGMENT_KEY, APP_PAGE_SEGMENT_KEY, resolveAppPageRouteStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey } from "./app-page-segment-state.js";
|
|
5
5
|
//#region src/server/app-page-segment-plan.ts
|
|
6
6
|
function requireGraphSequenceId(ids, index, kind) {
|
|
7
7
|
const id = ids[index];
|
|
@@ -118,6 +118,14 @@ function createAppPageSegmentPlan(options) {
|
|
|
118
118
|
treePosition
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
|
+
if (semanticSegments.length > 0) {
|
|
122
|
+
const leafKey = slotBinding.state === "default" ? APP_PAGE_DEFAULT_SEGMENT_KEY : APP_PAGE_SEGMENT_KEY;
|
|
123
|
+
semanticSegments.push({
|
|
124
|
+
identityKey: JSON.stringify([...boundSegmentKeys, leafKey]),
|
|
125
|
+
stateKey: leafKey,
|
|
126
|
+
treePosition: branchSegments.length
|
|
127
|
+
});
|
|
128
|
+
}
|
|
121
129
|
}
|
|
122
130
|
const includedInPayload = options.includeSlot(ownerTreePosition, targetTreePosition);
|
|
123
131
|
const graphId = graphIds ? graphIds.slots[slotKey] : slot.id ?? slotId;
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { AppPageParams } from "./app-page-boundary.js";
|
|
2
2
|
//#region src/server/app-page-segment-state.d.ts
|
|
3
3
|
export declare const APP_PAGE_SEGMENT_KEY = "__PAGE__";
|
|
4
|
+
export declare const APP_PAGE_DEFAULT_SEGMENT_KEY = "__DEFAULT__";
|
|
4
5
|
export declare function isAppPageRouteGroupSegment(segment: string): boolean;
|
|
5
6
|
export declare function canonicalizeAppPageParams(params: AppPageParams): void;
|
|
7
|
+
/**
|
|
8
|
+
* Rebinds segment state keys and BFCache identities resolved with one set of
|
|
9
|
+
* params to another. Keys are JSON arrays of segment keys, nested to any
|
|
10
|
+
* depth, so each whole segment key is compared with the param tokens
|
|
11
|
+
* `resolveSingleSegmentStateKey` produced for the learned params; only those
|
|
12
|
+
* whose value differs are replaced. Returns null when no token changes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createAppPageSegmentStateKeyRebinder(fromParams: AppPageParams, toParams: AppPageParams): ((stateKey: string) => string) | null;
|
|
6
15
|
export declare function resolveAppPageChildSegments(routeSegments: readonly string[], treePosition: number, params: AppPageParams): string[];
|
|
7
16
|
export declare function resolveAppPageSegmentStateKey(routeSegments: readonly string[], treePosition: number, params: AppPageParams): string;
|
|
8
17
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isCatchAllSegment, isOptionalCatchAllSegment } from "../routing/utils.js";
|
|
2
2
|
//#region src/server/app-page-segment-state.ts
|
|
3
3
|
const APP_PAGE_SEGMENT_KEY = "__PAGE__";
|
|
4
|
+
const APP_PAGE_DEFAULT_SEGMENT_KEY = "__DEFAULT__";
|
|
4
5
|
function isDynamicSegment(segment) {
|
|
5
6
|
return segment.startsWith("[") && segment.endsWith("]") && !segment.includes(".");
|
|
6
7
|
}
|
|
@@ -49,6 +50,58 @@ function resolveSingleSegmentStateKey(segment, params) {
|
|
|
49
50
|
if (!param) return segment;
|
|
50
51
|
return `${param.name}|${formatSegmentStateParamValue(param, params, segment)}|${param.type}`;
|
|
51
52
|
}
|
|
53
|
+
const SEGMENT_PARAM_TYPES = [
|
|
54
|
+
"d",
|
|
55
|
+
"c",
|
|
56
|
+
"oc"
|
|
57
|
+
];
|
|
58
|
+
function formatSegmentParamPattern(param) {
|
|
59
|
+
if (param.type === "oc") return `[[...${param.name}]]`;
|
|
60
|
+
if (param.type === "c") return `[...${param.name}]`;
|
|
61
|
+
return `[${param.name}]`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Rebinds segment state keys and BFCache identities resolved with one set of
|
|
65
|
+
* params to another. Keys are JSON arrays of segment keys, nested to any
|
|
66
|
+
* depth, so each whole segment key is compared with the param tokens
|
|
67
|
+
* `resolveSingleSegmentStateKey` produced for the learned params; only those
|
|
68
|
+
* whose value differs are replaced. Returns null when no token changes.
|
|
69
|
+
*/
|
|
70
|
+
function createAppPageSegmentStateKeyRebinder(fromParams, toParams) {
|
|
71
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const name of /* @__PURE__ */ new Set([...Object.keys(fromParams), ...Object.keys(toParams)])) for (const type of SEGMENT_PARAM_TYPES) {
|
|
73
|
+
const param = {
|
|
74
|
+
name,
|
|
75
|
+
type
|
|
76
|
+
};
|
|
77
|
+
const fallbackSegment = formatSegmentParamPattern(param);
|
|
78
|
+
const from = formatSegmentStateParamValue(param, fromParams, fallbackSegment);
|
|
79
|
+
const to = formatSegmentStateParamValue(param, toParams, fallbackSegment);
|
|
80
|
+
if (from !== to) tokens.set(`${name}|${from}|${type}`, `${name}|${to}|${type}`);
|
|
81
|
+
}
|
|
82
|
+
if (tokens.size === 0) return null;
|
|
83
|
+
const rebind = (stateKey) => {
|
|
84
|
+
const token = tokens.get(stateKey);
|
|
85
|
+
if (token !== void 0) return token;
|
|
86
|
+
if (!stateKey.startsWith("[")) return stateKey;
|
|
87
|
+
let parts;
|
|
88
|
+
try {
|
|
89
|
+
parts = JSON.parse(stateKey);
|
|
90
|
+
} catch {
|
|
91
|
+
return stateKey;
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(parts)) return stateKey;
|
|
94
|
+
let changed = false;
|
|
95
|
+
const rebound = parts.map((part) => {
|
|
96
|
+
if (typeof part !== "string") return part;
|
|
97
|
+
const next = rebind(part);
|
|
98
|
+
if (next !== part) changed = true;
|
|
99
|
+
return next;
|
|
100
|
+
});
|
|
101
|
+
return changed ? JSON.stringify(rebound) : stateKey;
|
|
102
|
+
};
|
|
103
|
+
return rebind;
|
|
104
|
+
}
|
|
52
105
|
function resolveAppPageChildSegments(routeSegments, treePosition, params) {
|
|
53
106
|
const rawSegments = routeSegments.slice(treePosition);
|
|
54
107
|
const resolvedSegments = [];
|
|
@@ -104,4 +157,4 @@ function resolveAppPageTemplateStateKey(routeSegments, treePosition, params) {
|
|
|
104
157
|
return statePath.length > 0 ? JSON.stringify(statePath) : "";
|
|
105
158
|
}
|
|
106
159
|
//#endregion
|
|
107
|
-
export { APP_PAGE_SEGMENT_KEY, canonicalizeAppPageParams, isAppPageRouteGroupSegment, resolveAppPageChildSegments, resolveAppPagePatternStateKey, resolveAppPageRouteStateKey, resolveAppPageSegmentStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey };
|
|
160
|
+
export { APP_PAGE_DEFAULT_SEGMENT_KEY, APP_PAGE_SEGMENT_KEY, canonicalizeAppPageParams, createAppPageSegmentStateKeyRebinder, isAppPageRouteGroupSegment, resolveAppPageChildSegments, resolveAppPagePatternStateKey, resolveAppPageRouteStateKey, resolveAppPageSegmentStateKey, resolveAppPageSemanticSegmentStateKey, resolveAppPageTemplateStateKey };
|
|
@@ -96,10 +96,13 @@ export type AppPageSsrHandler = {
|
|
|
96
96
|
/** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
|
|
97
97
|
* mirroring once a function returns true. */
|
|
98
98
|
mirrorNextFlight?: boolean | (() => boolean);
|
|
99
|
-
/** When true,
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
/** When true, a shell render error other than a redirect, HTTP access
|
|
100
|
+
* fallback or bail-out resolves to the default `__next_error__`
|
|
101
|
+
* error-document shell (with the original flight payload and
|
|
102
|
+
* bootstrap) instead of rejecting. See handleSsr. */
|
|
102
103
|
fallbackToErrorDocumentOnShellError?: boolean;
|
|
104
|
+
/** Whether the RSC render threw the given shell error. See handleSsr. */
|
|
105
|
+
isServerComponentError?: (error: unknown) => boolean;
|
|
103
106
|
/** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
|
|
104
107
|
resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
|
|
105
108
|
dynamicStaleTimeSeconds?: number;
|
|
@@ -153,8 +156,11 @@ type RenderAppPageHtmlStreamOptions = {
|
|
|
153
156
|
isForceStatic?: boolean;
|
|
154
157
|
/** Production render that may be stored under a query-free key. */
|
|
155
158
|
isCacheCandidate?: boolean;
|
|
156
|
-
/**
|
|
159
|
+
/** Whether handleSsr answers a shell error with the error document. Off by
|
|
160
|
+
* default: a boundary render that fails keeps rejecting. */
|
|
157
161
|
fallbackToErrorDocumentOnShellError?: boolean;
|
|
162
|
+
/** Whether the RSC render threw the given shell error. See handleSsr. */
|
|
163
|
+
isServerComponentError?: (error: unknown) => boolean;
|
|
158
164
|
/** The parallel slot not-found boundaries that catch a shell error. See handleSsr. */
|
|
159
165
|
resolveCaughtNotFoundBoundaries?: (error: unknown) => readonly string[] | null;
|
|
160
166
|
/** Dev-only: original server error to surface in the browser overlay. */
|
|
@@ -164,10 +170,6 @@ type RenderAppPageHtmlStreamOptions = {
|
|
|
164
170
|
/** Mirror inline Flight chunks into Next.js's `self.__next_f` transport, or start
|
|
165
171
|
* mirroring once a function returns true. */
|
|
166
172
|
mirrorNextFlight?: boolean | (() => boolean);
|
|
167
|
-
/** True when the app supplies a custom global-error.tsx. Disables the
|
|
168
|
-
* default error-document shell fallback so SSR shell errors keep driving
|
|
169
|
-
* the server-rendered global-error boundary re-render. */
|
|
170
|
-
hasCustomGlobalError?: boolean;
|
|
171
173
|
};
|
|
172
174
|
type RenderAppPageHtmlResponseOptions = {
|
|
173
175
|
clearRequestContext: () => void;
|