vinext 0.0.53 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +4 -0
- package/dist/check.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +2 -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 +83 -1
- package/dist/config/next-config.js +131 -2
- package/dist/config/next-config.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 +8 -1
- package/dist/entries/app-rsc-entry.js +18 -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 +6 -4
- 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 +82 -4
- 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 +63 -5
- package/dist/index.js.map +1 -1
- 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-entry.js +25 -16
- package/dist/server/app-browser-entry.js.map +1 -1
- 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-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-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-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-handler.d.ts +14 -3
- package/dist/server/app-rsc-handler.js +45 -5
- 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-server-action-execution.d.ts +21 -3
- package/dist/server/app-server-action-execution.js +42 -7
- 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/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 +34 -5
- package/dist/server/dev-server.js.map +1 -1
- 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 +7 -0
- package/dist/server/pages-document-initial-props.js +14 -0
- package/dist/server/pages-document-initial-props.js.map +1 -0
- 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 +6 -0
- package/dist/server/pages-page-response.js +10 -3
- 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 +3 -0
- 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/static-file-cache.js +2 -1
- package/dist/server/static-file-cache.js.map +1 -1
- 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 +6 -0
- package/dist/shims/cache.js +7 -0
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/error.js +3 -0
- package/dist/shims/error.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/link.d.ts +18 -2
- package/dist/shims/link.js +70 -6
- 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 +1 -2
- package/dist/shims/navigation.js +63 -12
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.d.ts +5 -0
- package/dist/shims/router.js +14 -4
- 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/utils/path.d.ts +13 -0
- package/dist/utils/path.js +16 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +1 -1
|
@@ -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,14 +12,14 @@ 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";
|
|
@@ -173,6 +173,8 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
173
173
|
searchParams: url.searchParams,
|
|
174
174
|
params: {}
|
|
175
175
|
});
|
|
176
|
+
const preActionMatch = options.matchRoute(cleanPathname);
|
|
177
|
+
if (preActionMatch) setRootParams(pickRootParams(preActionMatch.params, preActionMatch.route.rootParamNames));
|
|
176
178
|
const actionId = request.headers.get("x-rsc-action") ?? request.headers.get("next-action");
|
|
177
179
|
const contentType = request.headers.get("content-type") || "";
|
|
178
180
|
const progressiveActionResult = await options.handleProgressiveActionRequest({
|
|
@@ -200,7 +202,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
200
202
|
searchParams: url.searchParams
|
|
201
203
|
});
|
|
202
204
|
if (serverActionResponse) return serverActionResponse;
|
|
203
|
-
let match =
|
|
205
|
+
let match = preActionMatch;
|
|
204
206
|
if (!match || match.route.isDynamic) {
|
|
205
207
|
const afterFilesRewrite = await applyRewrite({
|
|
206
208
|
basePathState,
|
|
@@ -230,6 +232,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
230
232
|
}
|
|
231
233
|
}
|
|
232
234
|
if (!match) {
|
|
235
|
+
if (process.env.NODE_ENV !== "production" && canonicalPathname === "/favicon.ico") {
|
|
236
|
+
options.clearRequestContext();
|
|
237
|
+
return new Response("", { status: 404 });
|
|
238
|
+
}
|
|
233
239
|
const pagesFallbackResponse = await options.renderPagesFallback?.({
|
|
234
240
|
isRscRequest,
|
|
235
241
|
middlewareContext,
|
|
@@ -275,7 +281,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
275
281
|
searchParams: url.searchParams
|
|
276
282
|
});
|
|
277
283
|
}
|
|
278
|
-
|
|
284
|
+
const pageResponse = await options.dispatchMatchedPage({
|
|
279
285
|
cleanPathname,
|
|
280
286
|
formState,
|
|
281
287
|
actionError,
|
|
@@ -295,6 +301,40 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
|
|
|
295
301
|
searchParams: url.searchParams,
|
|
296
302
|
renderMode
|
|
297
303
|
});
|
|
304
|
+
if (isProgressiveActionRender) return applyProgressiveActionSideEffects(pageResponse, progressiveActionResult);
|
|
305
|
+
return pageResponse;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Append `Set-Cookie` headers and the `x-action-revalidated` marker captured
|
|
309
|
+
* during progressive (no-JS) server action execution to the page render
|
|
310
|
+
* response. See issue #1483.
|
|
311
|
+
*
|
|
312
|
+
* Falls back to rebuilding the response when the headers object is immutable
|
|
313
|
+
* (e.g. `Response.redirect()`), so cookies set by the action ride out on a
|
|
314
|
+
* redirect issued during the rerender too.
|
|
315
|
+
*/
|
|
316
|
+
function applyProgressiveActionSideEffects(response, sideEffects) {
|
|
317
|
+
const hasPendingCookies = sideEffects.pendingCookies.length > 0;
|
|
318
|
+
const hasDraftCookie = Boolean(sideEffects.draftCookie);
|
|
319
|
+
const hasRevalidationKind = sideEffects.revalidationKind !== 0;
|
|
320
|
+
if (!hasPendingCookies && !hasDraftCookie && !hasRevalidationKind) return response;
|
|
321
|
+
const applyTo = (headers) => {
|
|
322
|
+
for (const cookie of sideEffects.pendingCookies) headers.append("Set-Cookie", cookie);
|
|
323
|
+
if (sideEffects.draftCookie) headers.append("Set-Cookie", sideEffects.draftCookie);
|
|
324
|
+
if (hasRevalidationKind) headers.set(ACTION_REVALIDATED_HEADER, JSON.stringify(sideEffects.revalidationKind));
|
|
325
|
+
};
|
|
326
|
+
try {
|
|
327
|
+
applyTo(response.headers);
|
|
328
|
+
return response;
|
|
329
|
+
} catch {
|
|
330
|
+
const headers = new Headers(response.headers);
|
|
331
|
+
applyTo(headers);
|
|
332
|
+
return new Response(response.body, {
|
|
333
|
+
status: response.status,
|
|
334
|
+
statusText: response.statusText,
|
|
335
|
+
headers
|
|
336
|
+
});
|
|
337
|
+
}
|
|
298
338
|
}
|
|
299
339
|
function createAppRscHandler(options) {
|
|
300
340
|
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\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) 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 // 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":"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"}
|
|
@@ -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"}
|
|
@@ -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";
|
|
@@ -28,15 +29,32 @@ type AppServerActionReturnValue = {
|
|
|
28
29
|
type AppServerActionRoute = {
|
|
29
30
|
pattern: string;
|
|
30
31
|
};
|
|
31
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Side-effect headers captured during a progressive (no-JS) server action's
|
|
34
|
+
* non-redirect execution. The caller (app-rsc-handler) must apply these to the
|
|
35
|
+
* page render response so that `cookies().set(...)` and revalidation kinds
|
|
36
|
+
* propagate to the browser. Without this, no-JS form submissions silently
|
|
37
|
+
* lose cookie/header mutations — see issue #1483.
|
|
38
|
+
*
|
|
39
|
+
* Next.js' equivalent path mutates `res.setHeader('set-cookie', ...)` during
|
|
40
|
+
* action execution (action-handler.ts → app-render.tsx), then `sendResponse`
|
|
41
|
+
* merges those headers with the rendered Response. vinext works with Response
|
|
42
|
+
* objects directly so the cookies must ride out via the result instead.
|
|
43
|
+
*/
|
|
44
|
+
type ProgressiveServerActionSideEffects = {
|
|
45
|
+
/** `Set-Cookie` headers from `cookies().set(...)` / `cookies().delete(...)`. */pendingCookies: string[]; /** `Set-Cookie` header from `draftMode().enable()/disable()` (if any). */
|
|
46
|
+
draftCookie: string | null | undefined; /** Resolved revalidation kind to emit via `x-action-revalidated`. */
|
|
47
|
+
revalidationKind: ActionRevalidationKind;
|
|
48
|
+
};
|
|
49
|
+
type ProgressiveServerActionResult = ({
|
|
32
50
|
formState: ReactFormState | null;
|
|
33
51
|
kind: "form-state";
|
|
34
|
-
} | {
|
|
52
|
+
} & ProgressiveServerActionSideEffects) | ({
|
|
35
53
|
actionError: unknown;
|
|
36
54
|
actionFailed: true;
|
|
37
55
|
formState: null;
|
|
38
56
|
kind: "form-state";
|
|
39
|
-
};
|
|
57
|
+
} & ProgressiveServerActionSideEffects);
|
|
40
58
|
type AppServerActionMatch<TRoute extends AppServerActionRoute> = {
|
|
41
59
|
params: AppPageParams;
|
|
42
60
|
route: TRoute;
|
|
@@ -7,12 +7,13 @@ import { getAndClearActionRevalidationKind } from "../shims/cache.js";
|
|
|
7
7
|
import { APP_RSC_RENDER_MODE_ACTION_RERENDER_PRESERVE_UI } from "./app-rsc-render-mode.js";
|
|
8
8
|
import { setCurrentFetchCacheMode } from "../shims/fetch-cache.js";
|
|
9
9
|
import { VINEXT_RSC_CONTENT_TYPE, VINEXT_RSC_VARY_HEADER, applyRscCompatibilityIdHeader } from "./app-rsc-cache-busting.js";
|
|
10
|
+
import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
|
|
10
11
|
import { createServerActionNotFoundResponse, getServerActionNotFoundMessage, isServerActionNotFoundError } from "./server-action-not-found.js";
|
|
11
12
|
import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
|
|
12
13
|
import { applyEdgeRuntimeHeader } from "./app-page-response.js";
|
|
13
14
|
import { getNextErrorDigest, parseNextHttpErrorDigest, parseNextRedirectDigest } from "./next-error-digest.js";
|
|
14
|
-
import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
|
|
15
15
|
import { resolveAppPageActionRerenderTarget } from "./app-page-request.js";
|
|
16
|
+
import { getSetCookieName } from "./cookie-utils.js";
|
|
16
17
|
//#region src/server/app-server-action-execution.ts
|
|
17
18
|
/**
|
|
18
19
|
* Matches Next.js' server action argument cap to prevent stack overflow in
|
|
@@ -33,6 +34,32 @@ function resolveActionRevalidationKind(hasModifiedCookies) {
|
|
|
33
34
|
function isRequestBodyTooLarge(error) {
|
|
34
35
|
return error instanceof Error && error.message === "Request body too large";
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Collapse repeated `cookies().set(name, ...)` / `cookies().delete(name)`
|
|
39
|
+
* calls down to the last value per name, matching Next.js'
|
|
40
|
+
* `MutableRequestCookiesAdapter` semantics. Next.js stores response cookies in
|
|
41
|
+
* a `ResponseCookies` Map keyed by name — multiple sets for the same cookie
|
|
42
|
+
* collapse to the final value, and emit a single Set-Cookie header.
|
|
43
|
+
*
|
|
44
|
+
* Insertion order is preserved by first occurrence (Map iteration order),
|
|
45
|
+
* which mirrors how `ResponseCookies` iterates its underlying Map. See
|
|
46
|
+
* packages/next/src/server/web/spec-extension/adapters/request-cookies.ts.
|
|
47
|
+
* Issue: https://github.com/cloudflare/vinext/issues/1481
|
|
48
|
+
*/
|
|
49
|
+
function dedupePendingCookies(cookies) {
|
|
50
|
+
if (cookies.length <= 1) return cookies.slice();
|
|
51
|
+
const byName = /* @__PURE__ */ new Map();
|
|
52
|
+
const unkeyed = [];
|
|
53
|
+
for (const cookie of cookies) {
|
|
54
|
+
const name = getSetCookieName(cookie);
|
|
55
|
+
if (name === null) {
|
|
56
|
+
unkeyed.push(cookie);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
byName.set(name, cookie);
|
|
60
|
+
}
|
|
61
|
+
return [...unkeyed, ...byName.values()];
|
|
62
|
+
}
|
|
36
63
|
function isAppServerActionFunction(action) {
|
|
37
64
|
return typeof action === "function";
|
|
38
65
|
}
|
|
@@ -210,19 +237,27 @@ async function handleProgressiveServerActionRequest(options) {
|
|
|
210
237
|
options.setHeadersAccessPhase(previousHeadersPhase);
|
|
211
238
|
}
|
|
212
239
|
if (!actionRedirect) {
|
|
213
|
-
|
|
240
|
+
const actionPendingCookies = options.getAndClearPendingCookies();
|
|
241
|
+
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
242
|
+
const revalidationKind = resolveActionRevalidationKind(actionPendingCookies.length > 0 || Boolean(actionDraftCookie));
|
|
214
243
|
if (actionFailed) return {
|
|
215
244
|
kind: "form-state",
|
|
216
245
|
formState: null,
|
|
217
246
|
actionError,
|
|
218
|
-
actionFailed
|
|
247
|
+
actionFailed,
|
|
248
|
+
pendingCookies: actionPendingCookies,
|
|
249
|
+
draftCookie: actionDraftCookie,
|
|
250
|
+
revalidationKind
|
|
219
251
|
};
|
|
220
252
|
return {
|
|
221
253
|
kind: "form-state",
|
|
222
|
-
formState: await options.decodeFormState(actionResult, body) ?? null
|
|
254
|
+
formState: await options.decodeFormState(actionResult, body) ?? null,
|
|
255
|
+
pendingCookies: actionPendingCookies,
|
|
256
|
+
draftCookie: actionDraftCookie,
|
|
257
|
+
revalidationKind
|
|
223
258
|
};
|
|
224
259
|
}
|
|
225
|
-
const actionPendingCookies = options.getAndClearPendingCookies();
|
|
260
|
+
const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
|
|
226
261
|
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
227
262
|
const actionRevalidationKind = resolveActionRevalidationKind(actionPendingCookies.length > 0 || Boolean(actionDraftCookie));
|
|
228
263
|
options.clearRequestContext();
|
|
@@ -340,7 +375,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
340
375
|
options.setHeadersAccessPhase(previousHeadersPhase);
|
|
341
376
|
}
|
|
342
377
|
if (actionRedirect) {
|
|
343
|
-
const actionPendingCookies = options.getAndClearPendingCookies();
|
|
378
|
+
const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
|
|
344
379
|
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
345
380
|
const actionRevalidationKind = resolveActionRevalidationKind(actionPendingCookies.length > 0 || Boolean(actionDraftCookie));
|
|
346
381
|
options.clearRequestContext();
|
|
@@ -362,7 +397,7 @@ async function handleServerActionRscRequest(options) {
|
|
|
362
397
|
headers: redirectHeaders
|
|
363
398
|
});
|
|
364
399
|
}
|
|
365
|
-
const actionPendingCookies = options.getAndClearPendingCookies();
|
|
400
|
+
const actionPendingCookies = dedupePendingCookies(options.getAndClearPendingCookies());
|
|
366
401
|
const actionDraftCookie = options.getDraftModeCookieHeader();
|
|
367
402
|
const actionRevalidationKind = resolveActionRevalidationKind(actionPendingCookies.length > 0 || Boolean(actionDraftCookie));
|
|
368
403
|
if (actionRevalidationKind === ACTION_DID_NOT_REVALIDATE) {
|