vinext 0.1.3 → 0.1.4

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.
Files changed (82) hide show
  1. package/dist/build/client-build-config.d.ts +11 -2
  2. package/dist/build/client-build-config.js +17 -6
  3. package/dist/build/prerender.js +1 -0
  4. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  5. package/dist/client/pages-router-link-navigation.js +32 -2
  6. package/dist/client/vinext-next-data.js +2 -0
  7. package/dist/config/config-matchers.d.ts +11 -1
  8. package/dist/config/config-matchers.js +14 -2
  9. package/dist/config/tsconfig-paths.js +14 -1
  10. package/dist/deploy.js +20 -13
  11. package/dist/entries/app-rsc-entry.js +3 -2
  12. package/dist/entries/pages-client-entry.js +14 -13
  13. package/dist/entries/pages-server-entry.js +6 -26
  14. package/dist/index.js +217 -40
  15. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  16. package/dist/plugins/fonts.js +5 -4
  17. package/dist/plugins/strip-server-exports.d.ts +9 -7
  18. package/dist/plugins/strip-server-exports.js +493 -46
  19. package/dist/routing/app-route-graph.js +2 -2
  20. package/dist/server/app-browser-action-result.js +1 -1
  21. package/dist/server/app-browser-entry.js +8 -1
  22. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  23. package/dist/server/app-browser-state.d.ts +1 -1
  24. package/dist/server/app-browser-state.js +19 -11
  25. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  26. package/dist/server/app-pages-bridge.d.ts +5 -1
  27. package/dist/server/app-pages-bridge.js +5 -13
  28. package/dist/server/app-rsc-handler.d.ts +3 -0
  29. package/dist/server/app-rsc-handler.js +51 -15
  30. package/dist/server/app-rsc-route-matching.js +6 -2
  31. package/dist/server/app-server-action-execution.js +5 -2
  32. package/dist/server/app-ssr-entry.js +1 -29
  33. package/dist/server/before-interactive-head.d.ts +17 -0
  34. package/dist/server/before-interactive-head.js +35 -0
  35. package/dist/server/csp.js +1 -4
  36. package/dist/server/dev-server.js +81 -36
  37. package/dist/server/middleware-matcher.js +12 -3
  38. package/dist/server/middleware-runtime.d.ts +3 -4
  39. package/dist/server/middleware-runtime.js +2 -0
  40. package/dist/server/navigation-planner.d.ts +3 -12
  41. package/dist/server/navigation-planner.js +24 -0
  42. package/dist/server/navigation-trace.d.ts +2 -1
  43. package/dist/server/navigation-trace.js +1 -0
  44. package/dist/server/operation-token.d.ts +40 -0
  45. package/dist/server/operation-token.js +85 -0
  46. package/dist/server/pages-data-route.d.ts +1 -1
  47. package/dist/server/pages-data-route.js +7 -4
  48. package/dist/server/pages-dev-module-url.d.ts +4 -0
  49. package/dist/server/pages-dev-module-url.js +15 -0
  50. package/dist/server/pages-document-initial-props.d.ts +4 -15
  51. package/dist/server/pages-document-initial-props.js +27 -56
  52. package/dist/server/pages-i18n.js +2 -2
  53. package/dist/server/pages-page-data.js +3 -1
  54. package/dist/server/pages-page-handler.js +3 -1
  55. package/dist/server/pages-page-response.d.ts +2 -0
  56. package/dist/server/pages-page-response.js +4 -4
  57. package/dist/server/pages-readiness.js +1 -1
  58. package/dist/server/pages-request-pipeline.d.ts +7 -7
  59. package/dist/server/pages-request-pipeline.js +63 -21
  60. package/dist/server/prod-server.d.ts +3 -1
  61. package/dist/server/prod-server.js +41 -10
  62. package/dist/server/static-file-cache.js +16 -4
  63. package/dist/shims/before-interactive-context.d.ts +14 -3
  64. package/dist/shims/document.d.ts +15 -20
  65. package/dist/shims/document.js +5 -8
  66. package/dist/shims/image.js +9 -2
  67. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  68. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  69. package/dist/shims/internal/pages-data-target.js +1 -1
  70. package/dist/shims/link.js +37 -16
  71. package/dist/shims/metadata.js +4 -4
  72. package/dist/shims/navigation.js +2 -0
  73. package/dist/shims/router.d.ts +6 -2
  74. package/dist/shims/router.js +99 -20
  75. package/dist/shims/script.js +8 -4
  76. package/dist/utils/has-trailing-comma.d.ts +24 -0
  77. package/dist/utils/has-trailing-comma.js +62 -0
  78. package/dist/utils/text-stream.d.ts +1 -1
  79. package/dist/utils/text-stream.js +2 -2
  80. package/dist/utils/vite-version.d.ts +12 -1
  81. package/dist/utils/vite-version.js +9 -1
  82. package/package.json +1 -1
