vinext 1.0.0-beta.3 → 1.0.0-beta.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 (90) hide show
  1. package/dist/build/precompress.js +1 -1
  2. package/dist/build/prerender.d.ts +2 -1
  3. package/dist/build/prerender.js +12 -2
  4. package/dist/cli-args.d.ts +2 -1
  5. package/dist/cli-args.js +10 -0
  6. package/dist/cli.js +14 -8
  7. package/dist/client/navigation-runtime.d.ts +6 -0
  8. package/dist/client/navigation-runtime.js +1 -1
  9. package/dist/config/dotenv.d.ts +3 -4
  10. package/dist/config/dotenv.js +30 -22
  11. package/dist/config/next-config.d.ts +4 -2
  12. package/dist/config/next-config.js +3 -0
  13. package/dist/entries/app-browser-entry.js +1 -1
  14. package/dist/entries/app-rsc-entry.js +18 -3
  15. package/dist/entries/pages-server-entry.js +4 -1
  16. package/dist/index.js +11 -7
  17. package/dist/plugins/client-reference-dedup.js +7 -5
  18. package/dist/plugins/sass.js +3 -4
  19. package/dist/server/api-handler.js +4 -3
  20. package/dist/server/app-bfcache-identity.js +28 -2
  21. package/dist/server/app-browser-entry.js +73 -45
  22. package/dist/server/app-browser-navigation-controller.d.ts +1 -1
  23. package/dist/server/app-browser-navigation-controller.js +29 -4
  24. package/dist/server/app-browser-prefetch-response.d.ts +9 -0
  25. package/dist/server/app-browser-prefetch-response.js +32 -0
  26. package/dist/server/app-browser-state.d.ts +8 -3
  27. package/dist/server/app-browser-state.js +9 -3
  28. package/dist/server/app-page-dispatch.d.ts +1 -1
  29. package/dist/server/app-page-dispatch.js +6 -2
  30. package/dist/server/app-page-element-builder.d.ts +2 -1
  31. package/dist/server/app-page-element-builder.js +1 -0
  32. package/dist/server/app-page-render.d.ts +1 -1
  33. package/dist/server/app-page-render.js +13 -3
  34. package/dist/server/app-page-request.d.ts +0 -1
  35. package/dist/server/app-page-request.js +3 -12
  36. package/dist/server/app-page-response.d.ts +2 -0
  37. package/dist/server/app-page-response.js +6 -1
  38. package/dist/server/app-page-route-wiring.d.ts +3 -2
  39. package/dist/server/app-page-route-wiring.js +61 -13
  40. package/dist/server/app-pages-bridge.d.ts +2 -1
  41. package/dist/server/app-pages-bridge.js +2 -1
  42. package/dist/server/app-rsc-cache-busting.d.ts +7 -0
  43. package/dist/server/app-rsc-cache-busting.js +6 -0
  44. package/dist/server/app-rsc-handler.d.ts +2 -0
  45. package/dist/server/app-rsc-handler.js +9 -4
  46. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  47. package/dist/server/app-rsc-render-mode.js +4 -1
  48. package/dist/server/app-rsc-request-normalization.js +65 -4
  49. package/dist/server/app-server-action-execution.d.ts +1 -1
  50. package/dist/server/app-visited-response-cache.d.ts +6 -1
  51. package/dist/server/app-visited-response-cache.js +49 -1
  52. package/dist/server/dev-server.d.ts +1 -1
  53. package/dist/server/dev-server.js +61 -38
  54. package/dist/server/edge-api-runtime.d.ts +10 -1
  55. package/dist/server/edge-api-runtime.js +29 -1
  56. package/dist/server/headers.d.ts +3 -1
  57. package/dist/server/headers.js +3 -1
  58. package/dist/server/pages-api-route.d.ts +2 -0
  59. package/dist/server/pages-api-route.js +2 -2
  60. package/dist/server/pages-asset-tags.d.ts +1 -0
  61. package/dist/server/pages-asset-tags.js +6 -4
  62. package/dist/server/pages-document-asset-props.d.ts +26 -0
  63. package/dist/server/pages-document-asset-props.js +69 -0
  64. package/dist/server/pages-node-compat.js +3 -3
  65. package/dist/server/pages-page-handler.d.ts +1 -0
  66. package/dist/server/pages-page-handler.js +4 -1
  67. package/dist/server/pages-page-response.d.ts +2 -0
  68. package/dist/server/pages-page-response.js +19 -8
  69. package/dist/server/pages-router-entry.js +1 -1
  70. package/dist/server/prerender-manifest.d.ts +1 -0
  71. package/dist/server/prod-server.js +2 -1
  72. package/dist/server/seed-cache.js +1 -1
  73. package/dist/server/worker-revalidation-context.js +1 -0
  74. package/dist/shims/app-router-scroll-state.d.ts +2 -1
  75. package/dist/shims/app-router-scroll-state.js +7 -2
  76. package/dist/shims/document.js +15 -2
  77. package/dist/shims/hash-scroll.d.ts +1 -1
  78. package/dist/shims/hash-scroll.js +2 -1
  79. package/dist/shims/link.d.ts +2 -0
  80. package/dist/shims/link.js +59 -18
  81. package/dist/shims/metadata.d.ts +11 -17
  82. package/dist/shims/metadata.js +24 -32
  83. package/dist/shims/navigation.d.ts +18 -2
  84. package/dist/shims/navigation.js +69 -20
  85. package/dist/shims/request-context.d.ts +2 -1
  86. package/dist/shims/slot.d.ts +2 -1
  87. package/dist/shims/slot.js +6 -2
  88. package/dist/shims/streamed-icons.d.ts +9 -0
  89. package/dist/shims/streamed-icons.js +33 -0
  90. package/package.json +1 -1
