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
|
@@ -6,11 +6,9 @@ export type NormalizedRscRequest = {
|
|
|
6
6
|
url: URL;
|
|
7
7
|
/** Normalized pathname with basePath stripped. Used for all internal routing. */
|
|
8
8
|
pathname: string;
|
|
9
|
-
/**
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
requestCleanPathname: string;
|
|
13
|
-
/** True when the request targets a canonical `.rsc` payload URL. */
|
|
9
|
+
/** Original encoded request pathname with basePath stripped. */
|
|
10
|
+
requestPathname: string;
|
|
11
|
+
/** True when the request selects the RSC payload with `RSC: 1`. */
|
|
14
12
|
isRscRequest: boolean;
|
|
15
13
|
/** Sanitized X-Vinext-Interception-Context header (null bytes stripped). null when absent. */
|
|
16
14
|
interceptionContextHeader: string | null;
|
|
@@ -42,13 +40,13 @@ export type NormalizedRscRequest = {
|
|
|
42
40
|
* 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
|
|
43
41
|
* 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
|
|
44
42
|
* `/__vinext/` bypasses this for internal prerender endpoints.
|
|
45
|
-
* 6. RSC detection:
|
|
46
|
-
* `_rsc` cache-busting query is
|
|
47
|
-
* responses do not share the
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
43
|
+
* 6. RSC detection: `RSC: 1`, as in Next.js. A `.rsc` suffix is an ordinary
|
|
44
|
+
* part of the pathname. The internal `_rsc` cache-busting query is
|
|
45
|
+
* validated separately so full-route Flight responses do not share the
|
|
46
|
+
* canonical HTML URL in caches that ignore Vary.
|
|
47
|
+
* 7. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
|
|
48
|
+
* 8. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
|
|
49
|
+
* 9. Read semantic render mode for refresh/action payload rendering
|
|
52
50
|
*
|
|
53
51
|
* @returns A 400 or 404 Response for invalid or out-of-scope inputs,
|
|
54
52
|
* or a NormalizedRscRequest for valid requests.
|
|
@@ -8,7 +8,7 @@ import { normalizePathnameForRouteMatchStrict } from "../routing/utils.js";
|
|
|
8
8
|
import { normalizePath } from "./normalize-path.js";
|
|
9
9
|
import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
|
|
10
10
|
import { APP_RSC_RENDER_MODE_NAVIGATION, APP_RSC_RENDER_MODE_PREFETCH_EMPTY, APP_RSC_RENDER_MODE_PREFETCH_LOADING_SHELL, parseAppRscRenderMode } from "./app-rsc-render-mode.js";
|
|
11
|
-
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM
|
|
11
|
+
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM } from "./app-rsc-cache-busting.js";
|
|
12
12
|
import { normalizeInterceptionContextHeader } from "./app-interception-context-header.js";
|
|
13
13
|
//#region src/server/app-rsc-request-normalization.ts
|
|
14
14
|
function extractFlightRouterStatePath(value, depth = 0) {
|
|
@@ -78,13 +78,13 @@ function tryNormalizeComparablePathAndSearch(value, basePath, baseUrl) {
|
|
|
78
78
|
* 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
|
|
79
79
|
* 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
|
|
80
80
|
* `/__vinext/` bypasses this for internal prerender endpoints.
|
|
81
|
-
* 6. RSC detection:
|
|
82
|
-
* `_rsc` cache-busting query is
|
|
83
|
-
* responses do not share the
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
81
|
+
* 6. RSC detection: `RSC: 1`, as in Next.js. A `.rsc` suffix is an ordinary
|
|
82
|
+
* part of the pathname. The internal `_rsc` cache-busting query is
|
|
83
|
+
* validated separately so full-route Flight responses do not share the
|
|
84
|
+
* canonical HTML URL in caches that ignore Vary.
|
|
85
|
+
* 7. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)
|
|
86
|
+
* 8. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys
|
|
87
|
+
* 9. Read semantic render mode for refresh/action payload rendering
|
|
88
88
|
*
|
|
89
89
|
* @returns A 400 or 404 Response for invalid or out-of-scope inputs,
|
|
90
90
|
* or a NormalizedRscRequest for valid requests.
|
|
@@ -110,9 +110,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
|
|
|
110
110
|
requestPathname = stripBasePath(requestPathname, basePath);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
-
const isRscRequest =
|
|
114
|
-
const cleanPathname = stripRscSuffix(pathname);
|
|
115
|
-
const requestCleanPathname = stripRscSuffix(requestPathname);
|
|
113
|
+
const isRscRequest = request.headers.get("RSC") === "1";
|
|
116
114
|
const interceptionContextHeader = normalizeInterceptionContextHeader(request.headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER));
|
|
117
115
|
const rawInterceptionId = request.headers.get(VINEXT_INTERCEPTION_ID_HEADER);
|
|
118
116
|
if (rawInterceptionId !== null && (rawInterceptionId.length > 4096 || !rawInterceptionId.startsWith("interception:"))) return badRequestResponse();
|
|
@@ -124,7 +122,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
|
|
|
124
122
|
const routerState = parsePrefetchRouterState(request.headers.get(NEXT_ROUTER_STATE_TREE_HEADER));
|
|
125
123
|
if (nextUrl && routerState) {
|
|
126
124
|
const targetUrl = new URL(url);
|
|
127
|
-
targetUrl.pathname =
|
|
125
|
+
targetUrl.pathname = pathname;
|
|
128
126
|
targetUrl.searchParams.delete(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);
|
|
129
127
|
const routerPathAndSearch = tryNormalizeComparablePathAndSearch(routerState.pathAndSearch, basePath, url);
|
|
130
128
|
const nextPathAndSearch = tryNormalizeComparablePathAndSearch(nextUrl, basePath, url);
|
|
@@ -136,8 +134,7 @@ function normalizeRscRequest(request, basePath, allowOutsideBasePath = false, as
|
|
|
136
134
|
hadBasePath,
|
|
137
135
|
url,
|
|
138
136
|
pathname,
|
|
139
|
-
|
|
140
|
-
requestCleanPathname,
|
|
137
|
+
requestPathname,
|
|
141
138
|
isRscRequest,
|
|
142
139
|
interceptionContextHeader,
|
|
143
140
|
interceptionIdHeader,
|
|
@@ -32,6 +32,9 @@ type FinalizeAppRscResponseOptions = {
|
|
|
32
32
|
};
|
|
33
33
|
/** Mark a response whose final target pipeline has already applied config headers. */
|
|
34
34
|
export declare function markAppRscResponseConfigHeadersApplied(response: Response): Response;
|
|
35
|
+
/** Mark a Server Action redirect that streams its target's render, and so keeps its policy. */
|
|
36
|
+
export declare function markStreamedActionRedirectResponse(response: Response): Response;
|
|
37
|
+
export declare function isStreamedActionRedirectResponse(response: Response): boolean;
|
|
35
38
|
/** Apply only the matching next.config headers for an App Router request. */
|
|
36
39
|
export declare function applyAppRscConfigHeaders(headers: Headers, request: Request, options: FinalizeAppRscResponseOptions): Promise<void>;
|
|
37
40
|
/**
|
|
@@ -25,6 +25,15 @@ function markAppRscResponseConfigHeadersApplied(response) {
|
|
|
25
25
|
configHeadersAlreadyApplied.add(response);
|
|
26
26
|
return response;
|
|
27
27
|
}
|
|
28
|
+
const streamedActionRedirects = /* @__PURE__ */ new WeakSet();
|
|
29
|
+
/** Mark a Server Action redirect that streams its target's render, and so keeps its policy. */
|
|
30
|
+
function markStreamedActionRedirectResponse(response) {
|
|
31
|
+
streamedActionRedirects.add(response);
|
|
32
|
+
return response;
|
|
33
|
+
}
|
|
34
|
+
function isStreamedActionRedirectResponse(response) {
|
|
35
|
+
return streamedActionRedirects.has(response);
|
|
36
|
+
}
|
|
28
37
|
/** Apply only the matching next.config headers for an App Router request. */
|
|
29
38
|
async function applyAppRscConfigHeaders(headers, request, options) {
|
|
30
39
|
if (!HAS_CONFIG_HEADERS || !options.configHeaders.length) return;
|
|
@@ -73,6 +82,7 @@ async function finalizeAppRscResponse(response, request, options) {
|
|
|
73
82
|
if (!response.headers.has("Cache-Control")) {
|
|
74
83
|
applyCdnResponseHeaders(response.headers, { cacheControl: "" });
|
|
75
84
|
captureRouteCacheabilityResponsePolicy(captureCdnResponsePolicyHeaders(response.headers));
|
|
85
|
+
if (isStaticFileSignal(response)) response.headers.delete("Cache-Control");
|
|
76
86
|
}
|
|
77
87
|
if (configHeadersAlreadyApplied.has(response)) {
|
|
78
88
|
normalizeExplicitNonCacheablePolicy(response.headers);
|
|
@@ -84,4 +94,4 @@ async function finalizeAppRscResponse(response, request, options) {
|
|
|
84
94
|
return response;
|
|
85
95
|
}
|
|
86
96
|
//#endregion
|
|
87
|
-
export { applyAppRscConfigHeaders, finalizeAppRscResponse, markAppRscResponseConfigHeadersApplied };
|
|
97
|
+
export { applyAppRscConfigHeaders, finalizeAppRscResponse, isStreamedActionRedirectResponse, markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse };
|
|
@@ -10,7 +10,7 @@ import { applyEffectiveRequestCookieHeader, getDraftModeCookieHeader, headersCon
|
|
|
10
10
|
import { ensureFetchPatch, setCurrentFetchSoftTags } from "../shims/fetch-cache.js";
|
|
11
11
|
import { traceFindPageComponents } from "./pages-execution-tracing.js";
|
|
12
12
|
import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
|
|
13
|
-
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, hasRscCacheBustingSearchParam, stripRscCacheBustingSearchParam
|
|
13
|
+
import { VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM, hasRscCacheBustingSearchParam, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
|
|
14
14
|
import { traceAppPageRender } from "./app-page-tracing.js";
|
|
15
15
|
import { buildPageCacheTags } from "./implicit-tags.js";
|
|
16
16
|
import { pickRootParams, setRootParams } from "../shims/root-params.js";
|
|
@@ -48,13 +48,6 @@ function requestWithoutRscCacheBustingSearchParam(request) {
|
|
|
48
48
|
stripRscCacheBustingSearchParam(url);
|
|
49
49
|
return cloneRequestWithUrl(request, url.toString());
|
|
50
50
|
}
|
|
51
|
-
function requestWithoutRscSuffix(request) {
|
|
52
|
-
const url = new URL(request.url);
|
|
53
|
-
const pathname = stripRscSuffix(url.pathname);
|
|
54
|
-
if (pathname === url.pathname) return request;
|
|
55
|
-
url.pathname = pathname;
|
|
56
|
-
return cloneRequestWithUrl(request, url.toString());
|
|
57
|
-
}
|
|
58
51
|
function pathnameForResolvedUrl(resolvedUrl) {
|
|
59
52
|
return resolvedUrl.split("#", 1)[0].split("?", 1)[0];
|
|
60
53
|
}
|
|
@@ -192,9 +185,8 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
|
|
|
192
185
|
setRootParams(rootParams);
|
|
193
186
|
if (props.kind === "app-route-handler") {
|
|
194
187
|
setCurrentFetchSoftTags(buildPageCacheTags(props.cleanPathname, [], [...route.routeSegments], "route"));
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const routeHandlerRequest = isEdgeRouteHandler(route.routeHandler) ? userlandRequest : normalizedUserlandRequest;
|
|
188
|
+
const userlandRequest = requestWithoutRscCacheBustingSearchParam(request);
|
|
189
|
+
const routeHandlerRequest = isEdgeRouteHandler(route.routeHandler) ? userlandRequest : request;
|
|
198
190
|
const routeHandlerUrl = new URL(routeHandlerRequest.url);
|
|
199
191
|
const internalRscValues = isEdgeRouteHandler(route.routeHandler) ? [] : routeHandlerUrl.searchParams.getAll(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM);
|
|
200
192
|
routeHandlerUrl.search = searchParams.toString();
|
|
@@ -225,7 +217,7 @@ async function renderAppWorkerResponseStage(options, rawRequest, ctx, props, sta
|
|
|
225
217
|
handlerStart: process.env.NODE_ENV !== "production" ? performance.now() : 0,
|
|
226
218
|
interceptionContext: normalized.interceptionContextHeader,
|
|
227
219
|
interceptionId: normalized.interceptionIdHeader,
|
|
228
|
-
interceptionPathname: props.matchKind === "resolved" ? props.cleanPathname : normalized.
|
|
220
|
+
interceptionPathname: props.matchKind === "resolved" ? props.cleanPathname : normalized.requestPathname,
|
|
229
221
|
isProgressiveActionRender: false,
|
|
230
222
|
isRscRequest: normalized.isRscRequest,
|
|
231
223
|
middlewareContext,
|
|
@@ -101,6 +101,7 @@ type AppRscRouteForMatching = {
|
|
|
101
101
|
pattern: string;
|
|
102
102
|
patternParts: string[];
|
|
103
103
|
routeHandler?: unknown;
|
|
104
|
+
routeSegments?: readonly string[] | null;
|
|
104
105
|
slots?: Record<string, AppRscSlotForMatching>;
|
|
105
106
|
siblingIntercepts?: AppRscSiblingInterceptForMatching[];
|
|
106
107
|
};
|
|
@@ -108,6 +109,14 @@ type AppRscInterceptMatch = AppRscInterceptLookupEntry & {
|
|
|
108
109
|
matchedParams: AppRscRouteParams;
|
|
109
110
|
sourceRouteIsConcrete: boolean;
|
|
110
111
|
sourceMatchedParams: AppRscRouteParams;
|
|
112
|
+
/**
|
|
113
|
+
* The source route and params the request's interception context names,
|
|
114
|
+
* which the request handler authorizes. They differ from the rendered
|
|
115
|
+
* `sourceRouteIndex` only for a sibling intercept opened from a deeper
|
|
116
|
+
* source, which renders the intercepting route itself.
|
|
117
|
+
*/
|
|
118
|
+
authorizationSourceRouteIndex: number;
|
|
119
|
+
authorizationSourceParams: AppRscRouteParams;
|
|
111
120
|
};
|
|
112
121
|
type AppRscInterceptLoadState = {
|
|
113
122
|
page: unknown;
|
|
@@ -120,6 +129,11 @@ type AppRscInterceptLookupEntry = {
|
|
|
120
129
|
interceptionId: string | null;
|
|
121
130
|
interceptionGraphId: string | null;
|
|
122
131
|
sourceRouteIndex: number;
|
|
132
|
+
/**
|
|
133
|
+
* A sibling intercept whose owner route's page sits in the directory of the
|
|
134
|
+
* interception marker, so the owner is the intercepting route itself.
|
|
135
|
+
*/
|
|
136
|
+
ownerIsInterceptingRoute: boolean;
|
|
123
137
|
slotKey: string;
|
|
124
138
|
targetPattern: string;
|
|
125
139
|
targetPatternParts: string[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { decodeMatchedParams, decodeRouteSegment, splitPathSegments
|
|
1
|
+
import { decodeMatchedParams, decodeRouteSegment, splitPathSegments } from "../routing/utils.js";
|
|
2
2
|
import { createAppRouteGraphInterceptionId } from "../routing/app-route-ids.js";
|
|
3
3
|
import { buildRouteTrie, trieMatchRaw } from "../routing/route-trie.js";
|
|
4
4
|
import { extractRawRoutePatternParams, matchRoutePatternPrefix, matchRoutePatternRaw } from "../routing/route-pattern.js";
|
|
5
|
+
import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
|
|
5
6
|
import { canonicalizeAppPageParams } from "./app-page-segment-state.js";
|
|
6
7
|
//#region src/server/app-rsc-route-matching.ts
|
|
7
8
|
/**
|
|
@@ -13,10 +14,19 @@ const SIBLING_PAGE_INTERCEPT_SLOT_KEY = "__vinext_page_intercept";
|
|
|
13
14
|
function createRouteParams() {
|
|
14
15
|
return Object.create(null);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Whether each encoded raw segment is exactly the spelling the URL parser, as
|
|
19
|
+
* a browser runs it, gives its decoded form: `caf%C3%A9` for `café`, but `q&a`
|
|
20
|
+
* for `q&a`. `%61dmin`, `caf%c3%a9`, `%63af%C3%A9` and `q%26a` are other
|
|
21
|
+
* spellings of a page, which middleware never compared as its pathname.
|
|
22
|
+
*/
|
|
23
|
+
function isCanonicalPathEncoding(rawParts, decodedParts) {
|
|
24
|
+
return rawParts.every((rawPart, index) => !rawPart.includes("%") || rawPart === new URL(`/${decodedParts[index]}`, "http://n").pathname.slice(1));
|
|
25
|
+
}
|
|
26
|
+
function appRscPathnameParts(pathname) {
|
|
17
27
|
const pathOnly = pathname.split("?")[0];
|
|
18
28
|
const normalizedPathname = pathOnly === "/" ? "/" : pathOnly.replace(/\/$/, "");
|
|
19
|
-
return
|
|
29
|
+
return splitPathSegments(normalizedPathname);
|
|
20
30
|
}
|
|
21
31
|
function isAppRouteHandlerRoute(route) {
|
|
22
32
|
return route.routeHandler != null || typeof route.__loadRouteHandler === "function";
|
|
@@ -49,8 +59,10 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
49
59
|
const result = matchRawRequestParts(rawParts);
|
|
50
60
|
if (!result) return null;
|
|
51
61
|
if (decodedPathnameRoutes.size > 0 && result.route.patternParts.some((part) => part.startsWith(":")) && !isAppRouteHandlerRoute(result.route) && rawParts.some((part) => part.includes("%"))) {
|
|
52
|
-
const
|
|
53
|
-
|
|
62
|
+
const decodedParts = rawParts.map(decodeRouteSegment);
|
|
63
|
+
const decodedPathname = decodedParts.join("/");
|
|
64
|
+
const route = decodedPathnameRoutes.get(decodedPathname);
|
|
65
|
+
if (route && route !== result.route && (isCanonicalPathEncoding(rawParts, decodedParts) || getRenderedConcreteUrlPathsForRoute(route.pattern)?.has(`/${decodedPathname}`) === true)) return {
|
|
54
66
|
route,
|
|
55
67
|
params: createRouteParams()
|
|
56
68
|
};
|
|
@@ -62,23 +74,18 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
62
74
|
return interceptionIds.has(interceptionId);
|
|
63
75
|
},
|
|
64
76
|
matchRoute(url) {
|
|
65
|
-
|
|
66
|
-
const result = trieMatchRaw(routeTrie, appRscPathnameParts(url, false));
|
|
67
|
-
if (!result) return null;
|
|
68
|
-
result.params = extractRawRoutePatternParams(result.route.patternParts, rawParts);
|
|
69
|
-
normalizeMatchedParamsForRoute(result);
|
|
70
|
-
return result;
|
|
77
|
+
return matchRequestParts(appRscPathnameParts(url));
|
|
71
78
|
},
|
|
72
79
|
matchRequestRoute(url) {
|
|
73
|
-
return matchRequestParts(appRscPathnameParts(url
|
|
80
|
+
return matchRequestParts(appRscPathnameParts(url));
|
|
74
81
|
},
|
|
75
82
|
matchRawRequestRoute(url) {
|
|
76
|
-
return matchRawRequestParts(appRscPathnameParts(url
|
|
83
|
+
return matchRawRequestParts(appRscPathnameParts(url));
|
|
77
84
|
},
|
|
78
85
|
findIntercept(pathname, sourcePathname = null, interceptionId = null) {
|
|
79
86
|
if (sourcePathname === null) return null;
|
|
80
|
-
const urlParts = appRscPathnameParts(pathname
|
|
81
|
-
const sourceParts = appRscPathnameParts(sourcePathname
|
|
87
|
+
const urlParts = appRscPathnameParts(pathname);
|
|
88
|
+
const sourceParts = appRscPathnameParts(sourcePathname);
|
|
82
89
|
const matchedSourceRoute = matchRawRequestParts(sourceParts);
|
|
83
90
|
for (const entry of interceptLookup) {
|
|
84
91
|
if (interceptionId !== null && entry.interceptionId !== interceptionId) continue;
|
|
@@ -92,15 +99,21 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
92
99
|
if (sourceRoute && isAppRouteHandlerRoute(sourceRoute)) continue;
|
|
93
100
|
const matchedSourceParams = concreteSourceRoute ? concreteSourceRoute.params : sourceRoute ? matchSlotOwnerSourceParams(sourceParts, sourceRoute.patternParts, entry.sourceMatchPatternParts !== null) : null;
|
|
94
101
|
if (!concreteSourceRoute && matchedSourceParams) canonicalizeAppPageParams(matchedSourceParams);
|
|
102
|
+
const ownerRoute = routes[entry.sourceRouteIndex];
|
|
103
|
+
const rendersOwnerRoute = entry.ownerIsInterceptingRoute && concreteSourceRouteIndex !== entry.sourceRouteIndex && ownerRoute !== void 0;
|
|
104
|
+
const renderedSourceParams = rendersOwnerRoute ? matchSlotOwnerSourceParams(sourceParts, ownerRoute.patternParts, true) : matchedSourceParams;
|
|
105
|
+
if (rendersOwnerRoute && renderedSourceParams) canonicalizeAppPageParams(renderedSourceParams);
|
|
95
106
|
if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
|
|
96
|
-
const sourceParams =
|
|
107
|
+
const sourceParams = renderedSourceParams && entry.sourceMatchPatternParts !== null ? pickPatternParams(renderedSourceParams, entry.sourceMatchPatternParts) : renderedSourceParams ?? createRouteParams();
|
|
97
108
|
return {
|
|
98
109
|
...entry,
|
|
99
110
|
page: entry.__loadState.page,
|
|
100
111
|
sourceRouteIsConcrete: concreteSourceRoute !== null,
|
|
101
|
-
sourceRouteIndex: concreteSourceRouteIndex,
|
|
112
|
+
sourceRouteIndex: rendersOwnerRoute ? entry.sourceRouteIndex : concreteSourceRouteIndex,
|
|
102
113
|
matchedParams: mergeMatchedParams(sourceParams, params),
|
|
103
|
-
sourceMatchedParams:
|
|
114
|
+
sourceMatchedParams: renderedSourceParams ?? createRouteParams(),
|
|
115
|
+
authorizationSourceRouteIndex: concreteSourceRouteIndex,
|
|
116
|
+
authorizationSourceParams: matchedSourceParams ?? createRouteParams()
|
|
104
117
|
};
|
|
105
118
|
}
|
|
106
119
|
return null;
|
|
@@ -108,6 +121,15 @@ function createAppRscRouteMatcher(routes, decodedPathnamePatterns = []) {
|
|
|
108
121
|
};
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
124
|
+
* Whether a sibling intercept's owner route is the intercepting route: its
|
|
125
|
+
* page lives in the directory that holds the interception marker. An owner
|
|
126
|
+
* found elsewhere stands in for a marker directory without a page.
|
|
127
|
+
*/
|
|
128
|
+
function isInterceptingRoute(routeSegments, sourcePageSegments) {
|
|
129
|
+
if (!routeSegments || !sourcePageSegments) return false;
|
|
130
|
+
return sourcePageSegments.findIndex((segment) => /^\(\.+\)/.test(segment)) === routeSegments.length && routeSegments.every((segment, index) => segment === sourcePageSegments[index]);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
111
133
|
* Params for the slot owner when interception falls back to it instead of a
|
|
112
134
|
* concrete descendant source route. The owner is what renders, and
|
|
113
135
|
* `matchInterceptRoute` reads the promoted route's params solely from these,
|
|
@@ -195,6 +217,7 @@ function createInterceptLookup(routes) {
|
|
|
195
217
|
interceptionId: typeof slotModule.id === "string" && sourceMatchPattern !== null ? createAppRouteGraphInterceptionId(slotModule.id, sourceMatchPattern, intercept.targetPattern) : null,
|
|
196
218
|
interceptionGraphId: null,
|
|
197
219
|
sourceRouteIndex: ownerRouteIndex,
|
|
220
|
+
ownerIsInterceptingRoute: false,
|
|
198
221
|
slotKey,
|
|
199
222
|
slotId: typeof slotModule.id === "string" ? slotModule.id : null,
|
|
200
223
|
targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
|
|
@@ -246,6 +269,7 @@ function createInterceptLookup(routes) {
|
|
|
246
269
|
interceptionId: typeof intercept.id === "string" ? intercept.id : null,
|
|
247
270
|
interceptionGraphId: typeof intercept.id === "string" ? intercept.id : null,
|
|
248
271
|
sourceRouteIndex: routeIndex,
|
|
272
|
+
ownerIsInterceptingRoute: !isAppRouteHandlerRoute(route) && isInterceptingRoute(route.routeSegments, intercept.sourcePageSegments),
|
|
249
273
|
slotKey: SIBLING_PAGE_INTERCEPT_SLOT_KEY,
|
|
250
274
|
slotId: typeof intercept.slotId === "string" ? intercept.slotId : null,
|
|
251
275
|
targetRouteGraphId: resolveTargetRouteGraphId(intercept.targetPattern),
|
|
@@ -11,6 +11,8 @@ import { internalServerErrorResponse, payloadTooLargeResponse } from "./http-err
|
|
|
11
11
|
import { attachRequestCfMetadata, validateCsrfOrigin, validateServerActionPayload } from "./request-pipeline.js";
|
|
12
12
|
import { splitPathSegments } from "../routing/utils.js";
|
|
13
13
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
14
|
+
import { logRequestError } from "./request-error-log.js";
|
|
15
|
+
import { SERVER_ACTION_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
|
|
14
16
|
import { shareIsolatePromise } from "./isolate-shared-promise.js";
|
|
15
17
|
import { APP_RSC_RENDER_MODE_NAVIGATION } from "./app-rsc-render-mode.js";
|
|
16
18
|
import { isDraftModeRequest, setHeadersContext } from "../shims/headers.js";
|
|
@@ -20,17 +22,18 @@ import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
|
|
|
20
22
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
21
23
|
import { VINEXT_RSC_CONTENT_TYPE, applyRscCompatibilityIdHeader, createRscRequestUrl, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
|
|
22
24
|
import { getSetCookieName } from "./cookie-utils.js";
|
|
25
|
+
import { markAppRscResponseConfigHeadersApplied, markStreamedActionRedirectResponse } from "./app-rsc-response-finalizer.js";
|
|
23
26
|
import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
|
|
24
27
|
import { applyEdgeRuntimeHeader } from "./app-page-response.js";
|
|
25
28
|
import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
|
|
26
|
-
import {
|
|
27
|
-
import { resolveAppPageNavigationParams } from "./app-page-element-builder.js";
|
|
29
|
+
import { resolveAppPageNavigationParams } from "./app-page-navigation-params.js";
|
|
28
30
|
import "./app-page-stream.js";
|
|
31
|
+
import { hasDigest } from "./app-rsc-errors.js";
|
|
29
32
|
import { buildAppPageTags } from "./implicit-tags.js";
|
|
33
|
+
import { resolveAppPageInterceptSegmentConfig } from "./app-page-intercept-segment-config.js";
|
|
30
34
|
import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
|
|
31
35
|
import { createRootParamsUsageController, runWithRootParamsUsage } from "../shims/root-params.js";
|
|
32
36
|
import { createStaticGenerationHeadersContext } from "./app-static-generation.js";
|
|
33
|
-
import { markAppRscResponseConfigHeadersApplied } from "./app-rsc-response-finalizer.js";
|
|
34
37
|
//#region src/server/app-server-action-execution.ts
|
|
35
38
|
/**
|
|
36
39
|
* Run a server-reference load or decode outside every request scope.
|
|
@@ -509,7 +512,10 @@ function createRejectedActionResult(error) {
|
|
|
509
512
|
* Next.js, a thrown string is not logged in development.
|
|
510
513
|
*/
|
|
511
514
|
function logRejectedActionResult(error) {
|
|
512
|
-
if (process.env.NODE_ENV === "production" || hasDigest(error))
|
|
515
|
+
if (process.env.NODE_ENV === "production" || hasDigest(error)) {
|
|
516
|
+
if (typeof error === "string") console.error("[vinext] Server action error:", error);
|
|
517
|
+
else logRequestError("[vinext] Server action error:", error);
|
|
518
|
+
}
|
|
513
519
|
}
|
|
514
520
|
function getActionHttpFallbackStatus(error) {
|
|
515
521
|
const digest = getNextErrorDigest(error);
|
|
@@ -520,7 +526,7 @@ function getActionHttpFallbackStatus(error) {
|
|
|
520
526
|
}
|
|
521
527
|
function createServerActionErrorResponse(error, options) {
|
|
522
528
|
options.getAndClearPendingCookies();
|
|
523
|
-
|
|
529
|
+
logRequestError("[vinext] Server action error:", error);
|
|
524
530
|
options.reportRequestError(error, {
|
|
525
531
|
path: options.cleanPathname,
|
|
526
532
|
method: options.request.method,
|
|
@@ -627,7 +633,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
627
633
|
actionFailed = true;
|
|
628
634
|
actionFailedWithError = getActionHttpFallbackStatus(error) === null && !isServerActionNotFoundError(error, null);
|
|
629
635
|
if (actionFailedWithError) {
|
|
630
|
-
|
|
636
|
+
logRequestError("[vinext] Server action error:", error);
|
|
631
637
|
options.reportRequestError(error, {
|
|
632
638
|
path: options.cleanPathname,
|
|
633
639
|
method: options.request.method,
|
|
@@ -655,7 +661,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
655
661
|
options.clearRequestContext();
|
|
656
662
|
const headers = new Headers();
|
|
657
663
|
mergeMiddlewareResponseHeaders(headers, options.middlewareHeaders);
|
|
658
|
-
headers.set("Cache-Control",
|
|
664
|
+
headers.set("Cache-Control", SERVER_ACTION_CACHE_CONTROL);
|
|
659
665
|
for (const cookie of actionPendingCookies) headers.append("Set-Cookie", cookie);
|
|
660
666
|
if (actionDraftCookie) headers.append("Set-Cookie", actionDraftCookie);
|
|
661
667
|
return internalServerErrorResponse(process.env.NODE_ENV === "production" ? void 0 : "Server action failed: " + getServerActionFailureMessage(actionError), { headers });
|
|
@@ -699,7 +705,7 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
699
705
|
});
|
|
700
706
|
getAndClearActionRevalidationKind();
|
|
701
707
|
options.getAndClearPendingCookies();
|
|
702
|
-
|
|
708
|
+
logRequestError("[vinext] Server action payload parsing error:", error);
|
|
703
709
|
options.reportRequestError(error, {
|
|
704
710
|
path: options.cleanPathname,
|
|
705
711
|
method: options.request.method,
|
|
@@ -875,6 +881,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
875
881
|
applyEdgeRuntimeHeader(redirectHeaders, options.isEdgeRuntime);
|
|
876
882
|
mergeActionRedirectSourceHeaders(redirectHeaders, options.sourceConfigHeaders ?? null);
|
|
877
883
|
mergeActionRedirectSourceHeaders(redirectHeaders, options.middlewareHeaders);
|
|
884
|
+
applyCdnResponseHeaders(redirectHeaders, { cacheControl: SERVER_ACTION_CACHE_CONTROL });
|
|
878
885
|
applyRscCompatibilityIdHeader(redirectHeaders);
|
|
879
886
|
const actionRedirectUrl = applyActionRedirectBasePath(actionRedirect.url, options.basePath ?? "");
|
|
880
887
|
redirectHeaders.set(ACTION_REDIRECT_HEADER, actionRedirectUrl);
|
|
@@ -934,10 +941,10 @@ async function handleServerActionRscRequest(options) {
|
|
|
934
941
|
targetPathname,
|
|
935
942
|
targetRoute: targetMatch?.route ?? null
|
|
936
943
|
}) ? 200 : 303;
|
|
937
|
-
return markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
|
|
944
|
+
return markStreamedActionRedirectResponse(markAppRscResponseConfigHeadersApplied(createServerActionRscResponse(targetResponse.body, {
|
|
938
945
|
status: redirectResponseStatus,
|
|
939
946
|
headers: redirectHeaders
|
|
940
|
-
}, options.clearRequestContext));
|
|
947
|
+
}, options.clearRequestContext)));
|
|
941
948
|
}
|
|
942
949
|
const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
|
|
943
950
|
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
@@ -972,6 +979,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
972
979
|
let element;
|
|
973
980
|
let errorPattern = match ? match.route.pattern : options.cleanPathname;
|
|
974
981
|
let renderedPathAndSearch = null;
|
|
982
|
+
let actionRerenderIsEdgeRuntime = options.isEdgeRuntime;
|
|
975
983
|
const actionRerenderIsRscRequest = true;
|
|
976
984
|
if (match) {
|
|
977
985
|
const { route: actionRoute, params: actionParams } = match;
|
|
@@ -987,7 +995,9 @@ async function handleServerActionRscRequest(options) {
|
|
|
987
995
|
});
|
|
988
996
|
const resolvedActionNavigationParams = resolveAppPageNavigationParams(actionRerenderTarget.route, actionRerenderTarget.navigationParams, options.cleanPathname, actionRerenderTarget.interceptOpts);
|
|
989
997
|
await options.ensureRouteLoaded?.(actionRerenderTarget.route);
|
|
990
|
-
const
|
|
998
|
+
const actionInterceptConfig = actionRerenderTarget.interceptOpts ? resolveAppPageInterceptSegmentConfig(actionRerenderTarget.route, actionRerenderTarget.interceptOpts) : null;
|
|
999
|
+
if (actionInterceptConfig) actionRerenderIsEdgeRuntime = actionInterceptConfig.isEdgeRuntime;
|
|
1000
|
+
const actionRerenderDynamicConfig = actionInterceptConfig ? actionInterceptConfig.dynamicConfig : options.resolveRouteDynamicConfig?.(actionRerenderTarget.route);
|
|
991
1001
|
const actionRerenderSearchParams = prepareActionPageRerenderContext({
|
|
992
1002
|
draftModeCookie: actionDraftCookie,
|
|
993
1003
|
draftModeSecret: options.draftModeSecret,
|
|
@@ -1003,8 +1013,8 @@ async function handleServerActionRscRequest(options) {
|
|
|
1003
1013
|
});
|
|
1004
1014
|
const renderedSearch = options.searchParams.toString();
|
|
1005
1015
|
renderedPathAndSearch = renderedSearch ? `${options.cleanPathname}?${renderedSearch}` : options.cleanPathname;
|
|
1006
|
-
setCurrentFetchCacheMode(options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
|
|
1007
|
-
setCurrentFetchRevalidate(options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
|
|
1016
|
+
setCurrentFetchCacheMode(actionInterceptConfig ? actionInterceptConfig.fetchCacheMode : options.resolveRouteFetchCacheMode?.(actionRerenderTarget.route) ?? null);
|
|
1017
|
+
setCurrentFetchRevalidate(actionInterceptConfig ? actionInterceptConfig.revalidateSeconds : options.resolveRouteRevalidateSeconds?.(actionRerenderTarget.route) ?? null);
|
|
1008
1018
|
setCurrentForceDynamicFetchDefault(actionRerenderDynamicConfig === "force-dynamic");
|
|
1009
1019
|
setCurrentFetchSoftTags(buildServerActionPageTags(actionRerenderTarget.route, options.cleanPathname));
|
|
1010
1020
|
const buildActionRerenderElement = () => options.buildPageElement({
|
|
@@ -1043,7 +1053,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
1043
1053
|
"Content-Type": VINEXT_RSC_CONTENT_TYPE,
|
|
1044
1054
|
Vary: VINEXT_RSC_VARY_HEADER
|
|
1045
1055
|
});
|
|
1046
|
-
applyEdgeRuntimeHeader(actionHeaders,
|
|
1056
|
+
applyEdgeRuntimeHeader(actionHeaders, actionRerenderIsEdgeRuntime);
|
|
1047
1057
|
mergeMiddlewareResponseHeaders(actionHeaders, options.middlewareHeaders);
|
|
1048
1058
|
applyRscCompatibilityIdHeader(actionHeaders);
|
|
1049
1059
|
setActionRevalidatedHeader(actionHeaders, actionRevalidationKind);
|
|
@@ -72,6 +72,12 @@ export declare function handleSsr(rscStream: ReadableStream<Uint8Array>, navCont
|
|
|
72
72
|
*/
|
|
73
73
|
isCacheCandidate?: boolean;
|
|
74
74
|
fallbackToErrorDocumentOnShellError?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Whether the RSC render threw the given shell error. As in Next.js, in
|
|
77
|
+
* dev the error document carries only an error that the HTML render alone
|
|
78
|
+
* threw, for the overlay: the browser reports the others itself.
|
|
79
|
+
*/
|
|
80
|
+
isServerComponentError?: (error: unknown) => boolean;
|
|
75
81
|
/**
|
|
76
82
|
* The parallel slot not-found boundaries that catch the error that
|
|
77
83
|
* rejected the shell, or null when none catches it. The document then
|
|
@@ -2,6 +2,7 @@ import { deferUntilStreamConsumed } from "./defer-until-stream-consumed.js";
|
|
|
2
2
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
3
3
|
import { isOpenRedirectShaped, repeatedSlashRedirectResponse } from "./open-redirect.js";
|
|
4
4
|
import "./server-globals.js";
|
|
5
|
+
import { isNavigationSignalError } from "../utils/navigation-signal.js";
|
|
5
6
|
import { isPprFallbackShellAbortError } from "../shims/ppr-fallback-shell.js";
|
|
6
7
|
import { onRenderDynamicLatched } from "../shims/internal/headers-state.js";
|
|
7
8
|
import { AppRouterContext } from "../shims/internal/app-router-context.js";
|
|
@@ -15,6 +16,7 @@ import { createNavigationRuntimeRenderedSearchScript, createNavigationRuntimeRsc
|
|
|
15
16
|
import { AppElementsWire } from "./app-elements-wire.js";
|
|
16
17
|
import "./app-elements.js";
|
|
17
18
|
import { createInitialBfcacheMaps } from "./app-bfcache-identity.js";
|
|
19
|
+
import { isBailoutToCSRError, isDynamicServerError } from "../shims/navigation-errors.js";
|
|
18
20
|
import { ServerInsertedHTMLContext, clearServerInsertedHTML, getBfcacheIdMapContext, registerServerInsertedHTMLCallback, renderServerInsertedHTML, setNavigationContext } from "../shims/navigation-context-state.js";
|
|
19
21
|
import "../shims/navigation-server.js";
|
|
20
22
|
import { BfcacheIdentityMapContext } from "../shims/slot.js";
|
|
@@ -71,13 +73,14 @@ function createUtf8Stream(html) {
|
|
|
71
73
|
controller.close();
|
|
72
74
|
} });
|
|
73
75
|
}
|
|
74
|
-
function buildBootstrapModuleScript(bootstrapModuleUrl, nonce) {
|
|
76
|
+
function buildBootstrapModuleScript(bootstrapModuleUrl, nonce, crossOrigin) {
|
|
75
77
|
if (!bootstrapModuleUrl) return "";
|
|
76
|
-
|
|
78
|
+
const crossOriginAttribute = crossOrigin === void 0 ? "" : ` crossorigin="${crossOrigin === "use-credentials" ? "use-credentials" : ""}"`;
|
|
79
|
+
return `<script type="module"${createNonceAttribute(nonce)} src="` + escapeHtmlAttr(bootstrapModuleUrl) + `"${crossOriginAttribute} id="_R_" async="" fetchPriority="low"><\/script>`;
|
|
77
80
|
}
|
|
78
|
-
function renderSsrErrorDocumentShell(bootstrapModuleUrl, nonce) {
|
|
79
|
-
const html = renderToStaticMarkup(createElement(DefaultGlobalError, { error: null })).replace("<style>", "<style data-vinext-error-shell-style=\"\">");
|
|
80
|
-
const bootstrapScript = buildBootstrapModuleScript(bootstrapModuleUrl, nonce);
|
|
81
|
+
function renderSsrErrorDocumentShell(bootstrapModuleUrl, nonce, crossOrigin) {
|
|
82
|
+
const html = renderToStaticMarkup(createElement(DefaultGlobalError, { error: null })).replace("<head>", "<head><meta name=\"robots\" content=\"noindex\"/>").replace("<style>", "<style data-vinext-error-shell-style=\"\">");
|
|
83
|
+
const bootstrapScript = buildBootstrapModuleScript(bootstrapModuleUrl, nonce, crossOrigin);
|
|
81
84
|
if (!bootstrapScript) return createUtf8Stream(`<!DOCTYPE html>${html}`);
|
|
82
85
|
const documentClose = "</body></html>";
|
|
83
86
|
if (!html.endsWith(documentClose)) return createUtf8Stream(`<!DOCTYPE html>${html}${bootstrapScript}`);
|
|
@@ -292,6 +295,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
292
295
|
let htmlStream;
|
|
293
296
|
let renderComplete = Promise.resolve();
|
|
294
297
|
let shellErrorRecovered = false;
|
|
298
|
+
let ssrOnlyShellError;
|
|
295
299
|
let caughtShellError;
|
|
296
300
|
let caughtBoundaries = null;
|
|
297
301
|
let shouldDelayInitialHtmlPull = false;
|
|
@@ -355,10 +359,11 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
355
359
|
shouldDelayInitialHtmlPull = options?.waitForAllReady !== true;
|
|
356
360
|
caughtShellError = error;
|
|
357
361
|
htmlStream = caughtHtmlStream;
|
|
358
|
-
} else if (options?.fallbackToErrorDocumentOnShellError !== true ||
|
|
362
|
+
} else if (options?.fallbackToErrorDocumentOnShellError !== true || isNavigationSignalError(error) || isBailoutToCSRError(error) || isDynamicServerError(error)) throw error;
|
|
359
363
|
else {
|
|
360
364
|
shellErrorRecovered = true;
|
|
361
|
-
|
|
365
|
+
if (options?.isServerComponentError?.(error) !== true) ssrOnlyShellError = error;
|
|
366
|
+
htmlStream = renderSsrErrorDocumentShell(bootstrapModuleUrl, options?.scriptNonce, assetCrossOrigin);
|
|
362
367
|
}
|
|
363
368
|
}
|
|
364
369
|
}
|
|
@@ -385,7 +390,7 @@ async function handleSsr(rscStream, navContext, fontData, options) {
|
|
|
385
390
|
const getInsertedHTML = () => {
|
|
386
391
|
const insertedHTML = renderInsertedHtml(renderServerInsertedHTML());
|
|
387
392
|
const errorMetaHTML = errorMetaRenderer.flush();
|
|
388
|
-
const initialDevServerErrorHTML = createInitialDevServerErrorScript(options?.initialDevServerError, options?.scriptNonce);
|
|
393
|
+
const initialDevServerErrorHTML = createInitialDevServerErrorScript(options?.initialDevServerError ?? ssrOnlyShellError, options?.scriptNonce);
|
|
389
394
|
if (didInjectHeadHTML) return insertedHTML + errorMetaHTML + getRenderedSearchHTML();
|
|
390
395
|
didInjectHeadHTML = true;
|
|
391
396
|
const hidesQuery = searchParamsGate !== null && searchParamsGate.gate.decision !== "real";
|
|
@@ -107,11 +107,20 @@ export type SharedAppCacheIdentityProps = Exclude<AppWorkerResponseStageProps, {
|
|
|
107
107
|
export declare function createSharedAppCacheIdentity(request: Request, props: SharedAppCacheIdentityProps): SharedAppCacheIdentity;
|
|
108
108
|
/**
|
|
109
109
|
* Build the query-free cache identity of a shared App page dispatch. The user
|
|
110
|
-
* query leaves both the URL and `resolvedUrl`;
|
|
111
|
-
*
|
|
112
|
-
* representation. HTML identities also drop the RSC selector headers.
|
|
110
|
+
* query leaves both the URL and `resolvedUrl`; an RSC request's validated
|
|
111
|
+
* `_rsc` and the render mode stay because they select the representation. HTML identities also drop the RSC selector headers.
|
|
113
112
|
*/
|
|
114
113
|
export declare function createSharedAppPageCacheIdentity(request: Request, props: AppMatchedWorkerResponseStageProps): SharedAppCacheIdentity;
|
|
114
|
+
/**
|
|
115
|
+
* Build the query-free cache identity of a shared Route Handler or metadata
|
|
116
|
+
* route dispatch. Next.js keys a route handler by its pathname: the query and
|
|
117
|
+
* the RSC selectors pick no representation of a handler's response, and a
|
|
118
|
+
* handler that reads the request's URL or headers is dynamic, so admission
|
|
119
|
+
* refuses its response. Keeping them in the identity would only let a client
|
|
120
|
+
* store one entry per query or selector value. The method, path and route
|
|
121
|
+
* props stay; the selector headers and the props derived from them leave.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createSharedAppRouteCacheIdentity(request: Request, props: AppMatchedWorkerResponseStageProps | AppMetadataWorkerResponseStageProps): SharedAppCacheIdentity;
|
|
115
124
|
/**
|
|
116
125
|
* Build the cache identity of a shared hybrid Pages page dispatch: the
|
|
117
126
|
* dispatched request and props under `origin`, so every host that names it
|