@@ -8,6 +8,7 @@ import "./app-bfcache-id.js";
8
8
  import { createHistoryStateWithNavigationMetadata, createHistoryStateWithPreviousNextUrl, isBfcacheSegmentId, isHistoryStateBfcacheVersionCurrent, readHistoryStateBfcacheIds, readHistoryStateBfcacheVersion, readHistoryStatePreviousNextUrl, readHistoryStateTraversalIndex, resolveHistoryTraversalIntent } from "./app-history-state.js";
9
9
  import { createRscRequestHeaders } from "./app-rsc-cache-busting.js";
10
10
  import { NavigationTraceReasonCodes, createNavigationLifecycleTraceFields, createNavigationTrace } from "./navigation-trace.js";
11
+ import { verifyOperationTokenForCommit } from "./operation-token.js";
11
12
  import { navigationPlanner, resolveDefaultOrUnmatchedSlotPersistenceForLayouts } from "./navigation-planner.js";
12
13
  import { createSnapshotPathAndSearch } from "../shims/navigation.js";
13
14
  import { createCacheEntryReuseProof } from "./cache-proof.js";
@@ -192,7 +193,17 @@ function resolveServerActionRequestState(options) {
192
193
  }
193
194
  function resolvePendingNavigationCommitDispositionDecision(options) {
194
195
  const traceFields = createPendingNavigationTraceFields(options);
195
- if (options.startedNavigationId !== options.activeNavigationId || options.pending.action.operation.startedVisibleCommitVersion !== options.currentState.visibleCommitVersion) return {
196
+ const targetSnapshot = createPendingRouteSnapshot(options.pending);
197
+ const verdict = verifyOperationTokenForCommit(createPendingNavigationOperationToken({
198
+ pending: options.pending,
199
+ routeManifest: options.routeManifest ?? null,
200
+ startedNavigationId: options.startedNavigationId,
201
+ targetSnapshot
202
+ }), {
203
+ activeNavigationId: options.activeNavigationId,
204
+ visibleCommitVersion: options.currentState.visibleCommitVersion
205
+ });
206
+ if (!verdict.authorized) return {
196
207
  disposition: "skip",
197
208
  preserveElementIds: [],
198
209
  trace: createNavigationTrace(NavigationTraceReasonCodes.staleOperation, traceFields)
@@ -202,6 +213,8 @@ function resolvePendingNavigationCommitDispositionDecision(options) {
202
213
  pending: options.pending,
203
214
  routeManifest: options.routeManifest ?? null,
204
215
  targetHref: options.targetHref,
216
+ targetSnapshot,
217
+ token: verdict.token,
205
218
  traceFields
206
219
  }));
207
220
  return mergeSkippedLayoutPreservation({
@@ -277,6 +290,7 @@ function createPendingNavigationOperationToken(options) {
277
290
  deploymentVersion: null,
278
291
  graphVersion: options.routeManifest?.graphVersion ?? null,
279
292
  lane: options.pending.action.operation.lane,
293
+ navigationId: options.startedNavigationId,
280
294
  operationId: options.pending.action.operation.id,
281
295
  targetSnapshotFingerprint: createRootBoundarySnapshotFingerprint(options.targetSnapshot)
282
296
  };
@@ -285,17 +299,11 @@ function createRootBoundarySnapshotFingerprint(snapshot) {
285
299
  return `${snapshot.routeId}|root:${snapshot.rootBoundaryId ?? "unknown"}`;
286
300
  }
287
301
  function planPendingRootBoundaryFlightResponse(options) {
288
- const targetSnapshot = createPendingRouteSnapshot(options.pending);
289
- const token = createPendingNavigationOperationToken({
290
- pending: options.pending,
291
- routeManifest: options.routeManifest,
292
- targetSnapshot
293
- });
294
302
  const cacheEntryReuseProof = options.pending.cacheEntryReuseProof;
295
303
  return navigationPlanner.plan({
296
304
  routeManifest: options.routeManifest,
297
305
  state: {
298
- nextOperationToken: token,
306
+ nextOperationToken: options.token,
299
307
  traceFields: options.traceFields,
300
308
  visibleCommitVersion: options.currentState.visibleCommitVersion,
301
309
  visibleSnapshot: createVisibleRouteSnapshot(options.currentState)
@@ -304,10 +312,10 @@ function planPendingRootBoundaryFlightResponse(options) {
304
312
  kind: "flightResponseArrived",
305
313
  result: {
306
314
  ...cacheEntryReuseProof ? { cacheEntryReuseProof } : {},
307
- href: options.targetHref ?? targetSnapshot.displayUrl,
308
- targetSnapshot
315
+ href: options.targetHref ?? options.targetSnapshot.displayUrl,
316
+ targetSnapshot: options.targetSnapshot
309
317
  },
310
- token
318
+ token: options.token
311
319
  }
312
320
  });
313
321
  }
@@ -1,7 +1,7 @@
1
1
  import { RouteManifest } from "../routing/app-route-graph.js";
2
2
  import { AppElements } from "./app-elements-wire.js";
3
3
  import { NavigationTrace } from "./navigation-trace.js";
4
- import { OperationLane } from "./navigation-planner.js";
4
+ import { OperationLane } from "./operation-token.js";
5
5
  import { ClientNavigationRenderSnapshot } from "../shims/navigation.js";
6
6
  import { AppNavigationPayloadOrigin, AppRouterAction, AppRouterState, PendingNavigationCommit } from "./app-browser-state.js";
7
7
 
@@ -5,7 +5,9 @@ type PagesEntry = {
5
5
  handleApiRoute?: (request: Request, url: string) => Promise<Response> | Response;
6
6
  matchApiRoute?: (url: string, request: Request) => PagesRouteMatch | null;
7
7
  matchPageRoute?: (url: string, request: Request) => PagesRouteMatch | null;
8
- renderPage?: (request: Request, url: string, query: Record<string, unknown>, parsedUrl: unknown, middlewareRequestHeaders?: Headers | null) => Promise<Response> | Response;
8
+ renderPage?: (request: Request, url: string, query: Record<string, unknown>, parsedUrl: unknown, middlewareRequestHeaders?: Headers | null, options?: {
9
+ isDataReq?: boolean;
10
+ }) => Promise<Response> | Response;
9
11
  };
10
12
  type PagesRouteMatch = {
11
13
  route: {
@@ -44,10 +46,12 @@ type RenderPagesFallbackDependencies = {
44
46
  type RenderPagesFallbackOptions = {
45
47
  allowRscDocumentFallback?: boolean;
46
48
  appRouteMatch?: AppRouteMatch | null;
49
+ isDataRequest?: boolean;
47
50
  isRscRequest: boolean;
48
51
  matchKind?: "dynamic" | "static";
49
52
  middlewareContext: AppMiddlewareContext;
50
53
  pathname?: string;
54
+ pagesDataRequest?: Request | null;
51
55
  request: Request;
52
56
  url: URL;
53
57
  };
@@ -1,26 +1,17 @@
1
+ import { cloneRequestWithHeaders, cloneRequestWithUrl } from "./request-pipeline.js";
1
2
  import { pagesRouteHasPriorityOverAppRoute } from "./hybrid-route-priority.js";
2
3
  //#region src/server/app-pages-bridge.ts
3
4
  /**
4
5
  * Fallback handler to route App Router requests to the Pages Router when no App Router route matches.
5
6
  */
6
7
  async function renderPagesFallback(options, dependencies) {
7
- const { allowRscDocumentFallback = false, appRouteMatch = null, isRscRequest, matchKind, middlewareContext, pathname = options.url.pathname, request, url } = options;
8
+ const { allowRscDocumentFallback = false, appRouteMatch = null, isDataRequest = false, isRscRequest, matchKind, middlewareContext, pathname = options.url.pathname, pagesDataRequest = null, request, url } = options;
8
9
  const { loadPagesEntry, buildRequestHeaders, decodePathParams, applyRouteHandlerMiddlewareContext, getDraftModeCookieHeader } = dependencies;
9
10
  if (isRscRequest && !allowRscDocumentFallback) return null;
10
11
  const pagesEntry = await loadPagesEntry();
11
12
  const pagesRequestHeaders = middlewareContext.requestHeaders ? buildRequestHeaders(request.headers, middlewareContext.requestHeaders) : null;
12
13
  let pagesRequest = request;
13
- if (pagesRequestHeaders) {
14
- const pagesRequestInit = {
15
- method: request.method,
16
- headers: pagesRequestHeaders
17
- };
18
- if (request.method !== "GET" && request.method !== "HEAD") {
19
- pagesRequestInit.body = request.body;
20
- pagesRequestInit.duplex = "half";
21
- }
22
- pagesRequest = new Request(request.url, pagesRequestInit);
23
- }
14
+ if (pagesRequestHeaders) pagesRequest = cloneRequestWithHeaders(request, pagesRequestHeaders);
24
15
  const queryIndex = pathname.indexOf("?");
25
16
  const pagesPathname = queryIndex === -1 ? pathname : pathname.slice(0, queryIndex);
26
17
  const pagesSearch = queryIndex === -1 ? url.search || "" : pathname.slice(queryIndex);
@@ -46,7 +37,8 @@ async function renderPagesFallback(options, dependencies) {
46
37
  if (pageMatch !== null && matchKind === "static" && pageMatch.route.isDynamic) return null;
47
38
  if (pageMatch !== null && matchKind === "dynamic" && !pageMatch.route.isDynamic) return null;
48
39
  if (appRouteMatch !== null && (pageMatch === null || !pagesRouteHasPriorityOverAppRoute(pageMatch.route, appRouteMatch.route))) return null;
49
- const pagesRes = await pagesEntry.renderPage(pagesRequest, pagesUrl, {}, void 0, middlewareContext.requestHeaders);
40
+ const renderRequest = pagesDataRequest ? cloneRequestWithUrl(pagesRequest, pagesDataRequest.url) : pagesRequest;
41
+ const pagesRes = isDataRequest ? await pagesEntry.renderPage(renderRequest, pagesUrl, {}, void 0, middlewareContext.requestHeaders, { isDataReq: true }) : await pagesEntry.renderPage(renderRequest, pagesUrl, {}, void 0, middlewareContext.requestHeaders);
50
42
  if (pagesRes.status === 404 && pageMatch === null) return null;
51
43
  return applyDraftModeCookie(pagesRes, getDraftModeCookieHeader());
52
44
  }
@@ -130,10 +130,12 @@ type RenderPagesFallbackOptions = {
130
130
  pattern: string;
131
131
  };
132
132
  } | null;
133
+ isDataRequest?: boolean;
133
134
  isRscRequest: boolean;
134
135
  matchKind?: "dynamic" | "static";
135
136
  middlewareContext: AppRscMiddlewareContext;
136
137
  pathname?: string;
138
+ pagesDataRequest?: Request | null;
137
139
  request: Request;
138
140
  url: URL;
139
141
  };
@@ -144,6 +146,7 @@ type NavigationContextValue = {
144
146
  };
145
147
  type CreateAppRscHandlerOptions<TRoute extends AppRscHandlerRoute> = {
146
148
  basePath: string;
149
+ buildId: string | null;
147
150
  cacheComponents?: boolean;
148
151
  clearRequestContext: () => void;
149
152
  configHeaders: NextHeader[];
@@ -1,5 +1,5 @@
1
1
  import { createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
- import { hasBasePath } from "../utils/base-path.js";
2
+ import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { ACTION_REVALIDATED_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER } from "./headers.js";
5
5
  import { isExternalUrl, matchRedirect, matchRewrite, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, sanitizeDestination } from "../config/config-matchers.js";
@@ -14,6 +14,7 @@ import { normalizeDefaultLocalePathname } from "./pages-i18n.js";
14
14
  import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, resolveDevImageRedirect } from "./image-optimization.js";
15
15
  import { mergeMiddlewareResponseHeaders } from "./middleware-response-headers.js";
16
16
  import "./app-page-response.js";
17
+ import { buildNextDataNotFoundResponse, normalizePagesDataRequest } from "./pages-data-route.js";
17
18
  import { createAppPprFallbackShells } from "./app-ppr-fallback-shell.js";
18
19
  import { matchPrerenderRouteParamsPayload, readTrustedPrerenderRouteParams, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
19
20
  import { getRenderedConcreteUrlPathsForRoute } from "./pregenerated-concrete-paths.js";
@@ -108,7 +109,7 @@ function requestWithoutRscSuffix(request) {
108
109
  url.pathname = pathname;
109
110
  return cloneRequestWithUrl(request.body ? request.clone() : request, url.toString());
110
111
  }
111
- async function handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest) {
112
+ async function handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest, pagesDataRequest) {
112
113
  const handlerStart = process.env.NODE_ENV !== "production" ? performance.now() : 0;
113
114
  if (process.env.NODE_ENV !== "production") {
114
115
  const originBlock = options.validateDevRequestOrigin?.(request);
@@ -119,6 +120,7 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
119
120
  const { url, isRscRequest, interceptionContextHeader, mountedSlotsHeader, renderMode, clientReuseManifest } = normalized;
120
121
  let { pathname, cleanPathname } = normalized;
121
122
  let resolvedUrl = cleanPathname + url.search;
123
+ const originalResolvedUrl = resolvedUrl;
122
124
  const getResolvedSearchParams = () => new URL(resolvedUrl, url).searchParams;
123
125
  const canonicalPathname = cleanPathname;
124
126
  const basePathState = {
@@ -142,6 +144,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
142
144
  if (redirect) {
143
145
  const destination = sanitizeDestination(redirectDestinationWithBasePath(redirect.destination, options.basePath));
144
146
  const location = isRscRequest && request.headers.get("RSC") === "1" ? await createRscRedirectLocation(destination, request) : preserveRedirectDestinationQuery(destination, url.search);
147
+ if (isDataRequest) return new Response(null, {
148
+ status: 200,
149
+ headers: { "x-nextjs-redirect": location }
150
+ });
145
151
  return new Response(null, {
146
152
  status: redirect.permanent ? 308 : 307,
147
153
  headers: { Location: location }
@@ -269,16 +275,28 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
269
275
  });
270
276
  if (serverActionResponse) return serverActionResponse;
271
277
  let match = preActionMatch;
272
- const renderPagesForMatchKind = async (matchKind) => match === null || match.route.isDynamic ? await options.renderPagesFallback?.({
273
- appRouteMatch: match ?? null,
274
- allowRscDocumentFallback: didMiddlewareRewrite,
275
- isRscRequest,
276
- matchKind,
277
- middlewareContext,
278
- pathname: resolvedUrl,
279
- request,
280
- url
281
- }) ?? null : null;
278
+ const renderPagesForMatchKind = async (matchKind) => {
279
+ const response = match === null || match.route.isDynamic ? await options.renderPagesFallback?.({
280
+ appRouteMatch: match ?? null,
281
+ allowRscDocumentFallback: didMiddlewareRewrite,
282
+ isDataRequest,
283
+ isRscRequest,
284
+ matchKind,
285
+ middlewareContext,
286
+ pathname: resolvedUrl,
287
+ pagesDataRequest,
288
+ request,
289
+ url
290
+ }) ?? null : null;
291
+ if (!response || !pagesDataRequest || resolvedUrl === originalResolvedUrl) return response;
292
+ const headers = new Headers(response.headers);
293
+ headers.set("x-nextjs-rewrite", resolvedUrl);
294
+ return new Response(response.body, {
295
+ headers,
296
+ status: response.status,
297
+ statusText: response.statusText
298
+ });
299
+ };
282
300
  const staticPagesFallbackResponse = await renderPagesForMatchKind("static");
283
301
  if (staticPagesFallbackResponse) {
284
302
  options.clearRequestContext();
@@ -339,6 +357,10 @@ async function handleAppRscRequest(options, request, preMiddlewareRequestContext
339
357
  }
340
358
  if (match) break;
341
359
  }
360
+ if (pagesDataRequest) {
361
+ options.clearRequestContext();
362
+ return buildNextDataNotFoundResponse();
363
+ }
342
364
  if (!match) {
343
365
  if (process.env.NODE_ENV !== "production" && canonicalPathname === "/favicon.ico") {
344
366
  options.clearRequestContext();
@@ -461,13 +483,27 @@ function createAppRscHandler(options) {
461
483
  options.registerCacheAdapters();
462
484
  await options.ensureInstrumentation?.();
463
485
  const mwCtx = rawRequest.headers.get(VINEXT_MW_CTX_HEADER);
464
- const isDataRequest = rawRequest.headers.get("x-nextjs-data") === "1";
486
+ const hasDataRequestHeader = rawRequest.headers.get("x-nextjs-data") === "1";
487
+ const pagesDataUrl = new URL(rawRequest.url);
488
+ const pagesDataInScope = !options.basePath || hasBasePath(pagesDataUrl.pathname, options.basePath);
489
+ if (pagesDataInScope) pagesDataUrl.pathname = stripBasePath(pagesDataUrl.pathname, options.basePath);
490
+ const pagesDataCandidate = pagesDataInScope ? cloneRequestWithUrl(rawRequest, pagesDataUrl.toString()) : null;
491
+ const pagesDataNormalization = options.renderPagesFallback && pagesDataCandidate ? normalizePagesDataRequest(pagesDataCandidate, options.buildId) : null;
492
+ if (pagesDataNormalization?.notFoundResponse) return pagesDataNormalization.notFoundResponse;
493
+ const isDataRequest = hasDataRequestHeader || pagesDataNormalization?.isDataReq === true;
465
494
  const prerenderRouteParamsPayload = readTrustedPrerenderRouteParams(rawRequest);
466
495
  const filteredHeaders = filterInternalHeaders(rawRequest.headers);
467
496
  if (mwCtx !== null) filteredHeaders.set(VINEXT_MW_CTX_HEADER, mwCtx);
468
497
  const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParamsPayload);
469
498
  if (prerenderRouteParamsHeader !== null) filteredHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
470
- const request = cloneRequestWithHeaders(rawRequest, filteredHeaders);
499
+ let appRequest = rawRequest;
500
+ if (pagesDataNormalization?.isDataReq) {
501
+ const appRequestUrl = new URL(pagesDataNormalization.request.url);
502
+ appRequestUrl.pathname = addBasePathToPathname(appRequestUrl.pathname, options.basePath);
503
+ appRequest = cloneRequestWithUrl(pagesDataCandidate, appRequestUrl.toString());
504
+ }
505
+ const request = cloneRequestWithHeaders(appRequest, filteredHeaders);
506
+ const pagesDataRequest = pagesDataNormalization?.isDataReq ? cloneRequestWithHeaders(pagesDataCandidate, filteredHeaders) : null;
471
507
  const executionContext = isExecutionContextLike(ctx) ? ctx : getRequestExecutionContext() ?? null;
472
508
  return runWithRequestContext(createRequestContext({
473
509
  headersContext: headersContextFromRequest(request, { draftModeSecret: options.draftModeSecret }),
@@ -478,7 +514,7 @@ function createAppRscHandler(options) {
478
514
  const preMiddlewareRequestContext = requestContextFromRequest(request);
479
515
  let response;
480
516
  try {
481
- response = await handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest);
517
+ response = await handleAppRscRequest(options, request, preMiddlewareRequestContext, isDataRequest, pagesDataRequest);
482
518
  } catch (error) {
483
519
  if (process.env.NODE_ENV !== "production") flattenErrorCauses(error);
484
520
  throw error;
@@ -18,6 +18,7 @@ function appRscPathnameParts(pathname) {
18
18
  function createAppRscRouteMatcher(routes) {
19
19
  const routeTrie = buildRouteTrie(routes);
20
20
  const interceptLookup = createInterceptLookup(routes);
21
+ const routeIndexes = new Map(routes.map((route, index) => [route, index]));
21
22
  return {
22
23
  matchRoute(url) {
23
24
  return trieMatch(routeTrie, appRscPathnameParts(url));
@@ -26,16 +27,19 @@ function createAppRscRouteMatcher(routes) {
26
27
  if (sourcePathname === null) return null;
27
28
  const urlParts = appRscPathnameParts(pathname);
28
29
  const sourceParts = appRscPathnameParts(sourcePathname);
30
+ const matchedSourceRoute = trieMatch(routeTrie, sourceParts);
29
31
  for (const entry of interceptLookup) {
30
32
  if (!matchInterceptSource(sourceParts, entry)) continue;
31
33
  const params = matchAppRscRoutePattern(urlParts, entry.targetPatternParts);
32
34
  if (params === null) continue;
33
- const sourceRoute = routes[entry.sourceRouteIndex];
34
- const matchedSourceParams = sourceRoute ? matchAppRscRoutePattern(sourceParts, sourceRoute.patternParts) : null;
35
+ const concreteSourceRouteIndex = matchedSourceRoute && entry.sourceMatchPatternParts !== null ? routeIndexes.get(matchedSourceRoute.route) ?? entry.sourceRouteIndex : entry.sourceRouteIndex;
36
+ const sourceRoute = routes[concreteSourceRouteIndex];
37
+ const matchedSourceParams = matchedSourceRoute && entry.sourceMatchPatternParts !== null ? matchedSourceRoute.params : sourceRoute ? matchAppRscRoutePattern(sourceParts, sourceRoute.patternParts) : null;
35
38
  if (matchedSourceParams === null && entry.sourceMatchPatternParts === null) continue;
36
39
  const sourceParams = matchedSourceParams ?? createRouteParams();
37
40
  return {
38
41
  ...entry,
42
+ sourceRouteIndex: concreteSourceRouteIndex,
39
43
  matchedParams: mergeMatchedParams(sourceParams, params)
40
44
  };
41
45
  }
@@ -177,7 +177,7 @@ async function readActionFormDataWithLimit(request, maxBytes) {
177
177
  if (result.done) break;
178
178
  totalSize += result.value.byteLength;
179
179
  if (totalSize > maxBytes) {
180
- await reader.cancel();
180
+ reader.cancel();
181
181
  throw new Error("Request body too large");
182
182
  }
183
183
  chunks.push(result.value);
@@ -485,7 +485,10 @@ async function handleServerActionRscRequest(options) {
485
485
  if (options.request.method.toUpperCase() !== "POST" || !options.actionId) return null;
486
486
  const csrfResponse = validateCsrfOrigin(options.request, options.allowedOrigins);
487
487
  if (csrfResponse) return csrfResponse;
488
- if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) return renderFetchActionBodyExceededResponse(options);
488
+ if (parseInt(options.request.headers.get("content-length") || "0", 10) > options.maxActionBodySize) {
489
+ if (options.request.body) options.request.body.cancel().catch(() => {});
490
+ return renderFetchActionBodyExceededResponse(options);
491
+ }
489
492
  try {
490
493
  let action;
491
494
  if (options.contentType.startsWith("multipart/form-data")) {
@@ -21,6 +21,7 @@ import { createBfcacheSegmentStateKeyMap, createInitialBfcacheIdMap } from "./ap
21
21
  import { RSC_FORM_STATE_GLOBAL } from "./app-browser-hydration.js";
22
22
  import { createClientReferencePreloader } from "./app-client-reference-preloader.js";
23
23
  import { deferUntilStreamConsumed } from "./app-page-stream.js";
24
+ import { renderBeforeInteractiveInlineScripts } from "./before-interactive-head.js";
24
25
  import { createInitialDevServerErrorScript } from "./dev-initial-server-error.js";
25
26
  import { Fragment, createElement, use } from "react";
26
27
  import { renderToReadableStream, renderToStaticMarkup } from "react-dom/server.edge";
@@ -107,35 +108,6 @@ function renderInsertedHtml(insertedElements) {
107
108
  } catch {}
108
109
  return insertedHTML;
109
110
  }
110
- /**
111
- * Render captured `<Script strategy="beforeInteractive">` inline scripts to
112
- * HTML, ready to splice immediately after `<head ...>` opens. Each entry has
113
- * already had its inline content escaped via `escapeInlineContent(..., "script")`
114
- * inside the Script shim, so this function only quotes the attributes that
115
- * actually go on the tag (id, nonce, plus the residual passthroughs).
116
- *
117
- * Keeping this function colocated with the rest of the head-injection
118
- * helpers makes it obvious where the boundary is: anything passed through
119
- * here is being concatenated directly into HTML; treat the inputs
120
- * accordingly.
121
- */
122
- const VALID_ATTR_NAME = /^[a-zA-Z][\w.-]*$/;
123
- function renderBeforeInteractiveInlineScripts(scripts) {
124
- if (scripts.length === 0) return "";
125
- let html = "";
126
- for (const script of scripts) {
127
- let attrs = "";
128
- if (script.id) attrs += ` id="${escapeHtmlAttr(script.id)}"`;
129
- attrs += createNonceAttribute(script.nonce);
130
- if (script.attributes) for (const [key, value] of Object.entries(script.attributes)) {
131
- if (!VALID_ATTR_NAME.test(key)) continue;
132
- if (value === true) attrs += ` ${key}`;
133
- else if (typeof value === "string") attrs += ` ${key}="${escapeHtmlAttr(value)}"`;
134
- }
135
- html += `<script${attrs}>${script.innerHTML}<\/script>`;
136
- }
137
- return html;
138
- }
139
111
  function renderFontHtml(fontData, nonce, options = {}) {
140
112
  if (!fontData) return "";
141
113
  let fontHTML = "";
@@ -0,0 +1,17 @@
1
+ import { BeforeInteractiveInlineScript } from "../shims/before-interactive-context.js";
2
+
3
+ //#region src/server/before-interactive-head.d.ts
4
+ /**
5
+ * Render captured `<Script strategy="beforeInteractive">` scripts to HTML,
6
+ * ready to splice immediately after `<head ...>` opens. Each entry has already
7
+ * had its inline content escaped via `escapeInlineContent(..., "script")`
8
+ * inside the Script shim, so this function only quotes the attributes that
9
+ * actually go on the tag (id, src, nonce, plus the residual passthroughs).
10
+ *
11
+ * Keeping this function in its own module makes the boundary obvious: anything
12
+ * passed through here is being concatenated directly into HTML; treat the
13
+ * inputs accordingly.
14
+ */
15
+ declare function renderBeforeInteractiveInlineScripts(scripts: readonly BeforeInteractiveInlineScript[]): string;
16
+ //#endregion
17
+ export { renderBeforeInteractiveInlineScripts };
@@ -0,0 +1,35 @@
1
+ import { createNonceAttribute, escapeHtmlAttr } from "./html.js";
2
+ //#region src/server/before-interactive-head.ts
3
+ const VALID_ATTR_NAME = /^[a-zA-Z][\w.-]*$/;
4
+ /**
5
+ * Render captured `<Script strategy="beforeInteractive">` scripts to HTML,
6
+ * ready to splice immediately after `<head ...>` opens. Each entry has already
7
+ * had its inline content escaped via `escapeInlineContent(..., "script")`
8
+ * inside the Script shim, so this function only quotes the attributes that
9
+ * actually go on the tag (id, src, nonce, plus the residual passthroughs).
10
+ *
11
+ * Keeping this function in its own module makes the boundary obvious: anything
12
+ * passed through here is being concatenated directly into HTML; treat the
13
+ * inputs accordingly.
14
+ */
15
+ function renderBeforeInteractiveInlineScripts(scripts) {
16
+ if (scripts.length === 0) return "";
17
+ let html = "";
18
+ for (const script of scripts) {
19
+ let attrs = "";
20
+ if (script.id) attrs += ` id="${escapeHtmlAttr(script.id)}"`;
21
+ if (script.src) attrs += ` src="${escapeHtmlAttr(script.src)}"`;
22
+ attrs += createNonceAttribute(script.nonce);
23
+ if (script.attributes) for (const [key, value] of Object.entries(script.attributes)) {
24
+ if (!VALID_ATTR_NAME.test(key)) continue;
25
+ if (key === "data-nscript") continue;
26
+ if (value === true) attrs += ` ${key}`;
27
+ else if (typeof value === "string") attrs += ` ${key}="${escapeHtmlAttr(value)}"`;
28
+ }
29
+ attrs += ` data-nscript="beforeInteractive"`;
30
+ html += `<script${attrs}>${script.innerHTML ?? ""}<\/script>`;
31
+ }
32
+ return html;
33
+ }
34
+ //#endregion
35
+ export { renderBeforeInteractiveInlineScripts };
@@ -1,8 +1,5 @@
1
1
  //#region src/server/csp.ts
2
2
  const ESCAPE_REGEX = /[&><\u2028\u2029]/;
3
- function matchesDirectiveName(directive, name) {
4
- return directive === name || directive.startsWith(`${name} `);
5
- }
6
3
  function getNodeHeaderValue(headers, key) {
7
4
  const value = headers?.[key];
8
5
  if (Array.isArray(value)) return value.join(", ");
@@ -11,7 +8,7 @@ function getNodeHeaderValue(headers, key) {
11
8
  }
12
9
  function getScriptNonceFromHeader(cspHeaderValue) {
13
10
  const directives = cspHeaderValue.split(";").map((directive) => directive.trim());
14
- const directive = directives.find((value) => matchesDirectiveName(value, "script-src")) ?? directives.find((value) => matchesDirectiveName(value, "default-src"));
11
+ const directive = directives.find((value) => value.startsWith("script-src")) ?? directives.find((value) => value.startsWith("default-src"));
15
12
  if (!directive) return;
16
13
  const nonce = directive.split(" ").slice(1).map((source) => source.trim()).find((source) => source.startsWith("'nonce-") && source.length > 8 && source.endsWith("'"))?.slice(7, -1);
17
14
  if (!nonce) return;