@@ -1,3 +1,6 @@
1
+ import { AppElementsWire } from "./app-elements-wire.js";
2
+ import "./app-elements.js";
3
+ import { stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
1
4
  import { resolveCachedRscResponseExpiresAt } from "../shims/navigation.js";
2
5
  //#region src/server/app-visited-response-cache.ts
3
6
  const VISITED_RESPONSE_CACHE_TTL = 5 * 6e4;
@@ -17,5 +20,50 @@ function isVisitedResponseCacheEntryFresh(entry, options) {
17
20
  if (options.navigationKind === "traverse") return options.now - entry.createdAt < MAX_TRAVERSAL_CACHE_TTL;
18
21
  return entry.expiresAt > options.now;
19
22
  }
23
+ function normalizeVisitedResponseCacheLookupUrl(rscUrl) {
24
+ try {
25
+ const url = new URL(rscUrl, "http://vinext.local");
26
+ stripRscCacheBustingSearchParam(url);
27
+ return `${url.pathname}${url.search}`;
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ function parseVisitedResponseCacheKey(cacheKey) {
33
+ const separatorIndex = cacheKey.indexOf("\0");
34
+ if (separatorIndex === -1) return {
35
+ interceptionContext: null,
36
+ rscUrl: cacheKey
37
+ };
38
+ return {
39
+ interceptionContext: cacheKey.slice(separatorIndex + 1),
40
+ rscUrl: cacheKey.slice(0, separatorIndex)
41
+ };
42
+ }
43
+ function findVisitedResponseCacheEntry(cache, rscUrl, interceptionContext) {
44
+ const exactCacheKey = AppElementsWire.encodeCacheKey(rscUrl, interceptionContext);
45
+ const exactEntry = cache.get(exactCacheKey);
46
+ if (exactEntry) return {
47
+ cacheKey: exactCacheKey,
48
+ entry: exactEntry
49
+ };
50
+ const normalizedTarget = normalizeVisitedResponseCacheLookupUrl(rscUrl);
51
+ if (normalizedTarget === null) return null;
52
+ for (const [cacheKey, entry] of cache) {
53
+ const source = parseVisitedResponseCacheKey(cacheKey);
54
+ if (source.interceptionContext !== interceptionContext) continue;
55
+ if (normalizeVisitedResponseCacheLookupUrl(source.rscUrl) !== normalizedTarget) continue;
56
+ return {
57
+ cacheKey,
58
+ entry
59
+ };
60
+ }
61
+ return null;
62
+ }
63
+ function deleteVisitedResponseCacheEntry(cache, rscUrl, interceptionContext) {
64
+ const match = findVisitedResponseCacheEntry(cache, rscUrl, interceptionContext);
65
+ if (!match) return false;
66
+ return cache.delete(match.cacheKey);
67
+ }
20
68
  //#endregion
21
- export { MAX_TRAVERSAL_CACHE_TTL, VISITED_RESPONSE_CACHE_TTL, createVisitedResponseCacheEntry, isVisitedResponseCacheEntryFresh };
69
+ export { MAX_TRAVERSAL_CACHE_TTL, VISITED_RESPONSE_CACHE_TTL, createVisitedResponseCacheEntry, deleteVisitedResponseCacheEntry, findVisitedResponseCacheEntry, isVisitedResponseCacheEntryFresh };
@@ -55,7 +55,7 @@ clientTraceMetadata?: readonly string[], htmlLimitedBots?: string,
55
55
 
56
56
  reactStrictMode?: boolean, /** Next.js `expireTime`, used when formatting terminal GSP responses. */
57
57
 
58
- expireTime?: number): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
58
+ expireTime?: number, crossOrigin?: string): (req: IncomingMessage, res: ServerResponse, url: string, /** Status code override — propagated from middleware rewrite status. */
59
59
 
60
60
  statusCode?: number,
61
61
  /**
@@ -1,6 +1,5 @@
1
1
  import { closeAfterResponse, createRequestContext, runWithRequestContext } from "../shims/unified-request-context.js";
2
- import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
3
- import { createValidFileMatcher, findFileWithExtensions, findFileWithExts } from "../routing/file-matcher.js";
2
+ import { createValidFileMatcher, findFileWithExts } from "../routing/file-matcher.js";
4
3
  import { patternToNextFormat } from "../routing/route-validation.js";
5
4
  import { matchRoute } from "../routing/pages-router.js";
6
5
  import { PRERENDER_REVALIDATE_HEADER } from "../utils/protocol-headers.js";
@@ -16,6 +15,7 @@ import { mergeRouteParamsIntoQuery, parseQueryString } from "../utils/query.js";
16
15
  import "../shims/router-state.js";
17
16
  import { withScriptNonce } from "../shims/script-nonce-context.js";
18
17
  import { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr, safeJsonStringify } from "./html.js";
18
+ import { applyDocumentAssetProps, extractDocumentAssetProps, markDocumentAssetPropsProtectedTags, stripDocumentAssetPropsProtectionMarkers } from "./pages-document-asset-props.js";
19
19
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
20
20
  import { getScriptNonceFromNodeHeaderSources } from "./csp.js";
21
21
  import { logRequest, now } from "./request-log.js";
@@ -34,6 +34,7 @@ import { createPagesDevHydrationScript } from "./pages-dev-hydration.js";
34
34
  import { getManifestFilesForModule } from "./pages-asset-tags.js";
35
35
  import { PAGES_PREVIEW_CACHE_CONTROL, appendPagesPreviewClearCookies, getPagesPreviewState } from "./pages-preview.js";
36
36
  import { attachPagesRequestCookies } from "./pages-node-compat.js";
37
+ import { randomUUID } from "node:crypto";
37
38
  import React from "react";
38
39
  import { renderToReadableStream } from "react-dom/server.edge";
39
40
  //#region src/server/dev-server.ts
@@ -227,7 +228,7 @@ const STREAM_BODY_MARKER = "<!--VINEXT_STREAM_BODY-->";
227
228
  * shell sooner).
228
229
  */
229
230
  async function streamPageToResponse(res, element, options) {
230
- const { url, server, fontHeadHTML, assetHeadHTML = "", scripts, DocumentComponent, statusCode, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead, bufferBodyBeforeHeaders = false } = options;
231
+ const { url, server, fontHeadHTML, assetHeadHTML = "", scripts, DocumentComponent, statusCode, extraHeaders, getHeadHTML, enhancePageElement, scriptNonce, documentContext, setDocumentInitialHead, crossOrigin, bufferBodyBeforeHeaders = false } = options;
231
232
  const documentRenderPage = await runDocumentRenderPage({
232
233
  DocumentComponent,
233
234
  enhancePageElement,
@@ -250,26 +251,46 @@ async function streamPageToResponse(res, element, options) {
250
251
  let headHTML = getHeadHTML();
251
252
  if (documentRenderPage.status === "rendered" && documentRenderPage.stylesHTML) headHTML += `\n ${documentRenderPage.stylesHTML}`;
252
253
  let shellTemplate;
254
+ let documentAssetProps = {};
255
+ const protectedAssetMarker = `data-vinext-document-asset-props-protected-${randomUUID()}`;
256
+ const protectAssetTags = (html) => markDocumentAssetPropsProtectedTags(html, protectedAssetMarker);
257
+ const applyGeneratedAssetProps = (html, props, scriptOwner) => protectAssetTags(applyDocumentAssetProps(html, props, {
258
+ configuredCrossOrigin: crossOrigin,
259
+ scriptOwner
260
+ }));
253
261
  if (DocumentComponent) {
254
262
  const docProps = documentRenderPage.status === "skipped" ? await loadUserDocumentInitialProps(DocumentComponent) : documentRenderPage.docProps;
255
- let docHtml = await renderToStringAsync(docProps ? React.createElement(DocumentComponent, docProps) : React.createElement(DocumentComponent));
263
+ const renderedDocument = extractDocumentAssetProps(await renderToStringAsync(docProps ? React.createElement(DocumentComponent, docProps) : React.createElement(DocumentComponent)));
264
+ documentAssetProps = renderedDocument.props;
265
+ let docHtml = protectAssetTags(renderedDocument.html);
266
+ const generatedScripts = applyGeneratedAssetProps(scripts, renderedDocument.props, "next-script");
267
+ const generatedAssetHeadHTML = applyGeneratedAssetProps(assetHeadHTML, renderedDocument.props, "next-script");
256
268
  docHtml = docHtml.replace("__NEXT_MAIN__", STREAM_BODY_MARKER);
257
- if (headHTML || fontHeadHTML || assetHeadHTML) docHtml = docHtml.replace("</head>", ` ${fontHeadHTML}${headHTML}\n ${assetHeadHTML}\n</head>`);
258
- docHtml = docHtml.replace("<!-- __NEXT_SCRIPTS__ -->", scripts);
259
- if (!docHtml.includes("__NEXT_DATA__")) docHtml = docHtml.replace("</body>", ` ${scripts}\n</body>`);
269
+ if (headHTML || fontHeadHTML || generatedAssetHeadHTML) docHtml = docHtml.replace("</head>", ` ${protectAssetTags(fontHeadHTML)}${protectAssetTags(headHTML)}\n ${generatedAssetHeadHTML}\n</head>`);
270
+ docHtml = docHtml.replace("<!-- __NEXT_SCRIPTS__ -->", generatedScripts);
271
+ if (!docHtml.includes("__NEXT_DATA__")) docHtml = docHtml.replace("</body>", ` ${generatedScripts}\n</body>`);
260
272
  shellTemplate = docHtml;
261
- } else shellTemplate = `<!DOCTYPE html>
273
+ } else {
274
+ const generatedScripts = applyGeneratedAssetProps(scripts, {}, "next-script");
275
+ const generatedAssetHeadHTML = applyGeneratedAssetProps(assetHeadHTML, {}, "next-script");
276
+ shellTemplate = `<!DOCTYPE html>
262
277
  <html>
263
278
  <head>
264
- ${fontHeadHTML}${headHTML}
265
- ${assetHeadHTML}
279
+ ${protectAssetTags(fontHeadHTML)}${protectAssetTags(headHTML)}
280
+ ${generatedAssetHeadHTML}
266
281
  </head>
267
282
  <body>
268
283
  <div id="__next">${STREAM_BODY_MARKER}</div>
269
- ${scripts}
284
+ ${generatedScripts}
270
285
  </body>
271
286
  </html>`;
272
- const transformedShell = await server.transformIndexHtml(url, shellTemplate);
287
+ }
288
+ let transformedShell = await server.transformIndexHtml(url, shellTemplate);
289
+ transformedShell = stripDocumentAssetPropsProtectionMarkers(applyDocumentAssetProps(transformedShell, documentAssetProps, {
290
+ configuredCrossOrigin: crossOrigin,
291
+ protectedAssetMarker,
292
+ scriptOwner: "next-script"
293
+ }), protectedAssetMarker);
273
294
  const markerIdx = transformedShell.indexOf(STREAM_BODY_MARKER);
274
295
  const prefix = transformedShell.slice(0, markerIdx);
275
296
  const suffix = transformedShell.slice(markerIdx + 25);
@@ -330,7 +351,7 @@ function parseCookieLocale(req, i18nConfig) {
330
351
  * 4. Render the component to HTML
331
352
  * 5. Wrap in _document shell and send response
332
353
  */
333
- function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata, htmlLimitedBots, reactStrictMode = false, expireTime = PAGES_CACHE_ONE_YEAR_SECONDS) {
354
+ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatcher, basePath = "", trailingSlash = false, hasMiddleware = false, hasRewrites = false, clientTraceMetadata, htmlLimitedBots, reactStrictMode = false, expireTime = PAGES_CACHE_ONE_YEAR_SECONDS, crossOrigin) {
334
355
  const matcher = fileMatcher ?? createValidFileMatcher();
335
356
  const pagePatterns = routes.map((r) => patternToNextFormat(r.pattern));
336
357
  const _alsRegistration = Promise.all([runner.import("vinext/head-state"), runner.import("vinext/router-state")]);
@@ -372,7 +393,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
372
393
  basePath,
373
394
  locale: locale ?? currentDefaultLocale,
374
395
  locales: i18nConfig?.locales,
375
- defaultLocale: currentDefaultLocale
396
+ defaultLocale: currentDefaultLocale,
397
+ crossOrigin
376
398
  };
377
399
  const match = matchRoute(localeStrippedUrl, routes);
378
400
  if (!match) {
@@ -435,10 +457,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
435
457
  };
436
458
  const requestPreviewData = requestPreview.data;
437
459
  let AppComponent = null;
438
- const appPath = path.join(pagesDir, "_app");
439
- if (findFileWithExtensions(appPath, matcher)) try {
440
- AppComponent = (await importModule(runner, appPath)).default ?? null;
441
- } catch {}
460
+ const appFilePath = findFileWithExts(pagesDir, "_app", matcher);
461
+ if (appFilePath) AppComponent = (await importModule(runner, appFilePath)).default ?? null;
442
462
  const pagesNextData = {
443
463
  ...buildPagesReadinessNextData({
444
464
  pageModule,
@@ -754,7 +774,8 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
754
774
  if (typeof dynamicShim.flushPreloads === "function") await dynamicShim.flushPreloads();
755
775
  const nonceAttr = createNonceAttribute(scriptNonce);
756
776
  let fontHeadHTML = "";
757
- const assetHeadHTML = await collectDevInitialStylesheetHeadHTML(server, runner, [AppComponent ? findFileWithExts(pagesDir, "_app", matcher) : null, route.filePath], nonceAttr);
777
+ const appAssetPath = AppComponent ? appFilePath : null;
778
+ const assetHeadHTML = await collectDevInitialStylesheetHeadHTML(server, runner, [appAssetPath, route.filePath], nonceAttr);
758
779
  const allFontStyles = [];
759
780
  const allFontPreloads = [];
760
781
  try {
@@ -777,15 +798,15 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
777
798
  for (const { href, type } of allFontPreloads) {
778
799
  const safeHref = href.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
779
800
  const safeType = type.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
780
- fontHeadHTML += `<link rel="preload"${nonceAttr} href="${safeHref}" as="font" type="${safeType}" crossorigin />\n `;
801
+ fontHeadHTML += `<link rel="preload"${nonceAttr} href="${safeHref}" as="font" type="${safeType}" crossorigin="anonymous" />\n `;
781
802
  }
782
803
  if (allFontStyles.length > 0) fontHeadHTML += `<style data-vinext-fonts${nonceAttr}>${allFontStyles.join("\n")}</style>\n `;
783
804
  const viteRoot = server.config.root;
784
805
  const viteBase = server.config.base;
785
806
  const pageModuleUrl = createPagesDevModuleUrl(viteRoot, route.filePath, viteBase);
786
807
  const pageModuleSource = createPagesDevModuleUrl(viteRoot, route.filePath, "/");
787
- const appModuleUrl = AppComponent ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), viteBase) : null;
788
- const appModuleSource = AppComponent ? createPagesDevModuleUrl(viteRoot, path.join(pagesDir, "_app"), "/") : null;
808
+ const appModuleUrl = appAssetPath ? createPagesDevModuleUrl(viteRoot, appAssetPath, viteBase) : null;
809
+ const appModuleSource = appAssetPath ? createPagesDevModuleUrl(viteRoot, appAssetPath, "/") : null;
789
810
  const serializedPagesNextData = {
790
811
  ...pagesNextData,
791
812
  __vinext: {
@@ -815,11 +836,9 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
815
836
  domainLocales,
816
837
  ...serializedPagesNextData
817
838
  })}<\/script>`;
818
- const docPath = path.join(pagesDir, "_document");
839
+ const docFilePath = findFileWithExts(pagesDir, "_document", matcher);
819
840
  let DocumentComponent = null;
820
- if (findFileWithExtensions(docPath, matcher)) try {
821
- DocumentComponent = (await runner.import(docPath)).default ?? null;
822
- } catch {}
841
+ if (docFilePath) DocumentComponent = (await runner.import(docFilePath)).default ?? null;
823
842
  const allScripts = `${nextDataScript}\n ${createInlineScriptTag(`window.__VINEXT_PAGE_PATTERNS__=${safeJsonStringify(pagePatterns)}`, scriptNonce)}\n ${hydrationScript}`;
824
843
  const extraHeaders = { ...gsspExtraHeaders };
825
844
  if (typeof pageModule.getStaticProps === "function") {
@@ -868,6 +887,7 @@ function createSSRHandler(server, runner, routes, pagesDir, i18nConfig, fileMatc
868
887
  return traceHTML ? `${headHTML}\n ${traceHTML}` : headHTML;
869
888
  },
870
889
  setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0,
890
+ crossOrigin,
871
891
  bufferBodyBeforeHeaders: true
872
892
  });
873
893
  _renderEnd = now();
@@ -917,11 +937,8 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
917
937
  const ErrorComponent = errorModule.default;
918
938
  if (!ErrorComponent) continue;
919
939
  let AppComponent = null;
920
- const appPathErr = path.join(pagesDir, "_app");
921
940
  const appAssetPath = findFileWithExts(pagesDir, "_app", matcher);
922
- if (findFileWithExtensions(appPathErr, matcher)) try {
923
- AppComponent = (await importModule(runner, appAssetPath ?? appPathErr)).default ?? null;
924
- } catch {}
941
+ if (appAssetPath) AppComponent = (await importModule(runner, appAssetPath)).default ?? null;
925
942
  const createElement = React.createElement;
926
943
  res.statusCode = statusCode;
927
944
  const errorPage = candidate === "_error" ? "/_error" : `/${candidate}`;
@@ -1008,10 +1025,8 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1008
1025
  } : appRenderProps;
1009
1026
  } else renderProps = { pageProps: errorProps };
1010
1027
  let DocumentComponent = null;
1011
- const docPathErr = path.join(pagesDir, "_document");
1012
- if (findFileWithExtensions(docPathErr, matcher)) try {
1013
- DocumentComponent = (await importModule(runner, docPathErr)).default ?? null;
1014
- } catch {}
1028
+ const docFilePathErr = findFileWithExts(pagesDir, "_document", matcher);
1029
+ if (docFilePathErr) DocumentComponent = (await importModule(runner, docFilePathErr)).default ?? null;
1015
1030
  const createErrorElement = (FinalApp, FinalComponent) => {
1016
1031
  let errorElement = FinalApp ? createElement(FinalApp, {
1017
1032
  ...renderProps,
@@ -1069,9 +1084,14 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1069
1084
  return createErrorElement(FinalApp, FinalComponent);
1070
1085
  },
1071
1086
  getHeadHTML: () => typeof headShim.getSSRHeadHTML === "function" ? headShim.getSSRHeadHTML() : "",
1072
- setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0
1087
+ setDocumentInitialHead: typeof headShim.setDocumentInitialHead === "function" ? headShim.setDocumentInitialHead : void 0,
1088
+ crossOrigin: context.crossOrigin
1073
1089
  });
1074
1090
  else {
1091
+ const bodyHtml = await renderToStringAsync(element);
1092
+ const protectedAssetMarker = `data-vinext-document-asset-props-protected-${randomUUID()}`;
1093
+ const protectAssetTags = (assetHtml) => markDocumentAssetPropsProtectedTags(assetHtml, protectedAssetMarker);
1094
+ const protectedErrorScripts = protectAssetTags(applyDocumentAssetProps(errorScripts, {}, { configuredCrossOrigin: context.crossOrigin }));
1075
1095
  const html = `<!DOCTYPE html>
1076
1096
  <html>
1077
1097
  <head>
@@ -1080,11 +1100,14 @@ async function renderErrorPage(server, runner, req, res, url, pagesDir, statusCo
1080
1100
  ${assetHeadHTML}
1081
1101
  </head>
1082
1102
  <body>
1083
- <div id="__next">${await renderToStringAsync(element)}</div>
1084
- ${errorScripts}
1103
+ <div id="__next">${protectAssetTags(bodyHtml)}</div>
1104
+ ${protectedErrorScripts}
1085
1105
  </body>
1086
1106
  </html>`;
1087
- const transformedHtml = await server.transformIndexHtml(url, html);
1107
+ const transformedHtml = stripDocumentAssetPropsProtectionMarkers(applyDocumentAssetProps(await server.transformIndexHtml(url, html), {}, {
1108
+ configuredCrossOrigin: context.crossOrigin,
1109
+ protectedAssetMarker
1110
+ }), protectedAssetMarker);
1088
1111
  res.writeHead(statusCode, { "Content-Type": "text/html; charset=utf-8" });
1089
1112
  res.end(transformedHtml);
1090
1113
  }
@@ -1,4 +1,13 @@
1
1
  //#region src/server/edge-api-runtime.d.ts
2
2
  declare function isEdgeApiRuntime(runtime: string | undefined): boolean;
3
+ type EdgeApiExecutionRuntime = "node" | "worker";
4
+ /**
5
+ * Remove wire-level body headers after Node's fetch implementation has decoded
6
+ * an Edge API response. Next.js applies the same boundary in its edge sandbox.
7
+ *
8
+ * Workers keep the original response untouched: workerd preserves the encoded
9
+ * body/header pair and can stream it directly to the client.
10
+ */
11
+ declare function finalizeEdgeApiResponse(response: Response, runtime: EdgeApiExecutionRuntime): Response;
3
12
  //#endregion
4
- export { isEdgeApiRuntime };
13
+ export { EdgeApiExecutionRuntime, finalizeEdgeApiResponse, isEdgeApiRuntime };
@@ -2,5 +2,33 @@
2
2
  function isEdgeApiRuntime(runtime) {
3
3
  return runtime === "edge" || runtime === "experimental-edge";
4
4
  }
5
+ const NODE_EDGE_RESPONSE_FORBIDDEN_HEADERS = [
6
+ "content-length",
7
+ "content-encoding",
8
+ "transfer-encoding"
9
+ ];
10
+ /**
11
+ * Remove wire-level body headers after Node's fetch implementation has decoded
12
+ * an Edge API response. Next.js applies the same boundary in its edge sandbox.
13
+ *
14
+ * Workers keep the original response untouched: workerd preserves the encoded
15
+ * body/header pair and can stream it directly to the client.
16
+ */
17
+ function finalizeEdgeApiResponse(response, runtime) {
18
+ if (runtime === "worker") return response;
19
+ const headers = new Headers(response.headers);
20
+ let changed = false;
21
+ for (const name of NODE_EDGE_RESPONSE_FORBIDDEN_HEADERS) {
22
+ if (!headers.has(name)) continue;
23
+ headers.delete(name);
24
+ changed = true;
25
+ }
26
+ if (!changed) return response;
27
+ return new Response(response.body, {
28
+ headers,
29
+ status: response.status,
30
+ statusText: response.statusText
31
+ });
32
+ }
5
33
  //#endregion
6
- export { isEdgeApiRuntime };
34
+ export { finalizeEdgeApiResponse, isEdgeApiRuntime };
@@ -15,6 +15,8 @@ import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP
15
15
  declare const VINEXT_CACHE_HEADER = "X-Vinext-Cache";
16
16
  /** Next.js public ISR / page cache state indicator. */
17
17
  declare const NEXTJS_CACHE_HEADER = "x-nextjs-cache";
18
+ /** Next.js cache-tag metadata carried by prerendered App Router responses. */
19
+ declare const NEXT_CACHE_TAGS_HEADER = "x-next-cache-tags";
18
20
  /** Static file signal — value is URL-encoded pathname. */
19
21
  declare const VINEXT_STATIC_FILE_HEADER = "x-vinext-static-file";
20
22
  /** Timing metrics: `handlerStart,compileMs,renderMs`. */
@@ -107,4 +109,4 @@ declare const INTERNAL_HEADERS: string[];
107
109
  /** Vinext-only internal headers stripped alongside Next.js protocol internals. */
108
110
  declare const VINEXT_INTERNAL_HEADERS: string[];
109
111
  //#endregion
110
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
112
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -14,6 +14,8 @@ import { MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_OVERRIDE_HEADERS, MIDDLEWARE_SET_C
14
14
  const VINEXT_CACHE_HEADER = "X-Vinext-Cache";
15
15
  /** Next.js public ISR / page cache state indicator. */
16
16
  const NEXTJS_CACHE_HEADER = "x-nextjs-cache";
17
+ /** Next.js cache-tag metadata carried by prerendered App Router responses. */
18
+ const NEXT_CACHE_TAGS_HEADER = "x-next-cache-tags";
17
19
  /** Static file signal — value is URL-encoded pathname. */
18
20
  const VINEXT_STATIC_FILE_HEADER = "x-vinext-static-file";
19
21
  /** Timing metrics: `handlerStart,compileMs,renderMs`. */
@@ -131,4 +133,4 @@ const VINEXT_INTERNAL_HEADERS = [
131
133
  VINEXT_REVALIDATE_HOST_HEADER
132
134
  ];
133
135
  //#endregion
134
- export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
136
+ export { ACTION_FORWARDED_HEADER, ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER, ACTION_REDIRECT_TYPE_HEADER, ACTION_REVALIDATED_HEADER, FLIGHT_HEADERS, INTERNAL_HEADERS, MIDDLEWARE_HEADER_PREFIX, MIDDLEWARE_NEXT_HEADER, MIDDLEWARE_REWRITE_HEADER, MIDDLEWARE_SET_COOKIE_HEADER, MIDDLEWARE_SKIP_HEADER, NEXTJS_ACTION_NOT_FOUND_HEADER, NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER, NEXT_ACTION_HEADER, NEXT_CACHE_TAGS_HEADER, NEXT_HTML_REQUEST_ID_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_URL_HEADER, RSC_ACTION_HEADER, RSC_HEADER, VINEXT_CACHE_HEADER, VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_DYNAMIC_STALE_TIME_HEADER, VINEXT_INTERCEPTION_CONTEXT_HEADER, VINEXT_INTERNAL_HEADERS, VINEXT_MOUNTED_SLOTS_HEADER, VINEXT_MW_CTX_HEADER, VINEXT_PARAMS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_PAGES_STATIC_PATHS_PATH, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER, VINEXT_PRERENDER_STATIC_PARAMS_PATH, VINEXT_RENDERED_PATH_AND_SEARCH_HEADER, VINEXT_REVALIDATE_HEADER, VINEXT_REVALIDATE_HOST_HEADER, VINEXT_RSC_MARKER_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER, VINEXT_RSC_RENDER_MODE_HEADER, VINEXT_STATIC_FILE_HEADER, VINEXT_TIMING_HEADER };
@@ -1,6 +1,7 @@
1
1
  import { NextI18nConfig } from "../config/next-config.js";
2
2
  import { Route } from "../routing/pages-router.js";
3
3
  import { ExecutionContextLike } from "../shims/request-context.js";
4
+ import { EdgeApiExecutionRuntime } from "./edge-api-runtime.js";
4
5
  import { PagesReqResRequest, PagesReqResResponse, PagesRequestQuery } from "./pages-node-compat.js";
5
6
 
6
7
  //#region src/server/pages-api-route.d.ts
@@ -68,6 +69,7 @@ type HandlePagesApiRouteOptions = {
68
69
  * response. Omit on Node.js dev where no Workers lifecycle exists.
69
70
  */
70
71
  ctx?: ExecutionContextLike;
72
+ edgeRuntime?: EdgeApiExecutionRuntime;
71
73
  match: PagesApiRouteMatch | null;
72
74
  reportRequestError?: (error: Error, routePattern: string) => void | Promise<void>;
73
75
  request: Request;
@@ -7,7 +7,7 @@ import { mergeRouteParamsIntoQuery, parseQueryString, urlQueryToSearchParams } f
7
7
  import { resolveBodyParserConfig } from "./pages-body-parser-config.js";
8
8
  import { PagesBodyParseError } from "./pages-media-type.js";
9
9
  import { createPagesReqRes, parsePagesApiBody } from "./pages-node-compat.js";
10
- import { isEdgeApiRuntime } from "./edge-api-runtime.js";
10
+ import { finalizeEdgeApiResponse, isEdgeApiRuntime } from "./edge-api-runtime.js";
11
11
  //#region src/server/pages-api-route.ts
12
12
  function resolveModuleRuntime(module) {
13
13
  return module.runtime ?? module.config?.runtime;
@@ -42,7 +42,7 @@ async function _handlePagesApiRoute(options) {
42
42
  trailingSlash: options.nextConfig.trailingSlash
43
43
  } } : void 0);
44
44
  const response = await route.module.default(nextRequest);
45
- if (response instanceof Response) return response;
45
+ if (response instanceof Response) return finalizeEdgeApiResponse(response, options.edgeRuntime ?? "worker");
46
46
  throw new Error("Edge API route did not return a Response");
47
47
  }
48
48
  if (!isNodeApiRouteModule(route.module)) return new Response("API route does not export a default function", { status: 500 });
@@ -57,6 +57,7 @@ type CollectAssetTagsOptions = {
57
57
  basePath?: string;
58
58
  assetPrefix?: string;
59
59
  deploymentId?: string;
60
+ crossOrigin?: string;
60
61
  };
61
62
  /**
62
63
  * Build the HTML `<link>` and `<script>` tag string for the SSR response.
@@ -70,6 +70,8 @@ function collectAssetTags(options) {
70
70
  const seen = /* @__PURE__ */ new Set();
71
71
  const nonceAttr = createNonceAttribute(options.scriptNonce);
72
72
  const deferAttr = options.disableOptimizedLoading ? "" : " defer";
73
+ const scriptCrossOriginAttr = options.crossOrigin ? ` crossorigin="${options.crossOrigin}"` : " crossorigin";
74
+ const preloadCrossOriginAttr = options.crossOrigin ? ` crossorigin="${options.crossOrigin}"` : "";
73
75
  const basePath = options.basePath ?? "";
74
76
  const assetPrefix = options.assetPrefix ?? "";
75
77
  const href = (value) => {
@@ -82,8 +84,8 @@ function collectAssetTags(options) {
82
84
  const clientEntry = runtimeAssets.clientEntry;
83
85
  if (clientEntry) {
84
86
  seen.add(clientEntry);
85
- tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"" + href(clientEntry) + "\" />");
86
- tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"" + href(clientEntry) + "\" crossorigin><\/script>");
87
+ tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"" + href(clientEntry) + "\"" + preloadCrossOriginAttr + " />");
88
+ tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"" + href(clientEntry) + "\"" + scriptCrossOriginAttr + "><\/script>");
87
89
  }
88
90
  if (m) {
89
91
  const allFiles = [];
@@ -115,8 +117,8 @@ function collectAssetTags(options) {
115
117
  if (tf.endsWith(".css")) tags.push("<link rel=\"stylesheet\"" + nonceAttr + " href=\"" + href(tf) + "\" />");
116
118
  else if (tf.endsWith(".js")) {
117
119
  if (lazySet && lazySet.has(tf)) continue;
118
- tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"" + href(tf) + "\" />");
119
- tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"" + href(tf) + "\" crossorigin><\/script>");
120
+ tags.push("<link rel=\"modulepreload\"" + nonceAttr + " href=\"" + href(tf) + "\"" + preloadCrossOriginAttr + " />");
121
+ tags.push("<script type=\"module\"" + deferAttr + nonceAttr + " src=\"" + href(tf) + "\"" + scriptCrossOriginAttr + "><\/script>");
120
122
  }
121
123
  }
