vinext 0.0.53 → 0.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/build/inline-css.d.ts +7 -0
- package/dist/build/inline-css.js +50 -0
- package/dist/build/inline-css.js.map +1 -0
- package/dist/build/prerender.js +2 -1
- package/dist/build/prerender.js.map +1 -1
- package/dist/check.js +19 -3
- package/dist/check.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +3 -1
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/client/window-next.d.ts +7 -0
- package/dist/client/window-next.js.map +1 -1
- package/dist/config/next-config.d.ts +97 -2
- package/dist/config/next-config.js +155 -6
- package/dist/config/next-config.js.map +1 -1
- package/dist/config/tsconfig-paths.d.ts +12 -3
- package/dist/config/tsconfig-paths.js +55 -24
- package/dist/config/tsconfig-paths.js.map +1 -1
- package/dist/deploy.js +13 -0
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-browser-entry.d.ts +11 -1
- package/dist/entries/app-browser-entry.js +16 -6
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +9 -1
- package/dist/entries/app-rsc-entry.js +30 -5
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.d.ts +21 -1
- package/dist/entries/app-rsc-manifest.js +28 -9
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-client-entry.d.ts +4 -1
- package/dist/entries/pages-client-entry.js +18 -2
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +123 -8
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +1 -10
- package/dist/entries/runtime-entry-module.js +2 -12
- package/dist/entries/runtime-entry-module.js.map +1 -1
- package/dist/index.js +144 -44
- package/dist/index.js.map +1 -1
- package/dist/plugins/import-meta-url.d.ts +16 -0
- package/dist/plugins/import-meta-url.js +193 -0
- package/dist/plugins/import-meta-url.js.map +1 -0
- package/dist/plugins/remove-console.d.ts +16 -0
- package/dist/plugins/remove-console.js +176 -0
- package/dist/plugins/remove-console.js.map +1 -0
- package/dist/routing/app-route-graph.d.ts +24 -1
- package/dist/routing/app-route-graph.js +52 -4
- package/dist/routing/app-route-graph.js.map +1 -1
- package/dist/routing/app-router.d.ts +2 -2
- package/dist/routing/app-router.js +2 -2
- package/dist/routing/app-router.js.map +1 -1
- package/dist/routing/file-matcher.d.ts +21 -1
- package/dist/routing/file-matcher.js +39 -1
- package/dist/routing/file-matcher.js.map +1 -1
- package/dist/routing/pages-router.d.ts +1 -1
- package/dist/routing/pages-router.js +10 -3
- package/dist/routing/pages-router.js.map +1 -1
- package/dist/server/api-handler.js +1 -1
- package/dist/server/app-browser-action-result.d.ts +9 -16
- package/dist/server/app-browser-action-result.js +25 -14
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +195 -60
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
- package/dist/server/app-browser-mpa-navigation.js +36 -0
- package/dist/server/app-browser-mpa-navigation.js.map +1 -0
- package/dist/server/app-browser-navigation-controller.d.ts +2 -0
- package/dist/server/app-browser-navigation-controller.js +4 -0
- package/dist/server/app-browser-navigation-controller.js.map +1 -1
- package/dist/server/app-browser-popstate.d.ts +3 -1
- package/dist/server/app-browser-popstate.js +15 -1
- package/dist/server/app-browser-popstate.js.map +1 -1
- package/dist/server/app-browser-state.js +2 -1
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-elements-wire.d.ts +13 -4
- package/dist/server/app-elements-wire.js +10 -1
- package/dist/server/app-elements-wire.js.map +1 -1
- package/dist/server/app-elements.d.ts +2 -2
- package/dist/server/app-elements.js +2 -2
- package/dist/server/app-elements.js.map +1 -1
- package/dist/server/app-fallback-renderer.d.ts +15 -5
- package/dist/server/app-fallback-renderer.js +10 -4
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-inline-css-client.d.ts +7 -0
- package/dist/server/app-inline-css-client.js +37 -0
- package/dist/server/app-inline-css-client.js.map +1 -0
- package/dist/server/app-layout-param-observation.d.ts +30 -0
- package/dist/server/app-layout-param-observation.js +130 -0
- package/dist/server/app-layout-param-observation.js.map +1 -0
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.d.ts +21 -1
- package/dist/server/app-page-boundary.js +28 -3
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +7 -3
- package/dist/server/app-page-cache.js +7 -7
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +10 -1
- package/dist/server/app-page-dispatch.js +126 -79
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.js +12 -28
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-params.d.ts +2 -1
- package/dist/server/app-page-params.js +14 -1
- package/dist/server/app-page-params.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +12 -1
- package/dist/server/app-page-probe.js +116 -1
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-page-render-identity.d.ts +22 -0
- package/dist/server/app-page-render-identity.js +42 -0
- package/dist/server/app-page-render-identity.js.map +1 -0
- package/dist/server/app-page-render.d.ts +8 -1
- package/dist/server/app-page-render.js +4 -1
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-request.d.ts +6 -3
- package/dist/server/app-page-request.js +5 -2
- package/dist/server/app-page-request.js.map +1 -1
- package/dist/server/app-page-response.js +2 -2
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +15 -0
- package/dist/server/app-page-route-wiring.js +7 -5
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +11 -0
- package/dist/server/app-page-stream.js +1 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-response.js +37 -5
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +3 -2
- package/dist/server/app-rsc-cache-busting.js +9 -7
- package/dist/server/app-rsc-cache-busting.js.map +1 -1
- package/dist/server/app-rsc-handler.d.ts +14 -3
- package/dist/server/app-rsc-handler.js +56 -6
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.d.ts +2 -1
- package/dist/server/app-rsc-request-normalization.js +3 -2
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-segment-config.d.ts +1 -1
- package/dist/server/app-segment-config.js +4 -1
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +26 -3
- package/dist/server/app-server-action-execution.js +240 -29
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.d.ts +6 -0
- package/dist/server/app-ssr-entry.js +22 -7
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-error-meta.js +3 -3
- package/dist/server/app-ssr-error-meta.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +2 -1
- package/dist/server/app-ssr-stream.js +176 -31
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +2 -1
- package/dist/server/artifact-compatibility.js +10 -1
- package/dist/server/artifact-compatibility.js.map +1 -1
- package/dist/server/client-reuse-manifest.d.ts +9 -4
- package/dist/server/client-reuse-manifest.js +2 -1
- package/dist/server/client-reuse-manifest.js.map +1 -1
- package/dist/server/client-trace-metadata.d.ts +31 -0
- package/dist/server/client-trace-metadata.js +83 -0
- package/dist/server/client-trace-metadata.js.map +1 -0
- package/dist/server/cookie-utils.d.ts +13 -0
- package/dist/server/cookie-utils.js +20 -0
- package/dist/server/cookie-utils.js.map +1 -0
- package/dist/server/dev-server.d.ts +8 -1
- package/dist/server/dev-server.js +83 -12
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/document-initial-head.d.ts +7 -0
- package/dist/server/document-initial-head.js +35 -0
- package/dist/server/document-initial-head.js.map +1 -0
- package/dist/server/html.d.ts +2 -1
- package/dist/server/html.js +6 -1
- package/dist/server/html.js.map +1 -1
- package/dist/server/isr-cache.d.ts +7 -5
- package/dist/server/isr-cache.js +17 -6
- package/dist/server/isr-cache.js.map +1 -1
- package/dist/server/middleware-runtime.js +1 -2
- package/dist/server/middleware-runtime.js.map +1 -1
- package/dist/server/pages-document-initial-props.d.ts +89 -0
- package/dist/server/pages-document-initial-props.js +140 -0
- package/dist/server/pages-document-initial-props.js.map +1 -0
- package/dist/server/pages-node-compat.js +1 -1
- package/dist/server/pages-page-data.js +3 -0
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-method.d.ts +48 -0
- package/dist/server/pages-page-method.js +19 -0
- package/dist/server/pages-page-method.js.map +1 -0
- package/dist/server/pages-page-response.d.ts +20 -0
- package/dist/server/pages-page-response.js +37 -7
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/pages-serializable-props.d.ts +25 -0
- package/dist/server/pages-serializable-props.js +69 -0
- package/dist/server/pages-serializable-props.js.map +1 -0
- package/dist/server/prod-server.js +16 -6
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/server-action-not-found.js +3 -2
- package/dist/server/server-action-not-found.js.map +1 -1
- package/dist/server/skip-cache-proof.d.ts +23 -2
- package/dist/server/skip-cache-proof.js +81 -12
- package/dist/server/skip-cache-proof.js.map +1 -1
- package/dist/server/static-file-cache.js +2 -1
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
- package/dist/server/static-layout-client-reuse-proof.js +35 -0
- package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
- package/dist/shims/app-router-scroll-state.d.ts +4 -2
- package/dist/shims/app-router-scroll-state.js +16 -3
- package/dist/shims/app-router-scroll-state.js.map +1 -1
- package/dist/shims/app-router-scroll.d.ts +16 -2
- package/dist/shims/app-router-scroll.js +18 -3
- package/dist/shims/app-router-scroll.js.map +1 -1
- package/dist/shims/cache.d.ts +27 -1
- package/dist/shims/cache.js +108 -6
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/document.d.ts +6 -0
- package/dist/shims/document.js +7 -8
- package/dist/shims/document.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +4 -4
- package/dist/shims/error-boundary.js +27 -28
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/error.js +3 -0
- package/dist/shims/error.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +16 -5
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/hash-scroll.d.ts +4 -1
- package/dist/shims/hash-scroll.js +13 -1
- package/dist/shims/hash-scroll.js.map +1 -1
- package/dist/shims/head-state.d.ts +1 -0
- package/dist/shims/head-state.js +18 -3
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +35 -1
- package/dist/shims/head.js +113 -14
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +7 -0
- package/dist/shims/headers.js +9 -1
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/internal/app-route-detection.d.ts +37 -0
- package/dist/shims/internal/app-route-detection.js +69 -0
- package/dist/shims/internal/app-route-detection.js.map +1 -0
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
- package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
- package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
- package/dist/shims/link.d.ts +18 -2
- package/dist/shims/link.js +98 -8
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +7 -6
- package/dist/shims/metadata.js +9 -5
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.d.ts +40 -3
- package/dist/shims/navigation.js +124 -25
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.d.ts +5 -0
- package/dist/shims/router.js +51 -21
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.d.ts +11 -1
- package/dist/shims/script.js +75 -6
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/thenable-params.d.ts +5 -2
- package/dist/shims/thenable-params.js +25 -1
- package/dist/shims/thenable-params.js.map +1 -1
- package/dist/shims/unified-request-context.js +3 -0
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/utils/client-build-manifest.d.ts +15 -0
- package/dist/utils/client-build-manifest.js +54 -0
- package/dist/utils/client-build-manifest.js.map +1 -0
- package/dist/utils/hash.js +1 -1
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +1 -1
- package/dist/utils/lazy-chunks.js.map +1 -1
- package/dist/utils/path.d.ts +13 -0
- package/dist/utils/path.js +16 -0
- package/dist/utils/path.js.map +1 -0
- package/dist/utils/vite-version.d.ts +11 -0
- package/dist/utils/vite-version.js +36 -0
- package/dist/utils/vite-version.js.map +1 -0
- package/package.json +2 -2
|
@@ -67,15 +67,26 @@ type HandleProgressiveActionRequestOptions = {
|
|
|
67
67
|
middlewareContext: AppRscMiddlewareContext;
|
|
68
68
|
request: Request;
|
|
69
69
|
};
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Side-effect headers captured during a progressive (no-JS) server action's
|
|
72
|
+
* non-redirect execution. Forwarded onto the page render response so that
|
|
73
|
+
* `cookies().set(...)` and revalidation kinds reach the browser. See
|
|
74
|
+
* `app-server-action-execution.ts` and issue #1483 for the full rationale.
|
|
75
|
+
*/
|
|
76
|
+
type ProgressiveActionSideEffects = {
|
|
77
|
+
pendingCookies: string[];
|
|
78
|
+
draftCookie: string | null | undefined; /** Numeric revalidation kind: `0` (none), `1` (static+dynamic), etc. */
|
|
79
|
+
revalidationKind: number;
|
|
80
|
+
};
|
|
81
|
+
type ProgressiveActionFormStateResult = ({
|
|
71
82
|
formState: ReactFormState | null;
|
|
72
83
|
kind: "form-state";
|
|
73
|
-
} | {
|
|
84
|
+
} & ProgressiveActionSideEffects) | ({
|
|
74
85
|
actionError: unknown;
|
|
75
86
|
actionFailed: true;
|
|
76
87
|
formState: null;
|
|
77
88
|
kind: "form-state";
|
|
78
|
-
};
|
|
89
|
+
} & ProgressiveActionSideEffects);
|
|
79
90
|
type HandleServerActionRequestOptions = {
|
|
80
91
|
actionId: string | null;
|
|
81
92
|
cleanPathname: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
|
|
2
2
|
import { hasBasePath } from "../utils/base-path.js";
|
|
3
3
|
import { getRequestExecutionContext } from "../shims/request-context.js";
|
|
4
|
-
import { VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER } from "./headers.js";
|
|
4
|
+
import { ACTION_REVALIDATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER } from "./headers.js";
|
|
5
5
|
import { isExternalUrl, matchRedirect, matchRewrite, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
|
|
6
6
|
import { notFoundResponse } from "./http-error-responses.js";
|
|
7
7
|
import { applyConfigHeadersToResponse, cloneRequestWithHeaders, filterInternalHeaders, normalizeTrailingSlash, resolvePublicFileRoute, validateImageUrl } from "./request-pipeline.js";
|
|
@@ -12,19 +12,29 @@ import { getScriptNonceFromHeaderSources } from "./csp.js";
|
|
|
12
12
|
import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
|
|
13
13
|
import { isImageOptimizationPath } from "./image-optimization.js";
|
|
14
14
|
import { prerenderRouteParamsPayloadMatchesRoute, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
|
|
15
|
-
import {
|
|
15
|
+
import { flattenErrorCauses } from "../utils/error-cause.js";
|
|
16
16
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
17
17
|
import "./app-page-response.js";
|
|
18
18
|
import { applyAppMiddleware } from "./app-middleware.js";
|
|
19
19
|
import { buildPageCacheTags } from "./implicit-tags.js";
|
|
20
20
|
import { buildPostMwRequestContext } from "./app-post-middleware-context.js";
|
|
21
|
+
import { pickRootParams, setRootParams } from "../shims/root-params.js";
|
|
21
22
|
import { handleAppPrerenderEndpoint } from "./app-prerender-endpoints.js";
|
|
22
|
-
import { flattenErrorCauses } from "../utils/error-cause.js";
|
|
23
23
|
import { finalizeAppRscResponse } from "./app-rsc-response-finalizer.js";
|
|
24
24
|
import { normalizeRscRequest } from "./app-rsc-request-normalization.js";
|
|
25
25
|
import { handleMetadataRouteRequest } from "./metadata-route-response.js";
|
|
26
26
|
import { runWithPrerenderWorkUnit } from "./prerender-work-unit-setup.js";
|
|
27
27
|
//#region src/server/app-rsc-handler.ts
|
|
28
|
+
function applyMiddlewareContextToResponse(response, middlewareContext) {
|
|
29
|
+
if (!middlewareContext.headers && middlewareContext.status == null) return response;
|
|
30
|
+
const headers = new Headers(response.headers);
|
|
31
|
+
mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);
|
|
32
|
+
return new Response(response.body, {
|
|
33
|
+
status: middlewareContext.status ?? response.status,
|
|
34
|
+
statusText: response.statusText,
|
|
35
|
+
headers
|
|
36
|
+
});
|
|
37
|
+
}
|
|
28
38
|
function hasProperty(value, key) {
|
|
29
39
|
return key in value;
|
|
30
40
|
}
|
|
@@ -155,7 +165,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
155
165
|
cleanPathname,
|
|
156
166
|
makeThenableParams: options.makeThenableParams
|
|
157
167
|
});
|
|
158
|
-
if (metadataRouteResponse) return metadataRouteResponse;
|
|
168
|
+
if (metadataRouteResponse) return applyMiddlewareContextToResponse(metadataRouteResponse, middlewareContext);
|
|
159
169
|
const publicFileResponse = resolvePublicFileRoute({
|
|
160
170
|
cleanPathname,
|
|
161
171
|
middlewareContext,
|
|
@@ -173,6 +183,8 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
173
183
|
searchParams: url.searchParams,
|
|
174
184
|
params: {}
|
|
175
185
|
});
|
|
186
|
+
const preActionMatch = options.matchRoute(cleanPathname);
|
|
187
|
+
if (preActionMatch) setRootParams(pickRootParams(preActionMatch.params, preActionMatch.route.rootParamNames));
|
|
176
188
|
const actionId = request.headers.get("x-rsc-action") ?? request.headers.get("next-action");
|
|
177
189
|
const contentType = request.headers.get("content-type") || "";
|
|
178
190
|
const progressiveActionResult = await options.handleProgressiveActionRequest({
|
|
@@ -200,7 +212,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
200
212
|
searchParams: url.searchParams
|
|
201
213
|
});
|
|
202
214
|
if (serverActionResponse) return serverActionResponse;
|
|
203
|
-
let match =
|
|
215
|
+
let match = preActionMatch;
|
|
204
216
|
if (!match || match.route.isDynamic) {
|
|
205
217
|
const afterFilesRewrite = await applyRewrite({
|
|
206
218
|
basePathState,
|
|
@@ -230,6 +242,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
230
242
|
}
|
|
231
243
|
}
|
|
232
244
|
if (!match) {
|
|
245
|
+
if (process.env.NODE_ENV !== "production" && canonicalPathname === "/favicon.ico") {
|
|
246
|
+
options.clearRequestContext();
|
|
247
|
+
return new Response("", { status: 404 });
|
|
248
|
+
}
|
|
233
249
|
const pagesFallbackResponse = await options.renderPagesFallback?.({
|
|
234
250
|
isRscRequest,
|
|
235
251
|
middlewareContext,
|
|
@@ -275,7 +291,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
275
291
|
searchParams: url.searchParams
|
|
276
292
|
});
|
|
277
293
|
}
|
|
278
|
-
|
|
294
|
+
const pageResponse = await options.dispatchMatchedPage({
|
|
279
295
|
cleanPathname,
|
|
280
296
|
formState,
|
|
281
297
|
actionError,
|
|
@@ -295,6 +311,40 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
295
311
|
searchParams: url.searchParams,
|
|
296
312
|
renderMode
|
|
297
313
|
});
|
|
314
|
+
if (isProgressiveActionRender) return applyProgressiveActionSideEffects(pageResponse, progressiveActionResult);
|
|
315
|
+
return pageResponse;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Append `Set-Cookie` headers and the `x-action-revalidated` marker captured
|
|
319
|
+
* during progressive (no-JS) server action execution to the page render
|
|
320
|
+
* response. See issue #1483.
|
|
321
|
+
*
|
|
322
|
+
* Falls back to rebuilding the response when the headers object is immutable
|
|
323
|
+
* (e.g. `Response.redirect()`), so cookies set by the action ride out on a
|
|
324
|
+
* redirect issued during the rerender too.
|
|
325
|
+
*/
|
|
326
|
+
function applyProgressiveActionSideEffects(response, sideEffects) {
|
|
327
|
+
const hasPendingCookies = sideEffects.pendingCookies.length > 0;
|
|
328
|
+
const hasDraftCookie = Boolean(sideEffects.draftCookie);
|
|
329
|
+
const hasRevalidationKind = sideEffects.revalidationKind !== 0;
|
|
330
|
+
if (!hasPendingCookies && !hasDraftCookie && !hasRevalidationKind) return response;
|
|
331
|
+
const applyTo = (headers) => {
|
|
332
|
+
for (const cookie of sideEffects.pendingCookies) headers.append("Set-Cookie", cookie);
|
|
333
|
+
if (sideEffects.draftCookie) headers.append("Set-Cookie", sideEffects.draftCookie);
|
|
334
|
+
if (hasRevalidationKind) headers.set(ACTION_REVALIDATED_HEADER, JSON.stringify(sideEffects.revalidationKind));
|
|
335
|
+
};
|
|
336
|
+
try {
|
|
337
|
+
applyTo(response.headers);
|
|
338
|
+
return response;
|
|
339
|
+
} catch {
|
|
340
|
+
const headers = new Headers(response.headers);
|
|
341
|
+
applyTo(headers);
|
|
342
|
+
return new Response(response.body, {
|
|
343
|
+
status: response.status,
|
|
344
|
+
statusText: response.statusText,
|
|
345
|
+
headers
|
|
346
|
+
});
|
|
347
|
+
}
|
|
298
348
|
}
|
|
299
349
|
function createAppRscHandler(options) {
|
|
300
350
|
return async function appRscHandler(rawRequest, ctx) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-rsc-handler.js","names":[],"sources":["../../src/server/app-rsc-handler.ts"],"sourcesContent":["import type {\n NextHeader,\n NextI18nConfig,\n NextRedirect,\n NextRewrite,\n} from \"../config/next-config.js\";\nimport {\n isExternalUrl,\n matchRedirect,\n matchRewrite,\n proxyExternalRequest,\n requestContextFromRequest,\n sanitizeDestination,\n type BasePathMatchState,\n} from \"../config/config-matchers.js\";\nimport { headersContextFromRequest } from \"vinext/shims/headers\";\nimport {\n NEXT_ACTION_HEADER,\n RSC_ACTION_HEADER,\n RSC_HEADER,\n VINEXT_MW_CTX_HEADER,\n VINEXT_PRERENDER_ROUTE_PARAMS_HEADER,\n} from \"./headers.js\";\nimport { ensureFetchPatch, setCurrentFetchSoftTags } from \"vinext/shims/fetch-cache\";\nimport type { ReactFormState } from \"react-dom/client\";\nimport {\n getRequestExecutionContext,\n type ExecutionContextLike,\n} from \"vinext/shims/request-context\";\nimport { pickRootParams, setRootParams, type RootParams } from \"vinext/shims/root-params\";\nimport { createRequestContext, runWithRequestContext } from \"vinext/shims/unified-request-context\";\nimport { flattenErrorCauses } from \"../utils/error-cause.js\";\nimport { hasBasePath } from \"../utils/base-path.js\";\nimport { applyAppMiddleware, type AppMiddlewareContext } from \"./app-middleware.js\";\nimport { mergeMiddlewareResponseHeaders } from \"./app-page-response.js\";\nimport { handleAppPrerenderEndpoint } from \"./app-prerender-endpoints.js\";\nimport {\n createRscRedirectLocation,\n resolveInvalidRscCacheBustingRequest,\n stripRscCacheBustingSearchParam,\n stripRscSuffix,\n} from \"./app-rsc-cache-busting.js\";\nimport { finalizeAppRscResponse } from \"./app-rsc-response-finalizer.js\";\nimport { normalizeRscRequest } from \"./app-rsc-request-normalization.js\";\nimport { normalizeDefaultLocalePathname } from \"./pages-i18n.js\";\nimport { notFoundResponse } from \"./http-error-responses.js\";\nimport { getScriptNonceFromHeaderSources } from \"./csp.js\";\nimport { buildPageCacheTags } from \"./implicit-tags.js\";\nimport { isImageOptimizationPath } from \"./image-optimization.js\";\nimport { handleMetadataRouteRequest } from \"./metadata-route-response.js\";\nimport type { MiddlewareModule } from \"./middleware-runtime.js\";\nimport { runWithPrerenderWorkUnit } from \"./prerender-work-unit-setup.js\";\nimport { buildPostMwRequestContext } from \"./app-post-middleware-context.js\";\nimport type { AppRscRenderMode } from \"./app-rsc-render-mode.js\";\nimport {\n cloneRequestWithHeaders,\n filterInternalHeaders,\n applyConfigHeadersToResponse,\n normalizeTrailingSlash,\n resolvePublicFileRoute,\n validateImageUrl,\n} from \"./request-pipeline.js\";\nimport {\n prerenderRouteParamsPayloadMatchesRoute,\n readTrustedPrerenderRouteParams,\n serializePrerenderRouteParamsHeader,\n} from \"./prerender-route-params.js\";\n\ntype AppPageParams = Record<string, string | string[]>;\ntype RequestContext = ReturnType<typeof requestContextFromRequest>;\ntype MetadataRoutes = Parameters<typeof handleMetadataRouteRequest>[0][\"metadataRoutes\"];\ntype MakeThenableParams = Parameters<typeof handleMetadataRouteRequest>[0][\"makeThenableParams\"];\ntype StaticParamsMap = Parameters<typeof handleAppPrerenderEndpoint>[1][\"staticParamsMap\"];\ntype RootParamNamesMap = Parameters<\n typeof handleAppPrerenderEndpoint\n>[1][\"rootParamNamesByPattern\"];\n\ntype AppRscMiddlewareContext = AppMiddlewareContext;\n\ntype AppRscHandlerRoute = {\n isDynamic: boolean;\n page?: unknown;\n pattern: string;\n rootParamNames?: readonly string[];\n routeHandler?: unknown;\n routeSegments: readonly string[];\n};\n\ntype AppRscRouteMatch<TRoute> = {\n params: AppPageParams;\n route: TRoute;\n};\n\ntype DispatchMatchedPageOptions<TRoute> = {\n cleanPathname: string;\n formState: ReactFormState | null;\n actionError?: unknown;\n actionFailed?: boolean;\n handlerStart: number;\n interceptionContext: string | null;\n isProgressiveActionRender: boolean;\n isRscRequest: boolean;\n middlewareContext: AppRscMiddlewareContext;\n mountedSlotsHeader: string | null;\n params: AppPageParams;\n staticParamsValidationParams?: AppPageParams;\n rootParams?: RootParams;\n request: Request;\n route: TRoute;\n scriptNonce?: string;\n searchParams: URLSearchParams;\n renderMode: AppRscRenderMode;\n};\n\ntype DispatchMatchedRouteHandlerOptions<TRoute> = {\n cleanPathname: string;\n middlewareContext: AppRscMiddlewareContext;\n /**\n * `null` for non-dynamic routes. Mirrors Next.js' route handler context\n * shape: user code that does `params ? await params : null` resolves to\n * `null` for routes without dynamic segments. Dynamic routes receive the\n * matched params object.\n */\n params: AppPageParams | null;\n request: Request;\n route: TRoute;\n searchParams: URLSearchParams;\n};\n\ntype HandleProgressiveActionRequestOptions = {\n actionId: string | null;\n cleanPathname: string;\n contentType: string;\n middlewareContext: AppRscMiddlewareContext;\n request: Request;\n};\n\ntype ProgressiveActionFormStateResult =\n | {\n formState: ReactFormState | null;\n kind: \"form-state\";\n }\n | {\n actionError: unknown;\n actionFailed: true;\n formState: null;\n kind: \"form-state\";\n };\n\ntype HandleServerActionRequestOptions = {\n actionId: string | null;\n cleanPathname: string;\n contentType: string;\n interceptionContext: string | null;\n isRscRequest: boolean;\n middlewareContext: AppRscMiddlewareContext;\n mountedSlotsHeader: string | null;\n request: Request;\n searchParams: URLSearchParams;\n};\n\ntype RenderNotFoundOptions<TRoute> = {\n isRscRequest: boolean;\n matchedParams?: AppPageParams;\n middlewareContext: AppRscMiddlewareContext;\n request: Request;\n route: TRoute | null;\n scriptNonce?: string;\n};\n\ntype RenderPagesFallbackOptions = {\n isRscRequest: boolean;\n middlewareContext: AppRscMiddlewareContext;\n request: Request;\n url: URL;\n};\n\ntype NavigationContextValue = {\n params: AppPageParams;\n pathname: string;\n searchParams: URLSearchParams;\n};\n\ntype CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {\n basePath: string;\n clearRequestContext: () => void;\n configHeaders: NextHeader[];\n configRedirects: NextRedirect[];\n configRewrites: {\n afterFiles: NextRewrite[];\n beforeFiles: NextRewrite[];\n fallback: NextRewrite[];\n };\n draftModeSecret: string;\n dispatchMatchedPage: (options: DispatchMatchedPageOptions<TRoute>) => Promise<Response>;\n dispatchMatchedRouteHandler: (\n options: DispatchMatchedRouteHandlerOptions<TRoute>,\n ) => Promise<Response>;\n ensureInstrumentation?: () => Promise<void>;\n handleProgressiveActionRequest: (\n options: HandleProgressiveActionRequestOptions,\n ) => Promise<Response | ProgressiveActionFormStateResult | null>;\n handleServerActionRequest: (\n options: HandleServerActionRequestOptions,\n ) => Promise<Response | null>;\n i18nConfig: NextI18nConfig | null;\n isMiddlewareProxy: boolean;\n loadPrerenderPagesRoutes?: () => Promise<unknown>;\n makeThenableParams: MakeThenableParams;\n matchRoute: (pathname: string) => AppRscRouteMatch<TRoute> | null;\n metadataRoutes: MetadataRoutes;\n middlewareModule: MiddlewareModule | null;\n publicFiles: ReadonlySet<string>;\n renderNotFound: (options: RenderNotFoundOptions<TRoute>) => Promise<Response | null>;\n renderPagesFallback?: (options: RenderPagesFallbackOptions) => Promise<Response | null>;\n rootParamNamesByPattern?: RootParamNamesMap;\n setNavigationContext: (context: NavigationContextValue) => void;\n staticParamsMap: StaticParamsMap;\n trailingSlash: boolean;\n validateDevRequestOrigin?: (request: Request) => Response | null;\n};\n\nfunction hasProperty<TKey extends PropertyKey>(\n value: object,\n key: TKey,\n): value is object & Record<TKey, unknown> {\n return key in value;\n}\n\nfunction isExecutionContextLike(value: unknown): value is ExecutionContextLike {\n if (!value || typeof value !== \"object\") return false;\n return hasProperty(value, \"waitUntil\") && typeof value.waitUntil === \"function\";\n}\n\n// TODO(#1333): once App Router supports `basePath: false` rules (see\n// `normalizeRscRequest` — it 404s out-of-basePath requests before they\n// reach this code), pass `hadBasePath` here and skip the prefix when\n// false, mirroring the same guard in `prod-server.ts` and `deploy.ts`.\nfunction redirectDestinationWithBasePath(destination: string, basePath: string): string {\n if (!basePath || isExternalUrl(destination) || hasBasePath(destination, basePath)) {\n return destination;\n }\n return basePath + destination;\n}\n\nasync function applyRewrite(\n options: {\n basePathState: BasePathMatchState;\n clearRequestContext: () => void;\n request: Request;\n requestContext: RequestContext;\n rewrites: NextRewrite[];\n },\n cleanPathname: string,\n): Promise<Response | string | null> {\n if (!options.rewrites.length) return null;\n\n const rewritten = matchRewrite(\n cleanPathname,\n options.rewrites,\n options.requestContext,\n options.basePathState,\n );\n if (!rewritten) return null;\n\n if (isExternalUrl(rewritten)) {\n options.clearRequestContext();\n return proxyExternalRequest(options.request, rewritten);\n }\n\n return rewritten;\n}\n\nfunction applyConfigHeadersToMiddlewareRedirect(\n response: Response,\n options: {\n basePathState: BasePathMatchState;\n configHeaders: NextHeader[];\n pathname: string;\n requestContext: RequestContext;\n },\n): Response {\n // Non-redirect middleware responses still pass through finalization, where\n // config headers are applied once. Redirects skip finalization to avoid\n // mutating immutable redirect headers, so they need the earlier header layer here.\n if (response.status < 300 || response.status >= 400) return response;\n if (!options.configHeaders.length) return response;\n\n const headers = new Headers();\n applyConfigHeadersToResponse(headers, {\n configHeaders: options.configHeaders,\n pathname: options.pathname,\n requestContext: options.requestContext,\n basePathState: options.basePathState,\n });\n\n if (!headers.entries().next().done) {\n mergeMiddlewareResponseHeaders(headers, response.headers);\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n return response;\n}\n\nasync function handleAppRscRequest<TRoute extends AppRscHandlerRoute>(\n options: CreateAppRscHandlerOptions<TRoute>,\n request: Request,\n preMiddlewareRequestContext: RequestContext,\n isDataRequest: boolean,\n): Promise<Response> {\n const handlerStart = process.env.NODE_ENV !== \"production\" ? performance.now() : 0;\n\n if (process.env.NODE_ENV !== \"production\") {\n const originBlock = options.validateDevRequestOrigin?.(request);\n if (originBlock) return originBlock;\n }\n\n const normalized = normalizeRscRequest(request, options.basePath);\n if (normalized instanceof Response) return normalized;\n\n const { url, isRscRequest, interceptionContextHeader, mountedSlotsHeader, renderMode } =\n normalized;\n let { pathname, cleanPathname } = normalized;\n // Canonical (external) pathname the user requested. Middleware rewrites and\n // next.config.js rewrites mutate `cleanPathname` so internal route matching\n // can find the destination page, but hooks like `usePathname()` must reflect\n // the original URL the user sees in the address bar.\n // Matches Next.js: test/e2e/app-dir/hooks/hooks.test.ts —\n // \"should have the canonical url pathname on rewrite\"\n const canonicalPathname = cleanPathname;\n\n // The request reached this point so it was either under basePath (stripped\n // by normalizeRscRequest) or basePath is empty. In both cases the matcher\n // gating below treats default (basePath: true) rules as eligible. The App\n // Router does not yet support `basePath: false` rules — they would need a\n // pre-strip hook in normalizeRscRequest to fire. Tracked as follow-up to\n // issue #1333.\n const basePathState = { basePath: options.basePath, hadBasePath: true };\n\n const prerenderEndpointResponse = await handleAppPrerenderEndpoint(request, {\n isPrerenderEnabled() {\n return process.env.VINEXT_PRERENDER === \"1\";\n },\n loadPagesRoutes: options.loadPrerenderPagesRoutes,\n pathname,\n rootParamNamesByPattern: options.rootParamNamesByPattern,\n staticParamsMap: options.staticParamsMap,\n });\n if (prerenderEndpointResponse) return prerenderEndpointResponse;\n\n const trailingSlashRedirect = normalizeTrailingSlash(\n pathname,\n options.basePath,\n options.trailingSlash,\n url.search,\n );\n if (trailingSlashRedirect) return trailingSlashRedirect;\n\n // Default-locale path normalisation (issue #1336, item 4). Next.js\n // splices in the (domain-aware) default locale on every request that\n // arrives without a locale prefix before running config redirect / rewrite\n // / header matching. Mirrors resolve-routes.ts lines ~250-263.\n //\n // Defined once here so the same helper is reused for the redirect match\n // below, the middleware-redirect config header match further down, and the\n // post-middleware rewrite matches. `i18nConfig` and `url.hostname` are\n // request-scoped constants from this point on.\n const matchPathname = (p: string): string =>\n normalizeDefaultLocalePathname(p, options.i18nConfig, { hostname: url.hostname });\n\n const redirectPathname = matchPathname(stripRscSuffix(pathname));\n const redirect = matchRedirect(\n redirectPathname,\n options.configRedirects,\n preMiddlewareRequestContext,\n basePathState,\n );\n if (redirect) {\n const destination = sanitizeDestination(\n redirectDestinationWithBasePath(redirect.destination, options.basePath),\n );\n const location =\n isRscRequest && request.headers.get(RSC_HEADER) === \"1\"\n ? await createRscRedirectLocation(destination, request)\n : destination;\n return new Response(null, {\n status: redirect.permanent ? 308 : 307,\n headers: { Location: location },\n });\n }\n\n const rscCacheBustingRedirect = await resolveInvalidRscCacheBustingRequest({\n isRscRequest,\n request,\n });\n if (rscCacheBustingRedirect) return rscCacheBustingRedirect;\n\n const middlewareContext: AppRscMiddlewareContext = {\n headers: null,\n requestHeaders: null,\n status: null,\n };\n\n if (options.middlewareModule) {\n const middlewareResult = await applyAppMiddleware({\n basePath: options.basePath,\n cleanPathname,\n context: middlewareContext,\n i18nConfig: options.i18nConfig,\n isDataRequest,\n isProxy: options.isMiddlewareProxy,\n module: options.middlewareModule,\n request,\n trailingSlash: options.trailingSlash,\n });\n if (middlewareResult.kind === \"response\") {\n return applyConfigHeadersToMiddlewareRedirect(middlewareResult.response, {\n basePathState,\n configHeaders: options.configHeaders,\n pathname: matchPathname(cleanPathname),\n requestContext: preMiddlewareRequestContext,\n });\n }\n\n cleanPathname = middlewareResult.cleanPathname;\n if (middlewareResult.search !== null) {\n url.search = middlewareResult.search;\n }\n }\n\n const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareContext.headers);\n const postMiddlewareRequestContext = buildPostMwRequestContext(request);\n\n // Rewrites (beforeFiles, afterFiles, fallback) use `matchPathname` from\n // above to splice in the default locale before matching. Route matching\n // itself continues to use the un-prefixed `cleanPathname` because App\n // Router files live under `app/...` with no locale segment. See issue\n // #1336 item 4 / pages-i18n.normalizeDefaultLocalePathname.\n const beforeFilesRewrite = await applyRewrite(\n {\n basePathState,\n clearRequestContext: options.clearRequestContext,\n request,\n requestContext: postMiddlewareRequestContext,\n rewrites: options.configRewrites.beforeFiles,\n },\n matchPathname(cleanPathname),\n );\n if (beforeFilesRewrite instanceof Response) return beforeFilesRewrite;\n if (beforeFilesRewrite) cleanPathname = beforeFilesRewrite;\n\n if (isImageOptimizationPath(cleanPathname)) {\n const imageUrlResult = validateImageUrl(url.searchParams.get(\"url\"), request.url);\n if (imageUrlResult instanceof Response) return imageUrlResult;\n return Response.redirect(new URL(imageUrlResult, url.origin).href, 302);\n }\n\n const metadataRouteResponse = await handleMetadataRouteRequest({\n metadataRoutes: options.metadataRoutes,\n cleanPathname,\n makeThenableParams: options.makeThenableParams,\n });\n if (metadataRouteResponse) return metadataRouteResponse;\n\n const publicFileResponse = resolvePublicFileRoute({\n cleanPathname,\n middlewareContext,\n pathname,\n publicFiles: options.publicFiles,\n request,\n });\n if (publicFileResponse) {\n options.clearRequestContext();\n return publicFileResponse;\n }\n\n if (isRscRequest) {\n stripRscCacheBustingSearchParam(url);\n }\n\n options.setNavigationContext({\n pathname: canonicalPathname,\n searchParams: url.searchParams,\n params: {},\n });\n\n const actionId =\n request.headers.get(RSC_ACTION_HEADER) ?? request.headers.get(NEXT_ACTION_HEADER);\n const contentType = request.headers.get(\"content-type\") || \"\";\n\n const progressiveActionResult = await options.handleProgressiveActionRequest({\n actionId,\n cleanPathname,\n contentType,\n middlewareContext,\n request,\n });\n if (progressiveActionResult instanceof Response) return progressiveActionResult;\n const isProgressiveActionRender = progressiveActionResult?.kind === \"form-state\";\n const formState = isProgressiveActionRender ? progressiveActionResult.formState : null;\n const failedProgressiveActionResult =\n isProgressiveActionRender && \"actionFailed\" in progressiveActionResult\n ? progressiveActionResult\n : null;\n const actionFailed = failedProgressiveActionResult !== null;\n const actionError = failedProgressiveActionResult?.actionError;\n\n const serverActionResponse = await options.handleServerActionRequest({\n actionId,\n cleanPathname,\n contentType,\n interceptionContext: interceptionContextHeader,\n isRscRequest,\n middlewareContext,\n mountedSlotsHeader,\n request,\n searchParams: url.searchParams,\n });\n if (serverActionResponse) return serverActionResponse;\n\n let match = options.matchRoute(cleanPathname);\n if (!match || match.route.isDynamic) {\n const afterFilesRewrite = await applyRewrite(\n {\n basePathState,\n clearRequestContext: options.clearRequestContext,\n request,\n requestContext: postMiddlewareRequestContext,\n rewrites: options.configRewrites.afterFiles,\n },\n matchPathname(cleanPathname),\n );\n if (afterFilesRewrite instanceof Response) return afterFilesRewrite;\n if (afterFilesRewrite) {\n cleanPathname = afterFilesRewrite;\n match = options.matchRoute(cleanPathname);\n }\n }\n\n if (!match) {\n const fallbackRewrite = await applyRewrite(\n {\n basePathState,\n clearRequestContext: options.clearRequestContext,\n request,\n requestContext: postMiddlewareRequestContext,\n rewrites: options.configRewrites.fallback,\n },\n matchPathname(cleanPathname),\n );\n if (fallbackRewrite instanceof Response) return fallbackRewrite;\n if (fallbackRewrite) {\n cleanPathname = fallbackRewrite;\n match = options.matchRoute(cleanPathname);\n }\n }\n\n if (!match) {\n const pagesFallbackResponse = await options.renderPagesFallback?.({\n isRscRequest,\n middlewareContext,\n request,\n url,\n });\n if (pagesFallbackResponse) {\n options.clearRequestContext();\n return pagesFallbackResponse;\n }\n\n const renderedNotFoundResponse = await options.renderNotFound({\n isRscRequest,\n middlewareContext,\n request,\n route: null,\n scriptNonce,\n });\n if (renderedNotFoundResponse) return renderedNotFoundResponse;\n\n options.clearRequestContext();\n const headers = new Headers();\n mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);\n return notFoundResponse({ headers });\n }\n\n const { route, params } = match;\n const prerenderRouteParamsPayload = readTrustedPrerenderRouteParams(request);\n const prerenderRouteParams = prerenderRouteParamsPayloadMatchesRoute(\n prerenderRouteParamsPayload,\n route.pattern,\n params,\n )\n ? prerenderRouteParamsPayload.params\n : null;\n const renderParams = prerenderRouteParams ?? params;\n options.setNavigationContext({\n pathname: canonicalPathname,\n searchParams: url.searchParams,\n params: renderParams,\n });\n const rootParams = pickRootParams(renderParams, route.rootParamNames);\n setRootParams(rootParams);\n\n if (route.routeHandler) {\n setCurrentFetchSoftTags(\n buildPageCacheTags(cleanPathname, [], [...route.routeSegments], \"route\"),\n );\n return options.dispatchMatchedRouteHandler({\n cleanPathname,\n middlewareContext,\n // Non-dynamic routes report params as `null` to match Next.js. Internal\n // bookkeeping above (navigation context, root params) keeps the matched\n // object (always `{}` for non-dynamic) so `useParams()` etc. still see\n // an object shape; only the user-facing handler context surfaces null.\n params: route.isDynamic ? renderParams : null,\n request,\n route,\n searchParams: url.searchParams,\n });\n }\n\n return options.dispatchMatchedPage({\n cleanPathname,\n formState,\n actionError,\n actionFailed,\n handlerStart,\n interceptionContext: interceptionContextHeader,\n isProgressiveActionRender,\n isRscRequest,\n middlewareContext,\n mountedSlotsHeader,\n params: renderParams,\n staticParamsValidationParams: prerenderRouteParams === null ? undefined : params,\n rootParams,\n request,\n route,\n scriptNonce,\n searchParams: url.searchParams,\n renderMode,\n });\n}\n\nexport function createAppRscHandler<TRoute extends AppRscHandlerRoute>(\n options: CreateAppRscHandlerOptions<TRoute>,\n): (request: Request, ctx: unknown) => Promise<Response> {\n return async function appRscHandler(rawRequest, ctx) {\n await options.ensureInstrumentation?.();\n\n // Strip forged internal headers at the App Router request boundary.\n // Must happen BEFORE headersContextFromRequest() and\n // requestContextFromRequest() so the captured context never contains\n // attacker-controlled internal headers. This is the correct boundary\n // for pure App Router requests; in hybrid app+pages mode the connect\n // handler already filtered headers upstream and x-vinext-mw-ctx\n // (not in INTERNAL_HEADERS) carries the forwarded middleware context.\n // srvx's NodeRequestHeaders reads from rawHeaders for iteration but falls\n // back to req.headers for .get() / .has(). In the dev server we add\n // x-vinext-mw-ctx to req.headers after the Request is built, so it is\n // visible to .get() but lost when filterInternalHeaders iterates. Read it\n // BEFORE iterating so applyForwardedMiddlewareContext can skip middleware.\n const mwCtx = rawRequest.headers.get(VINEXT_MW_CTX_HEADER);\n // Capture `x-nextjs-data` before filtering — the middleware redirect\n // protocol needs to know whether the inbound request was a `_next/data`\n // fetch to emit `x-nextjs-redirect` instead of an HTTP redirect.\n const isDataRequest = rawRequest.headers.get(\"x-nextjs-data\") === \"1\";\n // Read the trusted prerender route params before filtering strips the\n // route-params header (it IS in VINEXT_INTERNAL_HEADERS), then re-attach the\n // validated value below so the second read in handleAppRscRequest still sees\n // it. The secret was already verified upstream at prod-server's\n // nodeToWebRequest boundary; the surviving secret header (NOT in either\n // internal-header list) lets readTrustedPrerenderRouteParams's\n // VINEXT_PRERENDER gate pass on the reconstructed request. If the secret\n // header is ever added to VINEXT_INTERNAL_HEADERS, that second read breaks.\n const prerenderRouteParamsPayload = readTrustedPrerenderRouteParams(rawRequest);\n const filteredHeaders = filterInternalHeaders(rawRequest.headers);\n if (mwCtx !== null) {\n filteredHeaders.set(VINEXT_MW_CTX_HEADER, mwCtx);\n }\n const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(\n prerenderRouteParamsPayload,\n );\n if (prerenderRouteParamsHeader !== null) {\n filteredHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);\n }\n const request = cloneRequestWithHeaders(rawRequest, filteredHeaders);\n\n const executionContext = isExecutionContextLike(ctx)\n ? ctx\n : (getRequestExecutionContext() ?? null);\n const headersContext = headersContextFromRequest(request, {\n draftModeSecret: options.draftModeSecret,\n });\n const requestContext = createRequestContext({\n headersContext,\n executionContext,\n unstableCacheRevalidation: \"background\",\n });\n\n return runWithRequestContext(requestContext, () =>\n runWithPrerenderWorkUnit(\n async () => {\n ensureFetchPatch();\n const preMiddlewareRequestContext = requestContextFromRequest(request);\n let response: Response;\n\n try {\n response = await handleAppRscRequest(\n options,\n request,\n preMiddlewareRequestContext,\n isDataRequest,\n );\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n flattenErrorCauses(error);\n }\n throw error;\n }\n\n return finalizeAppRscResponse(response, request, {\n basePath: options.basePath,\n configHeaders: options.configHeaders,\n i18nConfig: options.i18nConfig,\n requestContext: preMiddlewareRequestContext,\n });\n },\n { route: () => new URL(request.url).pathname },\n ),\n );\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-rsc-handler.js","names":[],"sources":["../../src/server/app-rsc-handler.ts"],"sourcesContent":["import type {\n NextHeader,\n NextI18nConfig,\n NextRedirect,\n NextRewrite,\n} from \"../config/next-config.js\";\nimport {\n isExternalUrl,\n matchRedirect,\n matchRewrite,\n proxyExternalRequest,\n requestContextFromRequest,\n sanitizeDestination,\n type BasePathMatchState,\n} from \"../config/config-matchers.js\";\nimport { headersContextFromRequest } from \"vinext/shims/headers\";\nimport {\n ACTION_REVALIDATED_HEADER,\n NEXT_ACTION_HEADER,\n RSC_ACTION_HEADER,\n RSC_HEADER,\n VINEXT_MW_CTX_HEADER,\n VINEXT_PRERENDER_ROUTE_PARAMS_HEADER,\n} from \"./headers.js\";\nimport { ensureFetchPatch, setCurrentFetchSoftTags } from \"vinext/shims/fetch-cache\";\nimport type { ReactFormState } from \"react-dom/client\";\nimport {\n getRequestExecutionContext,\n type ExecutionContextLike,\n} from \"vinext/shims/request-context\";\nimport { pickRootParams, setRootParams, type RootParams } from \"vinext/shims/root-params\";\nimport { createRequestContext, runWithRequestContext } from \"vinext/shims/unified-request-context\";\nimport { flattenErrorCauses } from \"../utils/error-cause.js\";\nimport { hasBasePath } from \"../utils/base-path.js\";\nimport { applyAppMiddleware, type AppMiddlewareContext } from \"./app-middleware.js\";\nimport { mergeMiddlewareResponseHeaders } from \"./app-page-response.js\";\nimport { handleAppPrerenderEndpoint } from \"./app-prerender-endpoints.js\";\nimport {\n createRscRedirectLocation,\n resolveInvalidRscCacheBustingRequest,\n stripRscCacheBustingSearchParam,\n stripRscSuffix,\n} from \"./app-rsc-cache-busting.js\";\nimport { finalizeAppRscResponse } from \"./app-rsc-response-finalizer.js\";\nimport { normalizeRscRequest } from \"./app-rsc-request-normalization.js\";\nimport { normalizeDefaultLocalePathname } from \"./pages-i18n.js\";\nimport { notFoundResponse } from \"./http-error-responses.js\";\nimport { getScriptNonceFromHeaderSources } from \"./csp.js\";\nimport { buildPageCacheTags } from \"./implicit-tags.js\";\nimport { isImageOptimizationPath } from \"./image-optimization.js\";\nimport { handleMetadataRouteRequest } from \"./metadata-route-response.js\";\nimport type { MiddlewareModule } from \"./middleware-runtime.js\";\nimport { runWithPrerenderWorkUnit } from \"./prerender-work-unit-setup.js\";\nimport { buildPostMwRequestContext } from \"./app-post-middleware-context.js\";\nimport type { AppRscRenderMode } from \"./app-rsc-render-mode.js\";\nimport {\n cloneRequestWithHeaders,\n filterInternalHeaders,\n applyConfigHeadersToResponse,\n normalizeTrailingSlash,\n resolvePublicFileRoute,\n validateImageUrl,\n} from \"./request-pipeline.js\";\nimport {\n prerenderRouteParamsPayloadMatchesRoute,\n readTrustedPrerenderRouteParams,\n serializePrerenderRouteParamsHeader,\n} from \"./prerender-route-params.js\";\n\ntype AppPageParams = Record<string, string | string[]>;\ntype RequestContext = ReturnType<typeof requestContextFromRequest>;\ntype MetadataRoutes = Parameters<typeof handleMetadataRouteRequest>[0][\"metadataRoutes\"];\ntype MakeThenableParams = Parameters<typeof handleMetadataRouteRequest>[0][\"makeThenableParams\"];\ntype StaticParamsMap = Parameters<typeof handleAppPrerenderEndpoint>[1][\"staticParamsMap\"];\ntype RootParamNamesMap = Parameters<\n typeof handleAppPrerenderEndpoint\n>[1][\"rootParamNamesByPattern\"];\n\ntype AppRscMiddlewareContext = AppMiddlewareContext;\n\ntype AppRscHandlerRoute = {\n isDynamic: boolean;\n page?: unknown;\n pattern: string;\n rootParamNames?: readonly string[];\n routeHandler?: unknown;\n routeSegments: readonly string[];\n};\n\ntype AppRscRouteMatch<TRoute> = {\n params: AppPageParams;\n route: TRoute;\n};\n\nfunction applyMiddlewareContextToResponse(\n response: Response,\n middlewareContext: AppRscMiddlewareContext,\n): Response {\n if (!middlewareContext.headers && middlewareContext.status == null) {\n return response;\n }\n\n const headers = new Headers(response.headers);\n mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);\n\n return new Response(response.body, {\n status: middlewareContext.status ?? response.status,\n statusText: response.statusText,\n headers,\n });\n}\n\ntype DispatchMatchedPageOptions<TRoute> = {\n cleanPathname: string;\n formState: ReactFormState | null;\n actionError?: unknown;\n actionFailed?: boolean;\n handlerStart: number;\n interceptionContext: string | null;\n isProgressiveActionRender: boolean;\n isRscRequest: boolean;\n middlewareContext: AppRscMiddlewareContext;\n mountedSlotsHeader: string | null;\n params: AppPageParams;\n staticParamsValidationParams?: AppPageParams;\n rootParams?: RootParams;\n request: Request;\n route: TRoute;\n scriptNonce?: string;\n searchParams: URLSearchParams;\n renderMode: AppRscRenderMode;\n};\n\ntype DispatchMatchedRouteHandlerOptions<TRoute> = {\n cleanPathname: string;\n middlewareContext: AppRscMiddlewareContext;\n /**\n * `null` for non-dynamic routes. Mirrors Next.js' route handler context\n * shape: user code that does `params ? await params : null` resolves to\n * `null` for routes without dynamic segments. Dynamic routes receive the\n * matched params object.\n */\n params: AppPageParams | null;\n request: Request;\n route: TRoute;\n searchParams: URLSearchParams;\n};\n\ntype HandleProgressiveActionRequestOptions = {\n actionId: string | null;\n cleanPathname: string;\n contentType: string;\n middlewareContext: AppRscMiddlewareContext;\n request: Request;\n};\n\n/**\n * Side-effect headers captured during a progressive (no-JS) server action's\n * non-redirect execution. Forwarded onto the page render response so that\n * `cookies().set(...)` and revalidation kinds reach the browser. See\n * `app-server-action-execution.ts` and issue #1483 for the full rationale.\n */\ntype ProgressiveActionSideEffects = {\n pendingCookies: string[];\n draftCookie: string | null | undefined;\n /** Numeric revalidation kind: `0` (none), `1` (static+dynamic), etc. */\n revalidationKind: number;\n};\n\ntype ProgressiveActionFormStateResult =\n | ({\n formState: ReactFormState | null;\n kind: \"form-state\";\n } & ProgressiveActionSideEffects)\n | ({\n actionError: unknown;\n actionFailed: true;\n formState: null;\n kind: \"form-state\";\n } & ProgressiveActionSideEffects);\n\ntype HandleServerActionRequestOptions = {\n actionId: string | null;\n cleanPathname: string;\n contentType: string;\n interceptionContext: string | null;\n isRscRequest: boolean;\n middlewareContext: AppRscMiddlewareContext;\n mountedSlotsHeader: string | null;\n request: Request;\n searchParams: URLSearchParams;\n};\n\ntype RenderNotFoundOptions<TRoute> = {\n isRscRequest: boolean;\n matchedParams?: AppPageParams;\n middlewareContext: AppRscMiddlewareContext;\n request: Request;\n route: TRoute | null;\n scriptNonce?: string;\n};\n\ntype RenderPagesFallbackOptions = {\n isRscRequest: boolean;\n middlewareContext: AppRscMiddlewareContext;\n request: Request;\n url: URL;\n};\n\ntype NavigationContextValue = {\n params: AppPageParams;\n pathname: string;\n searchParams: URLSearchParams;\n};\n\ntype CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {\n basePath: string;\n clearRequestContext: () => void;\n configHeaders: NextHeader[];\n configRedirects: NextRedirect[];\n configRewrites: {\n afterFiles: NextRewrite[];\n beforeFiles: NextRewrite[];\n fallback: NextRewrite[];\n };\n draftModeSecret: string;\n dispatchMatchedPage: (options: DispatchMatchedPageOptions<TRoute>) => Promise<Response>;\n dispatchMatchedRouteHandler: (\n options: DispatchMatchedRouteHandlerOptions<TRoute>,\n ) => Promise<Response>;\n ensureInstrumentation?: () => Promise<void>;\n handleProgressiveActionRequest: (\n options: HandleProgressiveActionRequestOptions,\n ) => Promise<Response | ProgressiveActionFormStateResult | null>;\n handleServerActionRequest: (\n options: HandleServerActionRequestOptions,\n ) => Promise<Response | null>;\n i18nConfig: NextI18nConfig | null;\n isMiddlewareProxy: boolean;\n loadPrerenderPagesRoutes?: () => Promise<unknown>;\n makeThenableParams: MakeThenableParams;\n matchRoute: (pathname: string) => AppRscRouteMatch<TRoute> | null;\n metadataRoutes: MetadataRoutes;\n middlewareModule: MiddlewareModule | null;\n publicFiles: ReadonlySet<string>;\n renderNotFound: (options: RenderNotFoundOptions<TRoute>) => Promise<Response | null>;\n renderPagesFallback?: (options: RenderPagesFallbackOptions) => Promise<Response | null>;\n rootParamNamesByPattern?: RootParamNamesMap;\n setNavigationContext: (context: NavigationContextValue) => void;\n staticParamsMap: StaticParamsMap;\n trailingSlash: boolean;\n validateDevRequestOrigin?: (request: Request) => Response | null;\n};\n\nfunction hasProperty<TKey extends PropertyKey>(\n value: object,\n key: TKey,\n): value is object & Record<TKey, unknown> {\n return key in value;\n}\n\nfunction isExecutionContextLike(value: unknown): value is ExecutionContextLike {\n if (!value || typeof value !== \"object\") return false;\n return hasProperty(value, \"waitUntil\") && typeof value.waitUntil === \"function\";\n}\n\n// TODO(#1333): once App Router supports `basePath: false` rules (see\n// `normalizeRscRequest` — it 404s out-of-basePath requests before they\n// reach this code), pass `hadBasePath` here and skip the prefix when\n// false, mirroring the same guard in `prod-server.ts` and `deploy.ts`.\nfunction redirectDestinationWithBasePath(destination: string, basePath: string): string {\n if (!basePath || isExternalUrl(destination) || hasBasePath(destination, basePath)) {\n return destination;\n }\n return basePath + destination;\n}\n\nasync function applyRewrite(\n options: {\n basePathState: BasePathMatchState;\n clearRequestContext: () => void;\n request: Request;\n requestContext: RequestContext;\n rewrites: NextRewrite[];\n },\n cleanPathname: string,\n): Promise<Response | string | null> {\n if (!options.rewrites.length) return null;\n\n const rewritten = matchRewrite(\n cleanPathname,\n options.rewrites,\n options.requestContext,\n options.basePathState,\n );\n if (!rewritten) return null;\n\n if (isExternalUrl(rewritten)) {\n options.clearRequestContext();\n return proxyExternalRequest(options.request, rewritten);\n }\n\n return rewritten;\n}\n\nfunction applyConfigHeadersToMiddlewareRedirect(\n response: Response,\n options: {\n basePathState: BasePathMatchState;\n configHeaders: NextHeader[];\n pathname: string;\n requestContext: RequestContext;\n },\n): Response {\n // Non-redirect middleware responses still pass through finalization, where\n // config headers are applied once. Redirects skip finalization to avoid\n // mutating immutable redirect headers, so they need the earlier header layer here.\n if (response.status < 300 || response.status >= 400) return response;\n if (!options.configHeaders.length) return response;\n\n const headers = new Headers();\n applyConfigHeadersToResponse(headers, {\n configHeaders: options.configHeaders,\n pathname: options.pathname,\n requestContext: options.requestContext,\n basePathState: options.basePathState,\n });\n\n if (!headers.entries().next().done) {\n mergeMiddlewareResponseHeaders(headers, response.headers);\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n\n return response;\n}\n\nasync function handleAppRscRequest<TRoute extends AppRscHandlerRoute>(\n options: CreateAppRscHandlerOptions<TRoute>,\n request: Request,\n preMiddlewareRequestContext: RequestContext,\n isDataRequest: boolean,\n): Promise<Response> {\n const handlerStart = process.env.NODE_ENV !== \"production\" ? performance.now() : 0;\n\n if (process.env.NODE_ENV !== \"production\") {\n const originBlock = options.validateDevRequestOrigin?.(request);\n if (originBlock) return originBlock;\n }\n\n const normalized = normalizeRscRequest(request, options.basePath);\n if (normalized instanceof Response) return normalized;\n\n const { url, isRscRequest, interceptionContextHeader, mountedSlotsHeader, renderMode } =\n normalized;\n let { pathname, cleanPathname } = normalized;\n // Canonical (external) pathname the user requested. Middleware rewrites and\n // next.config.js rewrites mutate `cleanPathname` so internal route matching\n // can find the destination page, but hooks like `usePathname()` must reflect\n // the original URL the user sees in the address bar.\n // Matches Next.js: test/e2e/app-dir/hooks/hooks.test.ts —\n // \"should have the canonical url pathname on rewrite\"\n const canonicalPathname = cleanPathname;\n\n // The request reached this point so it was either under basePath (stripped\n // by normalizeRscRequest) or basePath is empty. In both cases the matcher\n // gating below treats default (basePath: true) rules as eligible. The App\n // Router does not yet support `basePath: false` rules — they would need a\n // pre-strip hook in normalizeRscRequest to fire. Tracked as follow-up to\n // issue #1333.\n const basePathState = { basePath: options.basePath, hadBasePath: true };\n\n const prerenderEndpointResponse = await handleAppPrerenderEndpoint(request, {\n isPrerenderEnabled() {\n return process.env.VINEXT_PRERENDER === \"1\";\n },\n loadPagesRoutes: options.loadPrerenderPagesRoutes,\n pathname,\n rootParamNamesByPattern: options.rootParamNamesByPattern,\n staticParamsMap: options.staticParamsMap,\n });\n if (prerenderEndpointResponse) return prerenderEndpointResponse;\n\n const trailingSlashRedirect = normalizeTrailingSlash(\n pathname,\n options.basePath,\n options.trailingSlash,\n url.search,\n );\n if (trailingSlashRedirect) return trailingSlashRedirect;\n\n // Default-locale path normalisation (issue #1336, item 4). Next.js\n // splices in the (domain-aware) default locale on every request that\n // arrives without a locale prefix before running config redirect / rewrite\n // / header matching. Mirrors resolve-routes.ts lines ~250-263.\n //\n // Defined once here so the same helper is reused for the redirect match\n // below, the middleware-redirect config header match further down, and the\n // post-middleware rewrite matches. `i18nConfig` and `url.hostname` are\n // request-scoped constants from this point on.\n const matchPathname = (p: string): string =>\n normalizeDefaultLocalePathname(p, options.i18nConfig, { hostname: url.hostname });\n\n const redirectPathname = matchPathname(stripRscSuffix(pathname));\n const redirect = matchRedirect(\n redirectPathname,\n options.configRedirects,\n preMiddlewareRequestContext,\n basePathState,\n );\n if (redirect) {\n const destination = sanitizeDestination(\n redirectDestinationWithBasePath(redirect.destination, options.basePath),\n );\n const location =\n isRscRequest && request.headers.get(RSC_HEADER) === \"1\"\n ? await createRscRedirectLocation(destination, request)\n : destination;\n return new Response(null, {\n status: redirect.permanent ? 308 : 307,\n headers: { Location: location },\n });\n }\n\n const rscCacheBustingRedirect = await resolveInvalidRscCacheBustingRequest({\n isRscRequest,\n request,\n });\n if (rscCacheBustingRedirect) return rscCacheBustingRedirect;\n\n const middlewareContext: AppRscMiddlewareContext = {\n headers: null,\n requestHeaders: null,\n status: null,\n };\n\n if (options.middlewareModule) {\n const middlewareResult = await applyAppMiddleware({\n basePath: options.basePath,\n cleanPathname,\n context: middlewareContext,\n i18nConfig: options.i18nConfig,\n isDataRequest,\n isProxy: options.isMiddlewareProxy,\n module: options.middlewareModule,\n request,\n trailingSlash: options.trailingSlash,\n });\n if (middlewareResult.kind === \"response\") {\n return applyConfigHeadersToMiddlewareRedirect(middlewareResult.response, {\n basePathState,\n configHeaders: options.configHeaders,\n pathname: matchPathname(cleanPathname),\n requestContext: preMiddlewareRequestContext,\n });\n }\n\n cleanPathname = middlewareResult.cleanPathname;\n if (middlewareResult.search !== null) {\n url.search = middlewareResult.search;\n }\n }\n\n const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareContext.headers);\n const postMiddlewareRequestContext = buildPostMwRequestContext(request);\n\n // Rewrites (beforeFiles, afterFiles, fallback) use `matchPathname` from\n // above to splice in the default locale before matching. Route matching\n // itself continues to use the un-prefixed `cleanPathname` because App\n // Router files live under `app/...` with no locale segment. See issue\n // #1336 item 4 / pages-i18n.normalizeDefaultLocalePathname.\n const beforeFilesRewrite = await applyRewrite(\n {\n basePathState,\n clearRequestContext: options.clearRequestContext,\n request,\n requestContext: postMiddlewareRequestContext,\n rewrites: options.configRewrites.beforeFiles,\n },\n matchPathname(cleanPathname),\n );\n if (beforeFilesRewrite instanceof Response) return beforeFilesRewrite;\n if (beforeFilesRewrite) cleanPathname = beforeFilesRewrite;\n\n if (isImageOptimizationPath(cleanPathname)) {\n const imageUrlResult = validateImageUrl(url.searchParams.get(\"url\"), request.url);\n if (imageUrlResult instanceof Response) return imageUrlResult;\n return Response.redirect(new URL(imageUrlResult, url.origin).href, 302);\n }\n\n const metadataRouteResponse = await handleMetadataRouteRequest({\n metadataRoutes: options.metadataRoutes,\n cleanPathname,\n makeThenableParams: options.makeThenableParams,\n });\n if (metadataRouteResponse) {\n return applyMiddlewareContextToResponse(metadataRouteResponse, middlewareContext);\n }\n\n const publicFileResponse = resolvePublicFileRoute({\n cleanPathname,\n middlewareContext,\n pathname,\n publicFiles: options.publicFiles,\n request,\n });\n if (publicFileResponse) {\n options.clearRequestContext();\n return publicFileResponse;\n }\n\n if (isRscRequest) {\n stripRscCacheBustingSearchParam(url);\n }\n\n options.setNavigationContext({\n pathname: canonicalPathname,\n searchParams: url.searchParams,\n params: {},\n });\n\n // Eagerly seed `setRootParams` from the current cleanPathname before any\n // action dispatch so that user code which reads `unstable_rootParams()`\n // inside route handlers, `\"use cache\"` functions, and the page rerender\n // that follows a successful server action observes the matched layout's\n // root params. Without this seeding the rootParams remain null until the\n // post-action match block below runs, which is too late for action\n // execution and route-handler dispatch (both happen earlier).\n //\n // The route is matched against the pre-rewrite cleanPathname here. If the\n // afterFiles / fallback rewrites further down land on a different route,\n // the second `setRootParams` call below replaces this value before the\n // page renders, so there is no stale-value risk for ordinary page renders.\n // For action requests we intentionally do not re-run rewrites — actions\n // are always processed against the cleanPathname they were posted to.\n const preActionMatch = options.matchRoute(cleanPathname);\n if (preActionMatch) {\n setRootParams(pickRootParams(preActionMatch.params, preActionMatch.route.rootParamNames));\n }\n\n const actionId =\n request.headers.get(RSC_ACTION_HEADER) ?? request.headers.get(NEXT_ACTION_HEADER);\n const contentType = request.headers.get(\"content-type\") || \"\";\n\n const progressiveActionResult = await options.handleProgressiveActionRequest({\n actionId,\n cleanPathname,\n contentType,\n middlewareContext,\n request,\n });\n if (progressiveActionResult instanceof Response) return progressiveActionResult;\n const isProgressiveActionRender = progressiveActionResult?.kind === \"form-state\";\n const formState = isProgressiveActionRender ? progressiveActionResult.formState : null;\n const failedProgressiveActionResult =\n isProgressiveActionRender && \"actionFailed\" in progressiveActionResult\n ? progressiveActionResult\n : null;\n const actionFailed = failedProgressiveActionResult !== null;\n const actionError = failedProgressiveActionResult?.actionError;\n\n const serverActionResponse = await options.handleServerActionRequest({\n actionId,\n cleanPathname,\n contentType,\n interceptionContext: interceptionContextHeader,\n isRscRequest,\n middlewareContext,\n mountedSlotsHeader,\n request,\n searchParams: url.searchParams,\n });\n if (serverActionResponse) return serverActionResponse;\n\n let match = preActionMatch;\n if (!match || match.route.isDynamic) {\n const afterFilesRewrite = await applyRewrite(\n {\n basePathState,\n clearRequestContext: options.clearRequestContext,\n request,\n requestContext: postMiddlewareRequestContext,\n rewrites: options.configRewrites.afterFiles,\n },\n matchPathname(cleanPathname),\n );\n if (afterFilesRewrite instanceof Response) return afterFilesRewrite;\n if (afterFilesRewrite) {\n cleanPathname = afterFilesRewrite;\n match = options.matchRoute(cleanPathname);\n }\n }\n\n if (!match) {\n const fallbackRewrite = await applyRewrite(\n {\n basePathState,\n clearRequestContext: options.clearRequestContext,\n request,\n requestContext: postMiddlewareRequestContext,\n rewrites: options.configRewrites.fallback,\n },\n matchPathname(cleanPathname),\n );\n if (fallbackRewrite instanceof Response) return fallbackRewrite;\n if (fallbackRewrite) {\n cleanPathname = fallbackRewrite;\n match = options.matchRoute(cleanPathname);\n }\n }\n\n if (!match) {\n // Dev-only favicon short-circuit: browsers auto-request /favicon.ico on\n // every page load. Don't compile/render the not-found page for it.\n // Check `canonicalPathname` (the original browser-requested URL) so a\n // middleware rewrite that lands on `/favicon.ico` still falls through to\n // the normal not-found render.\n // Matches Next.js: packages/next/src/server/lib/router-server.ts —\n // condition `parsedUrl.pathname === '/favicon.ico'`.\n if (process.env.NODE_ENV !== \"production\" && canonicalPathname === \"/favicon.ico\") {\n options.clearRequestContext();\n return new Response(\"\", { status: 404 });\n }\n\n const pagesFallbackResponse = await options.renderPagesFallback?.({\n isRscRequest,\n middlewareContext,\n request,\n url,\n });\n if (pagesFallbackResponse) {\n options.clearRequestContext();\n return pagesFallbackResponse;\n }\n\n const renderedNotFoundResponse = await options.renderNotFound({\n isRscRequest,\n middlewareContext,\n request,\n route: null,\n scriptNonce,\n });\n if (renderedNotFoundResponse) return renderedNotFoundResponse;\n\n options.clearRequestContext();\n const headers = new Headers();\n mergeMiddlewareResponseHeaders(headers, middlewareContext.headers);\n return notFoundResponse({ headers });\n }\n\n const { route, params } = match;\n const prerenderRouteParamsPayload = readTrustedPrerenderRouteParams(request);\n const prerenderRouteParams = prerenderRouteParamsPayloadMatchesRoute(\n prerenderRouteParamsPayload,\n route.pattern,\n params,\n )\n ? prerenderRouteParamsPayload.params\n : null;\n const renderParams = prerenderRouteParams ?? params;\n options.setNavigationContext({\n pathname: canonicalPathname,\n searchParams: url.searchParams,\n params: renderParams,\n });\n const rootParams = pickRootParams(renderParams, route.rootParamNames);\n setRootParams(rootParams);\n\n if (route.routeHandler) {\n setCurrentFetchSoftTags(\n buildPageCacheTags(cleanPathname, [], [...route.routeSegments], \"route\"),\n );\n return options.dispatchMatchedRouteHandler({\n cleanPathname,\n middlewareContext,\n // Non-dynamic routes report params as `null` to match Next.js. Internal\n // bookkeeping above (navigation context, root params) keeps the matched\n // object (always `{}` for non-dynamic) so `useParams()` etc. still see\n // an object shape; only the user-facing handler context surfaces null.\n params: route.isDynamic ? renderParams : null,\n request,\n route,\n searchParams: url.searchParams,\n });\n }\n\n const pageResponse = await options.dispatchMatchedPage({\n cleanPathname,\n formState,\n actionError,\n actionFailed,\n handlerStart,\n interceptionContext: interceptionContextHeader,\n isProgressiveActionRender,\n isRscRequest,\n middlewareContext,\n mountedSlotsHeader,\n params: renderParams,\n staticParamsValidationParams: prerenderRouteParams === null ? undefined : params,\n rootParams,\n request,\n route,\n scriptNonce,\n searchParams: url.searchParams,\n renderMode,\n });\n\n // No-JS progressive form actions write cookies via cookies().set() / draftMode()\n // *during action execution*, before the page rerender begins. Those writes only\n // exist on the request-scoped headers state; the page-render path never flushes\n // them. We attach them here so the rendered Response carries the action's\n // Set-Cookie headers and revalidation marker, mirroring Next.js'\n // res.setHeader('set-cookie', ...) flush in action-handler.ts / app-render.tsx.\n // Issue: https://github.com/cloudflare/vinext/issues/1483\n if (isProgressiveActionRender) {\n return applyProgressiveActionSideEffects(pageResponse, progressiveActionResult);\n }\n return pageResponse;\n}\n\n/**\n * Append `Set-Cookie` headers and the `x-action-revalidated` marker captured\n * during progressive (no-JS) server action execution to the page render\n * response. See issue #1483.\n *\n * Falls back to rebuilding the response when the headers object is immutable\n * (e.g. `Response.redirect()`), so cookies set by the action ride out on a\n * redirect issued during the rerender too.\n */\nfunction applyProgressiveActionSideEffects(\n response: Response,\n sideEffects: ProgressiveActionFormStateResult,\n): Response {\n const hasPendingCookies = sideEffects.pendingCookies.length > 0;\n const hasDraftCookie = Boolean(sideEffects.draftCookie);\n const hasRevalidationKind = sideEffects.revalidationKind !== 0;\n if (!hasPendingCookies && !hasDraftCookie && !hasRevalidationKind) {\n return response;\n }\n\n const applyTo = (headers: Headers): void => {\n for (const cookie of sideEffects.pendingCookies) {\n headers.append(\"Set-Cookie\", cookie);\n }\n if (sideEffects.draftCookie) {\n headers.append(\"Set-Cookie\", sideEffects.draftCookie);\n }\n if (hasRevalidationKind) {\n headers.set(ACTION_REVALIDATED_HEADER, JSON.stringify(sideEffects.revalidationKind));\n }\n };\n\n try {\n applyTo(response.headers);\n return response;\n } catch {\n // Headers were immutable (Response.redirect()/Response.error()) — rebuild\n // with a fresh mutable Headers seeded from the original response.\n const headers = new Headers(response.headers);\n applyTo(headers);\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n }\n}\n\nexport function createAppRscHandler<TRoute extends AppRscHandlerRoute>(\n options: CreateAppRscHandlerOptions<TRoute>,\n): (request: Request, ctx: unknown) => Promise<Response> {\n return async function appRscHandler(rawRequest, ctx) {\n await options.ensureInstrumentation?.();\n\n // Strip forged internal headers at the App Router request boundary.\n // Must happen BEFORE headersContextFromRequest() and\n // requestContextFromRequest() so the captured context never contains\n // attacker-controlled internal headers. This is the correct boundary\n // for pure App Router requests; in hybrid app+pages mode the connect\n // handler already filtered headers upstream and x-vinext-mw-ctx\n // (not in INTERNAL_HEADERS) carries the forwarded middleware context.\n // srvx's NodeRequestHeaders reads from rawHeaders for iteration but falls\n // back to req.headers for .get() / .has(). In the dev server we add\n // x-vinext-mw-ctx to req.headers after the Request is built, so it is\n // visible to .get() but lost when filterInternalHeaders iterates. Read it\n // BEFORE iterating so applyForwardedMiddlewareContext can skip middleware.\n const mwCtx = rawRequest.headers.get(VINEXT_MW_CTX_HEADER);\n // Capture `x-nextjs-data` before filtering — the middleware redirect\n // protocol needs to know whether the inbound request was a `_next/data`\n // fetch to emit `x-nextjs-redirect` instead of an HTTP redirect.\n const isDataRequest = rawRequest.headers.get(\"x-nextjs-data\") === \"1\";\n // Read the trusted prerender route params before filtering strips the\n // route-params header (it IS in VINEXT_INTERNAL_HEADERS), then re-attach the\n // validated value below so the second read in handleAppRscRequest still sees\n // it. The secret was already verified upstream at prod-server's\n // nodeToWebRequest boundary; the surviving secret header (NOT in either\n // internal-header list) lets readTrustedPrerenderRouteParams's\n // VINEXT_PRERENDER gate pass on the reconstructed request. If the secret\n // header is ever added to VINEXT_INTERNAL_HEADERS, that second read breaks.\n const prerenderRouteParamsPayload = readTrustedPrerenderRouteParams(rawRequest);\n const filteredHeaders = filterInternalHeaders(rawRequest.headers);\n if (mwCtx !== null) {\n filteredHeaders.set(VINEXT_MW_CTX_HEADER, mwCtx);\n }\n const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(\n prerenderRouteParamsPayload,\n );\n if (prerenderRouteParamsHeader !== null) {\n filteredHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);\n }\n const request = cloneRequestWithHeaders(rawRequest, filteredHeaders);\n\n const executionContext = isExecutionContextLike(ctx)\n ? ctx\n : (getRequestExecutionContext() ?? null);\n const headersContext = headersContextFromRequest(request, {\n draftModeSecret: options.draftModeSecret,\n });\n const requestContext = createRequestContext({\n headersContext,\n executionContext,\n unstableCacheRevalidation: \"background\",\n });\n\n return runWithRequestContext(requestContext, () =>\n runWithPrerenderWorkUnit(\n async () => {\n ensureFetchPatch();\n const preMiddlewareRequestContext = requestContextFromRequest(request);\n let response: Response;\n\n try {\n response = await handleAppRscRequest(\n options,\n request,\n preMiddlewareRequestContext,\n isDataRequest,\n );\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n flattenErrorCauses(error);\n }\n throw error;\n }\n\n return finalizeAppRscResponse(response, request, {\n basePath: options.basePath,\n configHeaders: options.configHeaders,\n i18nConfig: options.i18nConfig,\n requestContext: preMiddlewareRequestContext,\n });\n },\n { route: () => new URL(request.url).pathname },\n ),\n );\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FA,SAAS,iCACP,UACA,mBACU;CACV,IAAI,CAAC,kBAAkB,WAAW,kBAAkB,UAAU,MAC5D,OAAO;CAGT,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;CAC7C,+BAA+B,SAAS,kBAAkB,QAAQ;CAElE,OAAO,IAAI,SAAS,SAAS,MAAM;EACjC,QAAQ,kBAAkB,UAAU,SAAS;EAC7C,YAAY,SAAS;EACrB;EACD,CAAC;;AAiJJ,SAAS,YACP,OACA,KACyC;CACzC,OAAO,OAAO;;AAGhB,SAAS,uBAAuB,OAA+C;CAC7E,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,OAAO,YAAY,OAAO,YAAY,IAAI,OAAO,MAAM,cAAc;;AAOvE,SAAS,gCAAgC,aAAqB,UAA0B;CACtF,IAAI,CAAC,YAAY,cAAc,YAAY,IAAI,YAAY,aAAa,SAAS,EAC/E,OAAO;CAET,OAAO,WAAW;;AAGpB,eAAe,aACb,SAOA,eACmC;CACnC,IAAI,CAAC,QAAQ,SAAS,QAAQ,OAAO;CAErC,MAAM,YAAY,aAChB,eACA,QAAQ,UACR,QAAQ,gBACR,QAAQ,cACT;CACD,IAAI,CAAC,WAAW,OAAO;CAEvB,IAAI,cAAc,UAAU,EAAE;EAC5B,QAAQ,qBAAqB;EAC7B,OAAO,qBAAqB,QAAQ,SAAS,UAAU;;CAGzD,OAAO;;AAGT,SAAS,uCACP,UACA,SAMU;CAIV,IAAI,SAAS,SAAS,OAAO,SAAS,UAAU,KAAK,OAAO;CAC5D,IAAI,CAAC,QAAQ,cAAc,QAAQ,OAAO;CAE1C,MAAM,UAAU,IAAI,SAAS;CAC7B,6BAA6B,SAAS;EACpC,eAAe,QAAQ;EACvB,UAAU,QAAQ;EAClB,gBAAgB,QAAQ;EACxB,eAAe,QAAQ;EACxB,CAAC;CAEF,IAAI,CAAC,QAAQ,SAAS,CAAC,MAAM,CAAC,MAAM;EAClC,+BAA+B,SAAS,SAAS,QAAQ;EACzD,OAAO,IAAI,SAAS,SAAS,MAAM;GACjC,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB;GACD,CAAC;;CAGJ,OAAO;;AAGT,eAAe,oBACb,SACA,SACA,6BACA,eACmB;CACnB,MAAM,eAAe,QAAQ,IAAI,aAAa,eAAe,YAAY,KAAK,GAAG;CAEjF,IAAI,QAAQ,IAAI,aAAa,cAAc;EACzC,MAAM,cAAc,QAAQ,2BAA2B,QAAQ;EAC/D,IAAI,aAAa,OAAO;;CAG1B,MAAM,aAAa,oBAAoB,SAAS,QAAQ,SAAS;CACjE,IAAI,sBAAsB,UAAU,OAAO;CAE3C,MAAM,EAAE,KAAK,cAAc,2BAA2B,oBAAoB,eACxE;CACF,IAAI,EAAE,UAAU,kBAAkB;CAOlC,MAAM,oBAAoB;CAQ1B,MAAM,gBAAgB;EAAE,UAAU,QAAQ;EAAU,aAAa;EAAM;CAEvE,MAAM,4BAA4B,MAAM,2BAA2B,SAAS;EAC1E,qBAAqB;GACnB,OAAO,QAAQ,IAAI,qBAAqB;;EAE1C,iBAAiB,QAAQ;EACzB;EACA,yBAAyB,QAAQ;EACjC,iBAAiB,QAAQ;EAC1B,CAAC;CACF,IAAI,2BAA2B,OAAO;CAEtC,MAAM,wBAAwB,uBAC5B,UACA,QAAQ,UACR,QAAQ,eACR,IAAI,OACL;CACD,IAAI,uBAAuB,OAAO;CAWlC,MAAM,iBAAiB,MACrB,+BAA+B,GAAG,QAAQ,YAAY,EAAE,UAAU,IAAI,UAAU,CAAC;CAGnF,MAAM,WAAW,cADQ,cAAc,eAAe,SAAS,CAE7C,EAChB,QAAQ,iBACR,6BACA,cACD;CACD,IAAI,UAAU;EACZ,MAAM,cAAc,oBAClB,gCAAgC,SAAS,aAAa,QAAQ,SAAS,CACxE;EACD,MAAM,WACJ,gBAAgB,QAAQ,QAAQ,IAAA,MAAe,KAAK,MAChD,MAAM,0BAA0B,aAAa,QAAQ,GACrD;EACN,OAAO,IAAI,SAAS,MAAM;GACxB,QAAQ,SAAS,YAAY,MAAM;GACnC,SAAS,EAAE,UAAU,UAAU;GAChC,CAAC;;CAGJ,MAAM,0BAA0B,MAAM,qCAAqC;EACzE;EACA;EACD,CAAC;CACF,IAAI,yBAAyB,OAAO;CAEpC,MAAM,oBAA6C;EACjD,SAAS;EACT,gBAAgB;EAChB,QAAQ;EACT;CAED,IAAI,QAAQ,kBAAkB;EAC5B,MAAM,mBAAmB,MAAM,mBAAmB;GAChD,UAAU,QAAQ;GAClB;GACA,SAAS;GACT,YAAY,QAAQ;GACpB;GACA,SAAS,QAAQ;GACjB,QAAQ,QAAQ;GAChB;GACA,eAAe,QAAQ;GACxB,CAAC;EACF,IAAI,iBAAiB,SAAS,YAC5B,OAAO,uCAAuC,iBAAiB,UAAU;GACvE;GACA,eAAe,QAAQ;GACvB,UAAU,cAAc,cAAc;GACtC,gBAAgB;GACjB,CAAC;EAGJ,gBAAgB,iBAAiB;EACjC,IAAI,iBAAiB,WAAW,MAC9B,IAAI,SAAS,iBAAiB;;CAIlC,MAAM,cAAc,gCAAgC,QAAQ,SAAS,kBAAkB,QAAQ;CAC/F,MAAM,+BAA+B,0BAA0B,QAAQ;CAOvE,MAAM,qBAAqB,MAAM,aAC/B;EACE;EACA,qBAAqB,QAAQ;EAC7B;EACA,gBAAgB;EAChB,UAAU,QAAQ,eAAe;EAClC,EACD,cAAc,cAAc,CAC7B;CACD,IAAI,8BAA8B,UAAU,OAAO;CACnD,IAAI,oBAAoB,gBAAgB;CAExC,IAAI,wBAAwB,cAAc,EAAE;EAC1C,MAAM,iBAAiB,iBAAiB,IAAI,aAAa,IAAI,MAAM,EAAE,QAAQ,IAAI;EACjF,IAAI,0BAA0B,UAAU,OAAO;EAC/C,OAAO,SAAS,SAAS,IAAI,IAAI,gBAAgB,IAAI,OAAO,CAAC,MAAM,IAAI;;CAGzE,MAAM,wBAAwB,MAAM,2BAA2B;EAC7D,gBAAgB,QAAQ;EACxB;EACA,oBAAoB,QAAQ;EAC7B,CAAC;CACF,IAAI,uBACF,OAAO,iCAAiC,uBAAuB,kBAAkB;CAGnF,MAAM,qBAAqB,uBAAuB;EAChD;EACA;EACA;EACA,aAAa,QAAQ;EACrB;EACD,CAAC;CACF,IAAI,oBAAoB;EACtB,QAAQ,qBAAqB;EAC7B,OAAO;;CAGT,IAAI,cACF,gCAAgC,IAAI;CAGtC,QAAQ,qBAAqB;EAC3B,UAAU;EACV,cAAc,IAAI;EAClB,QAAQ,EAAE;EACX,CAAC;CAgBF,MAAM,iBAAiB,QAAQ,WAAW,cAAc;CACxD,IAAI,gBACF,cAAc,eAAe,eAAe,QAAQ,eAAe,MAAM,eAAe,CAAC;CAG3F,MAAM,WACJ,QAAQ,QAAQ,IAAA,eAAsB,IAAI,QAAQ,QAAQ,IAAA,cAAuB;CACnF,MAAM,cAAc,QAAQ,QAAQ,IAAI,eAAe,IAAI;CAE3D,MAAM,0BAA0B,MAAM,QAAQ,+BAA+B;EAC3E;EACA;EACA;EACA;EACA;EACD,CAAC;CACF,IAAI,mCAAmC,UAAU,OAAO;CACxD,MAAM,4BAA4B,yBAAyB,SAAS;CACpE,MAAM,YAAY,4BAA4B,wBAAwB,YAAY;CAClF,MAAM,gCACJ,6BAA6B,kBAAkB,0BAC3C,0BACA;CACN,MAAM,eAAe,kCAAkC;CACvD,MAAM,cAAc,+BAA+B;CAEnD,MAAM,uBAAuB,MAAM,QAAQ,0BAA0B;EACnE;EACA;EACA;EACA,qBAAqB;EACrB;EACA;EACA;EACA;EACA,cAAc,IAAI;EACnB,CAAC;CACF,IAAI,sBAAsB,OAAO;CAEjC,IAAI,QAAQ;CACZ,IAAI,CAAC,SAAS,MAAM,MAAM,WAAW;EACnC,MAAM,oBAAoB,MAAM,aAC9B;GACE;GACA,qBAAqB,QAAQ;GAC7B;GACA,gBAAgB;GAChB,UAAU,QAAQ,eAAe;GAClC,EACD,cAAc,cAAc,CAC7B;EACD,IAAI,6BAA6B,UAAU,OAAO;EAClD,IAAI,mBAAmB;GACrB,gBAAgB;GAChB,QAAQ,QAAQ,WAAW,cAAc;;;CAI7C,IAAI,CAAC,OAAO;EACV,MAAM,kBAAkB,MAAM,aAC5B;GACE;GACA,qBAAqB,QAAQ;GAC7B;GACA,gBAAgB;GAChB,UAAU,QAAQ,eAAe;GAClC,EACD,cAAc,cAAc,CAC7B;EACD,IAAI,2BAA2B,UAAU,OAAO;EAChD,IAAI,iBAAiB;GACnB,gBAAgB;GAChB,QAAQ,QAAQ,WAAW,cAAc;;;CAI7C,IAAI,CAAC,OAAO;EAQV,IAAI,QAAQ,IAAI,aAAa,gBAAgB,sBAAsB,gBAAgB;GACjF,QAAQ,qBAAqB;GAC7B,OAAO,IAAI,SAAS,IAAI,EAAE,QAAQ,KAAK,CAAC;;EAG1C,MAAM,wBAAwB,MAAM,QAAQ,sBAAsB;GAChE;GACA;GACA;GACA;GACD,CAAC;EACF,IAAI,uBAAuB;GACzB,QAAQ,qBAAqB;GAC7B,OAAO;;EAGT,MAAM,2BAA2B,MAAM,QAAQ,eAAe;GAC5D;GACA;GACA;GACA,OAAO;GACP;GACD,CAAC;EACF,IAAI,0BAA0B,OAAO;EAErC,QAAQ,qBAAqB;EAC7B,MAAM,UAAU,IAAI,SAAS;EAC7B,+BAA+B,SAAS,kBAAkB,QAAQ;EAClE,OAAO,iBAAiB,EAAE,SAAS,CAAC;;CAGtC,MAAM,EAAE,OAAO,WAAW;CAC1B,MAAM,8BAA8B,gCAAgC,QAAQ;CAC5E,MAAM,uBAAuB,wCAC3B,6BACA,MAAM,SACN,OACD,GACG,4BAA4B,SAC5B;CACJ,MAAM,eAAe,wBAAwB;CAC7C,QAAQ,qBAAqB;EAC3B,UAAU;EACV,cAAc,IAAI;EAClB,QAAQ;EACT,CAAC;CACF,MAAM,aAAa,eAAe,cAAc,MAAM,eAAe;CACrE,cAAc,WAAW;CAEzB,IAAI,MAAM,cAAc;EACtB,wBACE,mBAAmB,eAAe,EAAE,EAAE,CAAC,GAAG,MAAM,cAAc,EAAE,QAAQ,CACzE;EACD,OAAO,QAAQ,4BAA4B;GACzC;GACA;GAKA,QAAQ,MAAM,YAAY,eAAe;GACzC;GACA;GACA,cAAc,IAAI;GACnB,CAAC;;CAGJ,MAAM,eAAe,MAAM,QAAQ,oBAAoB;EACrD;EACA;EACA;EACA;EACA;EACA,qBAAqB;EACrB;EACA;EACA;EACA;EACA,QAAQ;EACR,8BAA8B,yBAAyB,OAAO,KAAA,IAAY;EAC1E;EACA;EACA;EACA;EACA,cAAc,IAAI;EAClB;EACD,CAAC;CASF,IAAI,2BACF,OAAO,kCAAkC,cAAc,wBAAwB;CAEjF,OAAO;;;;;;;;;;;AAYT,SAAS,kCACP,UACA,aACU;CACV,MAAM,oBAAoB,YAAY,eAAe,SAAS;CAC9D,MAAM,iBAAiB,QAAQ,YAAY,YAAY;CACvD,MAAM,sBAAsB,YAAY,qBAAqB;CAC7D,IAAI,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,qBAC5C,OAAO;CAGT,MAAM,WAAW,YAA2B;EAC1C,KAAK,MAAM,UAAU,YAAY,gBAC/B,QAAQ,OAAO,cAAc,OAAO;EAEtC,IAAI,YAAY,aACd,QAAQ,OAAO,cAAc,YAAY,YAAY;EAEvD,IAAI,qBACF,QAAQ,IAAI,2BAA2B,KAAK,UAAU,YAAY,iBAAiB,CAAC;;CAIxF,IAAI;EACF,QAAQ,SAAS,QAAQ;EACzB,OAAO;SACD;EAGN,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ;EAC7C,QAAQ,QAAQ;EAChB,OAAO,IAAI,SAAS,SAAS,MAAM;GACjC,QAAQ,SAAS;GACjB,YAAY,SAAS;GACrB;GACD,CAAC;;;AAIN,SAAgB,oBACd,SACuD;CACvD,OAAO,eAAe,cAAc,YAAY,KAAK;EACnD,MAAM,QAAQ,yBAAyB;EAcvC,MAAM,QAAQ,WAAW,QAAQ,IAAI,qBAAqB;EAI1D,MAAM,gBAAgB,WAAW,QAAQ,IAAI,gBAAgB,KAAK;EASlE,MAAM,8BAA8B,gCAAgC,WAAW;EAC/E,MAAM,kBAAkB,sBAAsB,WAAW,QAAQ;EACjE,IAAI,UAAU,MACZ,gBAAgB,IAAI,sBAAsB,MAAM;EAElD,MAAM,6BAA6B,oCACjC,4BACD;EACD,IAAI,+BAA+B,MACjC,gBAAgB,IAAI,sCAAsC,2BAA2B;EAEvF,MAAM,UAAU,wBAAwB,YAAY,gBAAgB;EAEpE,MAAM,mBAAmB,uBAAuB,IAAI,GAChD,MACC,4BAA4B,IAAI;EAUrC,OAAO,sBANgB,qBAAqB;GAC1C,gBAJqB,0BAA0B,SAAS,EACxD,iBAAiB,QAAQ,iBAC1B,CAEe;GACd;GACA,2BAA2B;GAC5B,CAE0C,QACzC,yBACE,YAAY;GACV,kBAAkB;GAClB,MAAM,8BAA8B,0BAA0B,QAAQ;GACtE,IAAI;GAEJ,IAAI;IACF,WAAW,MAAM,oBACf,SACA,SACA,6BACA,cACD;YACM,OAAO;IACd,IAAI,QAAQ,IAAI,aAAa,cAC3B,mBAAmB,MAAM;IAE3B,MAAM;;GAGR,OAAO,uBAAuB,UAAU,SAAS;IAC/C,UAAU,QAAQ;IAClB,eAAe,QAAQ;IACvB,YAAY,QAAQ;IACpB,gBAAgB;IACjB,CAAC;KAEJ,EAAE,aAAa,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,CAC/C,CACF"}
|
|
@@ -30,7 +30,8 @@ type NormalizedRscRequest = {
|
|
|
30
30
|
* 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
|
|
31
31
|
* 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
|
|
32
32
|
* `/__vinext/` bypasses this for internal prerender endpoints.
|
|
33
|
-
* 6. RSC detection: `.rsc` suffix
|
|
33
|
+
* 6. RSC detection: `.rsc` suffix, or Next-style `RSC: 1` plus the internal
|
|
34
|
+
* `_rsc` cache-busting query. The header alone does not select payload
|
|
34
35
|
* rendering at the canonical HTML URL, so caches that ignore Vary cannot
|
|
35
36
|
* store Flight responses under HTML URLs.
|
|
36
37
|
* 7. cleanPathname — pathname with `.rsc` suffix stripped
|
|
@@ -27,7 +27,8 @@ import { parseClientReuseManifestHeader } from "./client-reuse-manifest.js";
|
|
|
27
27
|
* 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)
|
|
28
28
|
* 5. basePath check + strip — 404 when pathname lacks the basePath prefix.
|
|
29
29
|
* `/__vinext/` bypasses this for internal prerender endpoints.
|
|
30
|
-
* 6. RSC detection: `.rsc` suffix
|
|
30
|
+
* 6. RSC detection: `.rsc` suffix, or Next-style `RSC: 1` plus the internal
|
|
31
|
+
* `_rsc` cache-busting query. The header alone does not select payload
|
|
31
32
|
* rendering at the canonical HTML URL, so caches that ignore Vary cannot
|
|
32
33
|
* store Flight responses under HTML URLs.
|
|
33
34
|
* 7. cleanPathname — pathname with `.rsc` suffix stripped
|
|
@@ -54,7 +55,7 @@ function normalizeRscRequest(request, basePath) {
|
|
|
54
55
|
if (!hasBasePath(pathname, basePath) && !pathname.startsWith("/__vinext/")) return notFoundResponse();
|
|
55
56
|
pathname = stripBasePath(pathname, basePath);
|
|
56
57
|
}
|
|
57
|
-
const isRscRequest = pathname.endsWith(".rsc");
|
|
58
|
+
const isRscRequest = pathname.endsWith(".rsc") || request.headers.get("RSC") === "1" && url.searchParams.has("_rsc");
|
|
58
59
|
const cleanPathname = stripRscSuffix(pathname);
|
|
59
60
|
const interceptionContextHeader = normalizeInterceptionContextHeader(request.headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER));
|
|
60
61
|
const mountedSlotsHeader = normalizeMountedSlotsHeader(request.headers.get(VINEXT_MOUNTED_SLOTS_HEADER));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-rsc-request-normalization.js","names":[],"sources":["../../src/server/app-rsc-request-normalization.ts"],"sourcesContent":["import { normalizePath } from \"./normalize-path.js\";\nimport { normalizePathnameForRouteMatchStrict } from \"../routing/utils.js\";\nimport { guardProtocolRelativeUrl } from \"./request-pipeline.js\";\nimport { hasBasePath, stripBasePath } from \"../utils/base-path.js\";\nimport {\n VINEXT_CLIENT_REUSE_MANIFEST_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n VINEXT_RSC_RENDER_MODE_HEADER,\n} from \"./headers.js\";\nimport {\n parseClientReuseManifestHeader,\n type ClientReuseManifestParseResult,\n} from \"./client-reuse-manifest.js\";\nimport { normalizeInterceptionContextHeader } from \"./app-interception-context-header.js\";\nimport { normalizeMountedSlotsHeader } from \"./app-mounted-slots-header.js\";\nimport { stripRscSuffix } from \"./app-rsc-cache-busting.js\";\nimport {\n APP_RSC_RENDER_MODE_NAVIGATION,\n parseAppRscRenderMode,\n type AppRscRenderMode,\n} from \"./app-rsc-render-mode.js\";\nimport { badRequestResponse, notFoundResponse } from \"./http-error-responses.js\";\n\nexport { normalizeMountedSlotsHeader } from \"./app-mounted-slots-header.js\";\n\nexport type NormalizedRscRequest = {\n /** Parsed URL. Callers may mutate `url.search` after middleware runs. */\n url: URL;\n /** Normalized pathname with basePath stripped. Used for all internal routing. */\n pathname: string;\n /** Pathname with `.rsc` suffix removed. Used for route matching and navigation context. */\n cleanPathname: string;\n /** True when the request targets a canonical `.rsc` payload URL. */\n isRscRequest: boolean;\n /** Sanitized X-Vinext-Interception-Context header (null bytes stripped). null when absent. */\n interceptionContextHeader: string | null;\n /** Normalized x-vinext-mounted-slots header (deduplicated, sorted). null when absent or blank. */\n mountedSlotsHeader: string | null;\n /** Semantic RSC payload mode. HTML requests always normalize to \"navigation\". */\n renderMode: AppRscRenderMode;\n /** Disabled ClientReuseManifest hint. Never authorizes skip transport in this stage. */\n clientReuseManifest: ClientReuseManifestParseResult;\n};\n\n/**\n * Normalize an App Router RSC request.\n *\n * Performs all security-sensitive and compatibility-sensitive preprocessing before\n * route matching. The ordering of steps is security-critical — changing it introduces\n * vulnerabilities:\n *\n * 1. Parse URL\n * 2. Protocol-relative URL guard — on the raw pathname, BEFORE normalizePath collapses\n * `//` to `/`. If the guard ran after normalization, `//evil.com` → `/evil.com`\n * would bypass the check and reach the trailing-slash redirector, which echoes the\n * path into a `Location` header that browsers interpret as protocol-relative.\n * 3. Strict percent-decode each segment — throws on malformed sequences (→ 400). Must\n * run before basePath check so %2F-encoded slashes cannot create fake basePath prefixes.\n * 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)\n * 5. basePath check + strip — 404 when pathname lacks the basePath prefix.\n * `/__vinext/` bypasses this for internal prerender endpoints.\n * 6. RSC detection: `.rsc` suffix
|
|
1
|
+
{"version":3,"file":"app-rsc-request-normalization.js","names":[],"sources":["../../src/server/app-rsc-request-normalization.ts"],"sourcesContent":["import { normalizePath } from \"./normalize-path.js\";\nimport { normalizePathnameForRouteMatchStrict } from \"../routing/utils.js\";\nimport { guardProtocolRelativeUrl } from \"./request-pipeline.js\";\nimport { hasBasePath, stripBasePath } from \"../utils/base-path.js\";\nimport {\n RSC_HEADER,\n VINEXT_CLIENT_REUSE_MANIFEST_HEADER,\n VINEXT_INTERCEPTION_CONTEXT_HEADER,\n VINEXT_MOUNTED_SLOTS_HEADER,\n VINEXT_RSC_RENDER_MODE_HEADER,\n} from \"./headers.js\";\nimport {\n parseClientReuseManifestHeader,\n type ClientReuseManifestParseResult,\n} from \"./client-reuse-manifest.js\";\nimport { normalizeInterceptionContextHeader } from \"./app-interception-context-header.js\";\nimport { normalizeMountedSlotsHeader } from \"./app-mounted-slots-header.js\";\nimport { stripRscSuffix, VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM } from \"./app-rsc-cache-busting.js\";\nimport {\n APP_RSC_RENDER_MODE_NAVIGATION,\n parseAppRscRenderMode,\n type AppRscRenderMode,\n} from \"./app-rsc-render-mode.js\";\nimport { badRequestResponse, notFoundResponse } from \"./http-error-responses.js\";\n\nexport { normalizeMountedSlotsHeader } from \"./app-mounted-slots-header.js\";\n\nexport type NormalizedRscRequest = {\n /** Parsed URL. Callers may mutate `url.search` after middleware runs. */\n url: URL;\n /** Normalized pathname with basePath stripped. Used for all internal routing. */\n pathname: string;\n /** Pathname with `.rsc` suffix removed. Used for route matching and navigation context. */\n cleanPathname: string;\n /** True when the request targets a canonical `.rsc` payload URL. */\n isRscRequest: boolean;\n /** Sanitized X-Vinext-Interception-Context header (null bytes stripped). null when absent. */\n interceptionContextHeader: string | null;\n /** Normalized x-vinext-mounted-slots header (deduplicated, sorted). null when absent or blank. */\n mountedSlotsHeader: string | null;\n /** Semantic RSC payload mode. HTML requests always normalize to \"navigation\". */\n renderMode: AppRscRenderMode;\n /** Disabled ClientReuseManifest hint. Never authorizes skip transport in this stage. */\n clientReuseManifest: ClientReuseManifestParseResult;\n};\n\n/**\n * Normalize an App Router RSC request.\n *\n * Performs all security-sensitive and compatibility-sensitive preprocessing before\n * route matching. The ordering of steps is security-critical — changing it introduces\n * vulnerabilities:\n *\n * 1. Parse URL\n * 2. Protocol-relative URL guard — on the raw pathname, BEFORE normalizePath collapses\n * `//` to `/`. If the guard ran after normalization, `//evil.com` → `/evil.com`\n * would bypass the check and reach the trailing-slash redirector, which echoes the\n * path into a `Location` header that browsers interpret as protocol-relative.\n * 3. Strict percent-decode each segment — throws on malformed sequences (→ 400). Must\n * run before basePath check so %2F-encoded slashes cannot create fake basePath prefixes.\n * 4. Collapse double-slashes, resolve `.` and `..` segments (normalizePath)\n * 5. basePath check + strip — 404 when pathname lacks the basePath prefix.\n * `/__vinext/` bypasses this for internal prerender endpoints.\n * 6. RSC detection: `.rsc` suffix, or Next-style `RSC: 1` plus the internal\n * `_rsc` cache-busting query. The header alone does not select payload\n * rendering at the canonical HTML URL, so caches that ignore Vary cannot\n * store Flight responses under HTML URLs.\n * 7. cleanPathname — pathname with `.rsc` suffix stripped\n * 8. Sanitize X-Vinext-Interception-Context — strip null bytes (header injection)\n * 9. Normalize x-vinext-mounted-slots — dedup and sort for canonical cache keys\n * 10. Read semantic render mode for refresh/action payload rendering\n * 11. Parse disabled ClientReuseManifest hints on canonical RSC payload requests\n *\n * @returns A 400 or 404 Response for invalid or out-of-scope inputs,\n * or a NormalizedRscRequest for valid requests.\n */\nexport function normalizeRscRequest(\n request: Request,\n basePath: string,\n): Response | NormalizedRscRequest {\n const url = new URL(request.url);\n\n // Step 2: Guard against protocol-relative open redirects on the raw pathname.\n // normalizePath (step 4) would collapse //evil.com to /evil.com, causing the\n // guard to miss it. Raw pathname must be checked first.\n const protoGuard = guardProtocolRelativeUrl(url.pathname);\n if (protoGuard) return protoGuard;\n\n // Step 3: Strict segment-wise percent-decode. Preserves encoded path delimiters\n // (%2F stays %2F) to prevent encoded slashes from acting as path separators.\n // Throws on malformed sequences like %GG — caller must return 400.\n let decoded: string;\n try {\n decoded = normalizePathnameForRouteMatchStrict(url.pathname);\n } catch {\n return badRequestResponse();\n }\n\n // Step 4: Collapse double-slashes and resolve . / .. segments.\n let pathname = normalizePath(decoded);\n\n // Step 5: basePath check and strip.\n // Skipped when basePath is empty (no basePath configured).\n // /__vinext/ prefix bypasses the check for internal prerender endpoints\n // that must be reachable regardless of basePath configuration.\n if (basePath) {\n if (!hasBasePath(pathname, basePath) && !pathname.startsWith(\"/__vinext/\")) {\n return notFoundResponse();\n }\n pathname = stripBasePath(pathname, basePath);\n }\n\n // Steps 6-7: RSC detection and cleanPathname.\n const isRscRequest =\n pathname.endsWith(\".rsc\") ||\n (request.headers.get(RSC_HEADER) === \"1\" &&\n url.searchParams.has(VINEXT_RSC_CACHE_BUSTING_SEARCH_PARAM));\n const cleanPathname = stripRscSuffix(pathname);\n\n // Step 8: Validate and sanitize X-Vinext-Interception-Context.\n //\n // The legitimate value is always a same-origin URL pathname (`/feed`,\n // `/photos/42`, …) emitted by the vinext browser entry. We strip null bytes\n // (header-injection defense), bound length, and require a pathname-shaped\n // value so an attacker cannot fan out unbounded distinct values into the\n // RSC / optimistic-route cache keys. See SECURITY-AUDIT-2026-05.md F-PROD-1.\n const interceptionContextHeader = normalizeInterceptionContextHeader(\n request.headers.get(VINEXT_INTERCEPTION_CONTEXT_HEADER),\n );\n\n // Step 9: Normalize mounted-slots header for canonical cache keying.\n const mountedSlotsHeader = normalizeMountedSlotsHeader(\n request.headers.get(VINEXT_MOUNTED_SLOTS_HEADER),\n );\n const renderMode = isRscRequest\n ? parseAppRscRenderMode(request.headers.get(VINEXT_RSC_RENDER_MODE_HEADER))\n : APP_RSC_RENDER_MODE_NAVIGATION;\n const clientReuseManifest = isRscRequest\n ? parseClientReuseManifestHeader(request.headers.get(VINEXT_CLIENT_REUSE_MANIFEST_HEADER))\n : ({ kind: \"absent\" } satisfies ClientReuseManifestParseResult);\n\n return {\n clientReuseManifest,\n url,\n pathname,\n cleanPathname,\n isRscRequest,\n interceptionContextHeader,\n mountedSlotsHeader,\n renderMode,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EA,SAAgB,oBACd,SACA,UACiC;CACjC,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;CAKhC,MAAM,aAAa,yBAAyB,IAAI,SAAS;CACzD,IAAI,YAAY,OAAO;CAKvB,IAAI;CACJ,IAAI;EACF,UAAU,qCAAqC,IAAI,SAAS;SACtD;EACN,OAAO,oBAAoB;;CAI7B,IAAI,WAAW,cAAc,QAAQ;CAMrC,IAAI,UAAU;EACZ,IAAI,CAAC,YAAY,UAAU,SAAS,IAAI,CAAC,SAAS,WAAW,aAAa,EACxE,OAAO,kBAAkB;EAE3B,WAAW,cAAc,UAAU,SAAS;;CAI9C,MAAM,eACJ,SAAS,SAAS,OAAO,IACxB,QAAQ,QAAQ,IAAA,MAAe,KAAK,OACnC,IAAI,aAAa,IAAA,OAA0C;CAC/D,MAAM,gBAAgB,eAAe,SAAS;CAS9C,MAAM,4BAA4B,mCAChC,QAAQ,QAAQ,IAAI,mCAAmC,CACxD;CAGD,MAAM,qBAAqB,4BACzB,QAAQ,QAAQ,IAAI,4BAA4B,CACjD;CACD,MAAM,aAAa,eACf,sBAAsB,QAAQ,QAAQ,IAAI,8BAA8B,CAAC,GACzE;CAKJ,OAAO;EACL,qBAL0B,eACxB,+BAA+B,QAAQ,QAAQ,IAAI,oCAAoC,CAAC,GACvF,EAAE,MAAM,UAAU;EAIrB;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
|
|
@@ -14,7 +14,7 @@ type EffectiveAppPageSegmentConfig = {
|
|
|
14
14
|
dynamicParamsConfig?: boolean;
|
|
15
15
|
fetchCache?: FetchCacheMode;
|
|
16
16
|
revalidateSeconds: number | null;
|
|
17
|
-
runtime?:
|
|
17
|
+
runtime?: "edge" | "experimental-edge" | "nodejs";
|
|
18
18
|
};
|
|
19
19
|
type ResolveAppPageSegmentConfigOptions = {
|
|
20
20
|
layouts?: readonly (AppRouteSegmentConfigModule | null | undefined)[];
|
|
@@ -21,6 +21,9 @@ function isRouteSegmentDynamic(value) {
|
|
|
21
21
|
function isRouteSegmentFetchCache(value) {
|
|
22
22
|
return FETCH_CACHE_VALUES.has(value);
|
|
23
23
|
}
|
|
24
|
+
function isRouteSegmentRuntime(value) {
|
|
25
|
+
return value === "edge" || value === "experimental-edge" || value === "nodejs";
|
|
26
|
+
}
|
|
24
27
|
function resolveRevalidateSeconds(current, value) {
|
|
25
28
|
if (value === false) {
|
|
26
29
|
if (current === null) return Infinity;
|
|
@@ -55,6 +58,7 @@ function resolveAppPageSegmentConfig(options) {
|
|
|
55
58
|
for (const segment of segments) {
|
|
56
59
|
if (!segment) continue;
|
|
57
60
|
if (isRouteSegmentDynamic(segment.dynamic)) config.dynamicConfig = segment.dynamic;
|
|
61
|
+
if (isRouteSegmentRuntime(segment.runtime)) config.runtime = segment.runtime;
|
|
58
62
|
if (segment.dynamicParams === false) config.dynamicParamsConfig = false;
|
|
59
63
|
else if (segment.dynamicParams === true && config.dynamicParamsConfig !== false) config.dynamicParamsConfig = true;
|
|
60
64
|
if (isRouteSegmentFetchCache(segment.fetchCache)) {
|
|
@@ -73,7 +77,6 @@ function resolveAppPageSegmentConfig(options) {
|
|
|
73
77
|
else config.fetchCache = fetchCache;
|
|
74
78
|
}
|
|
75
79
|
config.revalidateSeconds = resolveRevalidateSeconds(config.revalidateSeconds, segment.revalidate);
|
|
76
|
-
if (typeof segment.runtime === "string") config.runtime = segment.runtime;
|
|
77
80
|
}
|
|
78
81
|
if (config.dynamicConfig === "force-dynamic") config.revalidateSeconds = 0;
|
|
79
82
|
if (config.fetchCache === void 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-segment-config.js","names":[],"sources":["../../src/server/app-segment-config.ts"],"sourcesContent":["import type { FetchCacheMode } from \"vinext/shims/fetch-cache\";\nimport { isEdgeApiRuntime } from \"./edge-api-runtime.js\";\n\ntype AppRouteSegmentDynamic = \"auto\" | \"error\" | \"force-dynamic\" | \"force-static\";\n\ntype AppRouteSegmentConfigModule = {\n dynamic?: unknown;\n dynamicParams?: unknown;\n fetchCache?: unknown;\n revalidate?: unknown;\n runtime?: unknown;\n};\n\ntype EffectiveAppPageSegmentConfig = {\n dynamicConfig?: AppRouteSegmentDynamic;\n dynamicParamsConfig?: boolean;\n fetchCache?: FetchCacheMode;\n revalidateSeconds: number | null;\n runtime?:
|
|
1
|
+
{"version":3,"file":"app-segment-config.js","names":[],"sources":["../../src/server/app-segment-config.ts"],"sourcesContent":["import type { FetchCacheMode } from \"vinext/shims/fetch-cache\";\nimport { isEdgeApiRuntime } from \"./edge-api-runtime.js\";\n\ntype AppRouteSegmentDynamic = \"auto\" | \"error\" | \"force-dynamic\" | \"force-static\";\n\ntype AppRouteSegmentConfigModule = {\n dynamic?: unknown;\n dynamicParams?: unknown;\n fetchCache?: unknown;\n revalidate?: unknown;\n runtime?: unknown;\n};\n\ntype EffectiveAppPageSegmentConfig = {\n dynamicConfig?: AppRouteSegmentDynamic;\n dynamicParamsConfig?: boolean;\n fetchCache?: FetchCacheMode;\n revalidateSeconds: number | null;\n runtime?: \"edge\" | \"experimental-edge\" | \"nodejs\";\n};\n\ntype ResolveAppPageSegmentConfigOptions = {\n layouts?: readonly (AppRouteSegmentConfigModule | null | undefined)[];\n page?: AppRouteSegmentConfigModule | null;\n};\n\nconst DYNAMIC_VALUES = new Set<unknown>([\"auto\", \"error\", \"force-dynamic\", \"force-static\"]);\nconst FETCH_CACHE_VALUES = new Set<unknown>([\n \"auto\",\n \"default-cache\",\n \"default-no-store\",\n \"force-cache\",\n \"force-no-store\",\n \"only-cache\",\n \"only-no-store\",\n]);\n\nfunction isRouteSegmentDynamic(value: unknown): value is AppRouteSegmentDynamic {\n return DYNAMIC_VALUES.has(value);\n}\n\nfunction isRouteSegmentFetchCache(value: unknown): value is FetchCacheMode {\n return FETCH_CACHE_VALUES.has(value);\n}\n\nfunction isRouteSegmentRuntime(value: unknown): value is EffectiveAppPageSegmentConfig[\"runtime\"] {\n return value === \"edge\" || value === \"experimental-edge\" || value === \"nodejs\";\n}\n\nfunction resolveRevalidateSeconds(current: number | null, value: unknown): number | null {\n // revalidate = false means \"cache indefinitely\" in Next.js segment config.\n // Represent it as Infinity so downstream code can distinguish \"never\n // revalidate\" (Infinity) from \"no config / unset\" (null).\n if (value === false) {\n if (current === null) return Infinity;\n // Shortest-wins: any finite interval is shorter than Infinity.\n return current === Infinity ? Infinity : current;\n }\n\n if (typeof value !== \"number\") {\n return current;\n }\n\n if (current === null) {\n return value;\n }\n\n return value < current ? value : current;\n}\n\nfunction isCacheFetchCacheMode(value: FetchCacheMode): boolean {\n return value === \"default-cache\" || value === \"force-cache\" || value === \"only-cache\";\n}\n\nfunction describeFetchCacheConflict(value: FetchCacheMode): string {\n return `Route segment config has incompatible fetchCache values including \"${value}\".`;\n}\n\n/**\n * Resolve the route segment config that applies to an App page route.\n *\n * Next.js collects config from every segment in the loader tree and reduces it\n * into the effective route config. The generated vinext entry already knows\n * the concrete layout/page modules for a route, so it should only describe\n * those modules and delegate the behavior to this helper.\n */\nexport function resolveAppPageSegmentConfig(\n options: ResolveAppPageSegmentConfigOptions,\n): EffectiveAppPageSegmentConfig {\n const segments = [...(options.layouts ?? []), options.page];\n // Reduction strategies differ by field:\n // - dynamic: child segments override parents.\n // - dynamicParams: false is sticky across the route tree.\n // - fetchCache: force/only modes take route-level precedence and reject conflicts.\n // - revalidate: the shortest numeric interval wins.\n const config: EffectiveAppPageSegmentConfig = {\n revalidateSeconds: null,\n };\n let hasForceCache = false;\n let hasForceNoStore = false;\n let hasOnlyCache = false;\n let hasOnlyNoStore = false;\n let hasParentDefaultNoStore = false;\n\n for (const segment of segments) {\n if (!segment) continue;\n\n if (isRouteSegmentDynamic(segment.dynamic)) {\n config.dynamicConfig = segment.dynamic;\n }\n\n if (isRouteSegmentRuntime(segment.runtime)) {\n config.runtime = segment.runtime;\n }\n\n if (segment.dynamicParams === false) {\n config.dynamicParamsConfig = false;\n } else if (segment.dynamicParams === true && config.dynamicParamsConfig !== false) {\n config.dynamicParamsConfig = true;\n }\n\n if (isRouteSegmentFetchCache(segment.fetchCache)) {\n const fetchCache = segment.fetchCache;\n\n if (hasParentDefaultNoStore && (fetchCache === \"auto\" || isCacheFetchCacheMode(fetchCache))) {\n throw new Error(describeFetchCacheConflict(fetchCache));\n }\n\n if (fetchCache === \"force-cache\") hasForceCache = true;\n if (fetchCache === \"force-no-store\") hasForceNoStore = true;\n if (fetchCache === \"only-cache\") hasOnlyCache = true;\n if (fetchCache === \"only-no-store\") hasOnlyNoStore = true;\n\n const hasCacheEnforcer = hasForceCache || hasOnlyCache;\n const hasNoStoreEnforcer = hasForceNoStore || hasOnlyNoStore;\n if (hasCacheEnforcer && hasNoStoreEnforcer) {\n throw new Error(describeFetchCacheConflict(fetchCache));\n }\n\n if (fetchCache === \"default-no-store\") {\n hasParentDefaultNoStore = true;\n }\n\n if (hasForceCache) {\n config.fetchCache = \"force-cache\";\n } else if (hasForceNoStore) {\n config.fetchCache = \"force-no-store\";\n } else if (hasOnlyCache) {\n config.fetchCache = \"only-cache\";\n } else if (hasOnlyNoStore) {\n config.fetchCache = \"only-no-store\";\n } else {\n config.fetchCache = fetchCache;\n }\n }\n\n config.revalidateSeconds = resolveRevalidateSeconds(\n config.revalidateSeconds,\n segment.revalidate,\n );\n }\n\n if (config.dynamicConfig === \"force-dynamic\") {\n config.revalidateSeconds = 0;\n }\n\n // Top-level dynamic modes supply fetchCache defaults unless a segment does.\n if (config.fetchCache === undefined) {\n if (config.dynamicConfig === \"force-dynamic\") {\n config.fetchCache = \"force-no-store\";\n } else if (config.dynamicConfig === \"error\") {\n config.fetchCache = \"only-cache\";\n }\n }\n\n // Static-only dynamic modes change the default, but explicit dynamicParams wins.\n if (\n config.dynamicParamsConfig === undefined &&\n (config.dynamicConfig === \"error\" || config.dynamicConfig === \"force-static\")\n ) {\n config.dynamicParamsConfig = false;\n }\n\n return config;\n}\n\nexport function resolveAppPageFetchCacheMode(\n options: ResolveAppPageSegmentConfigOptions,\n): FetchCacheMode | null {\n return resolveAppPageSegmentConfig(options).fetchCache ?? null;\n}\n\nexport function isEdgeRuntime(runtime: string | undefined): boolean {\n return isEdgeApiRuntime(runtime);\n}\n"],"mappings":";;AA0BA,MAAM,iBAAiB,IAAI,IAAa;CAAC;CAAQ;CAAS;CAAiB;CAAe,CAAC;AAC3F,MAAM,qBAAqB,IAAI,IAAa;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,sBAAsB,OAAiD;CAC9E,OAAO,eAAe,IAAI,MAAM;;AAGlC,SAAS,yBAAyB,OAAyC;CACzE,OAAO,mBAAmB,IAAI,MAAM;;AAGtC,SAAS,sBAAsB,OAAmE;CAChG,OAAO,UAAU,UAAU,UAAU,uBAAuB,UAAU;;AAGxE,SAAS,yBAAyB,SAAwB,OAA+B;CAIvF,IAAI,UAAU,OAAO;EACnB,IAAI,YAAY,MAAM,OAAO;EAE7B,OAAO,YAAY,WAAW,WAAW;;CAG3C,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI,YAAY,MACd,OAAO;CAGT,OAAO,QAAQ,UAAU,QAAQ;;AAGnC,SAAS,sBAAsB,OAAgC;CAC7D,OAAO,UAAU,mBAAmB,UAAU,iBAAiB,UAAU;;AAG3E,SAAS,2BAA2B,OAA+B;CACjE,OAAO,sEAAsE,MAAM;;;;;;;;;;AAWrF,SAAgB,4BACd,SAC+B;CAC/B,MAAM,WAAW,CAAC,GAAI,QAAQ,WAAW,EAAE,EAAG,QAAQ,KAAK;CAM3D,MAAM,SAAwC,EAC5C,mBAAmB,MACpB;CACD,IAAI,gBAAgB;CACpB,IAAI,kBAAkB;CACtB,IAAI,eAAe;CACnB,IAAI,iBAAiB;CACrB,IAAI,0BAA0B;CAE9B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,CAAC,SAAS;EAEd,IAAI,sBAAsB,QAAQ,QAAQ,EACxC,OAAO,gBAAgB,QAAQ;EAGjC,IAAI,sBAAsB,QAAQ,QAAQ,EACxC,OAAO,UAAU,QAAQ;EAG3B,IAAI,QAAQ,kBAAkB,OAC5B,OAAO,sBAAsB;OACxB,IAAI,QAAQ,kBAAkB,QAAQ,OAAO,wBAAwB,OAC1E,OAAO,sBAAsB;EAG/B,IAAI,yBAAyB,QAAQ,WAAW,EAAE;GAChD,MAAM,aAAa,QAAQ;GAE3B,IAAI,4BAA4B,eAAe,UAAU,sBAAsB,WAAW,GACxF,MAAM,IAAI,MAAM,2BAA2B,WAAW,CAAC;GAGzD,IAAI,eAAe,eAAe,gBAAgB;GAClD,IAAI,eAAe,kBAAkB,kBAAkB;GACvD,IAAI,eAAe,cAAc,eAAe;GAChD,IAAI,eAAe,iBAAiB,iBAAiB;GAIrD,KAFyB,iBAAiB,kBACf,mBAAmB,iBAE5C,MAAM,IAAI,MAAM,2BAA2B,WAAW,CAAC;GAGzD,IAAI,eAAe,oBACjB,0BAA0B;GAG5B,IAAI,eACF,OAAO,aAAa;QACf,IAAI,iBACT,OAAO,aAAa;QACf,IAAI,cACT,OAAO,aAAa;QACf,IAAI,gBACT,OAAO,aAAa;QAEpB,OAAO,aAAa;;EAIxB,OAAO,oBAAoB,yBACzB,OAAO,mBACP,QAAQ,WACT;;CAGH,IAAI,OAAO,kBAAkB,iBAC3B,OAAO,oBAAoB;CAI7B,IAAI,OAAO,eAAe,KAAA;MACpB,OAAO,kBAAkB,iBAC3B,OAAO,aAAa;OACf,IAAI,OAAO,kBAAkB,SAClC,OAAO,aAAa;;CAKxB,IACE,OAAO,wBAAwB,KAAA,MAC9B,OAAO,kBAAkB,WAAW,OAAO,kBAAkB,iBAE9D,OAAO,sBAAsB;CAG/B,OAAO;;AAGT,SAAgB,6BACd,SACuB;CACvB,OAAO,4BAA4B,QAAQ,CAAC,cAAc;;AAG5D,SAAgB,cAAc,SAAsC;CAClE,OAAO,iBAAiB,QAAQ"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActionRevalidationKind } from "../shims/cache.js";
|
|
1
2
|
import { AppRscRenderMode } from "./app-rsc-render-mode.js";
|
|
2
3
|
import { FetchCacheMode } from "../shims/fetch-cache.js";
|
|
3
4
|
import { HeadersAccessPhase } from "../shims/headers.js";
|
|
@@ -26,17 +27,38 @@ type AppServerActionReturnValue = {
|
|
|
26
27
|
ok: false;
|
|
27
28
|
};
|
|
28
29
|
type AppServerActionRoute = {
|
|
30
|
+
page?: unknown;
|
|
29
31
|
pattern: string;
|
|
32
|
+
routeHandler?: unknown;
|
|
33
|
+
routeSegments?: readonly string[];
|
|
30
34
|
};
|
|
31
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Side-effect headers captured during a progressive (no-JS) server action's
|
|
37
|
+
* non-redirect execution. The caller (app-rsc-handler) must apply these to the
|
|
38
|
+
* page render response so that `cookies().set(...)` and revalidation kinds
|
|
39
|
+
* propagate to the browser. Without this, no-JS form submissions silently
|
|
40
|
+
* lose cookie/header mutations — see issue #1483.
|
|
41
|
+
*
|
|
42
|
+
* Next.js' equivalent path mutates `res.setHeader('set-cookie', ...)` during
|
|
43
|
+
* action execution (action-handler.ts → app-render.tsx), then `sendResponse`
|
|
44
|
+
* merges those headers with the rendered Response. vinext works with Response
|
|
45
|
+
* objects directly so the cookies must ride out via the result instead.
|
|
46
|
+
*/
|
|
47
|
+
type ProgressiveServerActionSideEffects = {
|
|
48
|
+
/** `Set-Cookie` headers from `cookies().set(...)` / `cookies().delete(...)`. */pendingCookies: string[]; /** `Set-Cookie` header from `draftMode().enable()/disable()` (if any). */
|
|
49
|
+
draftCookie: string | null | undefined; /** Resolved revalidation kind to emit via `x-action-revalidated`. */
|
|
50
|
+
revalidationKind: ActionRevalidationKind;
|
|
51
|
+
};
|
|
52
|
+
type AppServerActionRouteRuntime = "edge" | "experimental-edge" | "nodejs" | null;
|
|
53
|
+
type ProgressiveServerActionResult = ({
|
|
32
54
|
formState: ReactFormState | null;
|
|
33
55
|
kind: "form-state";
|
|
34
|
-
} | {
|
|
56
|
+
} & ProgressiveServerActionSideEffects) | ({
|
|
35
57
|
actionError: unknown;
|
|
36
58
|
actionFailed: true;
|
|
37
59
|
formState: null;
|
|
38
60
|
kind: "form-state";
|
|
39
|
-
};
|
|
61
|
+
} & ProgressiveServerActionSideEffects);
|
|
40
62
|
type AppServerActionMatch<TRoute extends AppServerActionRoute> = {
|
|
41
63
|
params: AppPageParams;
|
|
42
64
|
route: TRoute;
|
|
@@ -124,6 +146,7 @@ type HandleServerActionRscRequestOptions<TElement, TRoute extends AppServerActio
|
|
|
124
146
|
renderToReadableStream: (model: AppServerActionRscModel<TElement>, options: RenderServerActionRscStreamOptions<TTemporaryReferences>) => BodyInit | null | Promise<BodyInit | null>;
|
|
125
147
|
reportRequestError: AppServerActionErrorReporter;
|
|
126
148
|
resolveRouteFetchCacheMode?: (route: TRoute) => FetchCacheMode | null;
|
|
149
|
+
resolveRouteRuntime?: (route: TRoute) => AppServerActionRouteRuntime;
|
|
127
150
|
request: Request;
|
|
128
151
|
sanitizeErrorForClient: (error: unknown) => unknown;
|
|
129
152
|
searchParams: URLSearchParams;
|