122
124
  }
@@ -0,0 +1,26 @@
1
+ //#region src/server/pages-document-asset-props.d.ts
2
+ type DocumentAssetProps = {
3
+ headNonce?: string;
4
+ headCrossOrigin?: string;
5
+ scriptNonce?: string;
6
+ scriptCrossOrigin?: string;
7
+ };
8
+ type ApplyDocumentAssetPropsOptions = {
9
+ configuredCrossOrigin?: string;
10
+ protectedAssetMarker?: string;
11
+ scriptOwner?: "head" | "next-script";
12
+ };
13
+ declare function extractDocumentAssetProps(html: string): {
14
+ html: string;
15
+ props: DocumentAssetProps;
16
+ };
17
+ /**
18
+ * Protect asset tags whose attributes already have a more specific owner while
19
+ * Vite runs its HTML transforms. Tags injected by Vite after this marker pass
20
+ * remain unmarked, so Document/config props can still be applied to them.
21
+ */
22
+ declare function markDocumentAssetPropsProtectedTags(html: string, markerAttribute: string): string;
23
+ declare function stripDocumentAssetPropsProtectionMarkers(html: string, markerAttribute: string): string;
24
+ declare function applyDocumentAssetProps(html: string, props: DocumentAssetProps, options?: ApplyDocumentAssetPropsOptions): string;
25
+ //#endregion
26
+ export { DocumentAssetProps, applyDocumentAssetProps, extractDocumentAssetProps, markDocumentAssetPropsProtectedTags, stripDocumentAssetPropsProtectionMarkers };
@@ -0,0 +1,69 @@
1
+ import { escapeHtmlAttr } from "./html.js";
2
+ //#region src/server/pages-document-asset-props.ts
3
+ const HEAD_NONCE_ATTR = "data-vinext-head-nonce";
4
+ const HEAD_CROSS_ORIGIN_ATTR = "data-vinext-head-cross-origin";
5
+ const SCRIPT_NONCE_ATTR = "data-vinext-script-nonce";
6
+ const SCRIPT_CROSS_ORIGIN_ATTR = "data-vinext-script-cross-origin";
7
+ function readAttribute(tag, name) {
8
+ if (!tag) return void 0;
9
+ return tag.match(new RegExp(`\\s${name}="([^"]*)"`))?.[1];
10
+ }
11
+ function removeAttributes(tag, names) {
12
+ return tag.replace(new RegExp(`\\s(?:${names.join("|")})="[^"]*"`, "g"), "");
13
+ }
14
+ function extractDocumentAssetProps(html) {
15
+ const headTag = html.match(/<head\b[^>]*>/i)?.[0];
16
+ const nextScriptTag = html.match(/<span\b[^>]*>(?=<!-- __NEXT_SCRIPTS__ -->)/i)?.[0];
17
+ const props = {
18
+ headNonce: readAttribute(headTag, HEAD_NONCE_ATTR),
19
+ headCrossOrigin: readAttribute(headTag, HEAD_CROSS_ORIGIN_ATTR),
20
+ scriptNonce: readAttribute(nextScriptTag, SCRIPT_NONCE_ATTR),
21
+ scriptCrossOrigin: readAttribute(nextScriptTag, SCRIPT_CROSS_ORIGIN_ATTR)
22
+ };
23
+ return {
24
+ html: html.replace(/<head\b[^>]*>/i, (tag) => removeAttributes(tag, [HEAD_NONCE_ATTR, HEAD_CROSS_ORIGIN_ATTR])).replace(/<span\b[^>]*>(?=<!-- __NEXT_SCRIPTS__ -->)/i, (tag) => removeAttributes(tag, [SCRIPT_NONCE_ATTR, SCRIPT_CROSS_ORIGIN_ATTR])),
25
+ props
26
+ };
27
+ }
28
+ function addAttribute(tag, name, value, replaceExisting = false) {
29
+ if (value === void 0) return tag;
30
+ const attributePattern = new RegExp(`\\s${name}(?:="[^"]*")?`, "i");
31
+ if (attributePattern.test(tag)) return replaceExisting ? tag.replace(attributePattern, ` ${name}="${escapeHtmlAttr(value)}"`) : tag;
32
+ if (!tag.endsWith(">")) return tag;
33
+ const selfClosing = tag.endsWith("/>");
34
+ const closingStart = selfClosing ? tag.length - 2 : tag.length - 1;
35
+ const opening = tag.slice(0, closingStart).trimEnd();
36
+ const closing = selfClosing ? " />" : ">";
37
+ return `${opening} ${name}="${escapeHtmlAttr(value)}"${closing}`;
38
+ }
39
+ function hasAttribute(tag, name) {
40
+ return name !== void 0 && new RegExp(`\\s${name}(?:="[^"]*")?`, "i").test(tag);
41
+ }
42
+ /**
43
+ * Protect asset tags whose attributes already have a more specific owner while
44
+ * Vite runs its HTML transforms. Tags injected by Vite after this marker pass
45
+ * remain unmarked, so Document/config props can still be applied to them.
46
+ */
47
+ function markDocumentAssetPropsProtectedTags(html, markerAttribute) {
48
+ return html.replace(/<script\b[^>]*>/gi, (tag) => addAttribute(tag, markerAttribute, "")).replace(/<link\b[^>]*\brel="(?:preload|modulepreload)"[^>]*>/gi, (tag) => addAttribute(tag, markerAttribute, ""));
49
+ }
50
+ function stripDocumentAssetPropsProtectionMarkers(html, markerAttribute) {
51
+ const stripMarker = (tag) => removeAttributes(tag, [markerAttribute]);
52
+ return html.replace(/<script\b[^>]*>/gi, stripMarker).replace(/<link\b[^>]*\brel="(?:preload|modulepreload)"[^>]*>/gi, stripMarker);
53
+ }
54
+ function applyDocumentAssetProps(html, props, options = {}) {
55
+ const scriptOwner = options.scriptOwner ?? "next-script";
56
+ const scriptNonce = scriptOwner === "head" ? props.headNonce : props.scriptNonce;
57
+ const preloadNonce = props.headNonce;
58
+ const scriptCrossOrigin = (scriptOwner === "head" ? props.headCrossOrigin : props.scriptCrossOrigin) ?? options.configuredCrossOrigin;
59
+ const preloadCrossOrigin = props.headCrossOrigin ?? options.configuredCrossOrigin;
60
+ return html.replace(/<script\b[^>]*>/gi, (tag) => {
61
+ if (hasAttribute(tag, options.protectedAssetMarker)) return tag;
62
+ return addAttribute(addAttribute(tag, "nonce", scriptNonce, true), "crossorigin", scriptCrossOrigin, true);
63
+ }).replace(/<link\b[^>]*\brel="(?:preload|modulepreload)"[^>]*>/gi, (tag) => {
64
+ if (hasAttribute(tag, options.protectedAssetMarker)) return tag;
65
+ return addAttribute(addAttribute(tag, "nonce", preloadNonce, true), "crossorigin", preloadCrossOrigin, true);
66
+ });
67
+ }
68
+ //#endregion
69
+ export { applyDocumentAssetProps, extractDocumentAssetProps, markDocumentAssetPropsProtectedTags, stripDocumentAssetPropsProtectionMarkers };
@@ -155,7 +155,7 @@ var PagesResponseStream = class extends Writable {
155
155
  }
156
156
  writeHead(code, headers) {
157
157
  this.resStatusCode = code;
158
- if (headers) for (const [key, value] of Object.entries(headers)) this.setHeaderValue(key, value, { replaceSetCookie: false });
158
+ if (headers) for (const [key, value] of Object.entries(headers)) this.setHeaderValue(key, value, { replaceSetCookie: true });
159
159
  return this;
160
160
  }
161
161
  setHeader(name, value) {
@@ -251,9 +251,9 @@ var PagesResponseStream = class extends Writable {
251
251
  }
252
252
  setHeaderValue(name, value, options) {
253
253
  if (name.toLowerCase() === "set-cookie") {
254
+ const values = Array.isArray(value) ? value.map(String) : [String(value)];
254
255
  if (options.replaceSetCookie) this.setCookieHeaders.length = 0;
255
- if (Array.isArray(value)) this.setCookieHeaders.push(...value.map(String));
256
- else this.setCookieHeaders.push(String(value));
256
+ this.setCookieHeaders.push(...values);
257
257
  return;
258
258
  }
259
259
  this.resHeaders[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
@@ -35,6 +35,7 @@ type VinextConfigSubset = {
35
35
  htmlLimitedBots?: string;
36
36
  clientTraceMetadata?: readonly string[];
37
37
  disableOptimizedLoading: boolean;
38
+ crossOrigin?: string;
38
39
  };
39
40
  declare function shouldEmitPagesClientTraceMetadata(pageModule: PagesPageModule, appComponent: unknown): boolean;
40
41
  /**
@@ -452,7 +452,8 @@ function createPagesPageHandler(opts) {
452
452
  disableOptimizedLoading: vinextConfig.disableOptimizedLoading,
453
453
  basePath: vinextConfig.basePath,
454
454
  assetPrefix: vinextConfig.assetPrefix,
455
- deploymentId: process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID
455
+ deploymentId: process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID,
456
+ crossOrigin: vinextConfig.crossOrigin
456
457
  }),
457
458
  buildId,
458
459
  clearSsrContext() {
@@ -500,6 +501,8 @@ function createPagesPageHandler(opts) {
500
501
  routeUrl: renderRouteUrl,
501
502
  safeJsonStringify,
502
503
  scriptNonce,
504
+ crossOrigin: vinextConfig.crossOrigin,
505
+ disableOptimizedLoading: vinextConfig.disableOptimizedLoading,
503
506
  statusCode: renderStatusCode,
504
507
  nextData: serializedPagesNextData,
505
508
  userAgent: request.headers.get("user-agent") ?? void 0,
@@ -114,6 +114,8 @@ type RenderPagesPageResponseOptions = {
114
114
  routeUrl: string;
115
115
  safeJsonStringify: (value: unknown) => string;
116
116
  scriptNonce?: string;
117
+ crossOrigin?: string;
118
+ disableOptimizedLoading: boolean;
117
119
  statusCode?: number;
118
120
  vinext?: VinextNextData["__vinext"];
119
121
  nextData?: PagesNextDataExtras;