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.
- package/dist/build/precompress.js +1 -1
- package/dist/build/prerender.d.ts +2 -1
- package/dist/build/prerender.js +12 -2
- package/dist/cli-args.d.ts +2 -1
- package/dist/cli-args.js +10 -0
- package/dist/cli.js +14 -8
- package/dist/client/navigation-runtime.d.ts +6 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/config/dotenv.d.ts +3 -4
- package/dist/config/dotenv.js +30 -22
- package/dist/config/next-config.d.ts +4 -2
- package/dist/config/next-config.js +3 -0
- package/dist/entries/app-browser-entry.js +1 -1
- package/dist/entries/app-rsc-entry.js +18 -3
- package/dist/entries/pages-server-entry.js +4 -1
- package/dist/index.js +11 -7
- package/dist/plugins/client-reference-dedup.js +7 -5
- package/dist/plugins/sass.js +3 -4
- package/dist/server/api-handler.js +4 -3
- package/dist/server/app-bfcache-identity.js +28 -2
- package/dist/server/app-browser-entry.js +73 -45
- package/dist/server/app-browser-navigation-controller.d.ts +1 -1
- package/dist/server/app-browser-navigation-controller.js +29 -4
- package/dist/server/app-browser-prefetch-response.d.ts +9 -0
- package/dist/server/app-browser-prefetch-response.js +32 -0
- package/dist/server/app-browser-state.d.ts +8 -3
- package/dist/server/app-browser-state.js +9 -3
- package/dist/server/app-page-dispatch.d.ts +1 -1
- package/dist/server/app-page-dispatch.js +6 -2
- package/dist/server/app-page-element-builder.d.ts +2 -1
- package/dist/server/app-page-element-builder.js +1 -0
- package/dist/server/app-page-render.d.ts +1 -1
- package/dist/server/app-page-render.js +13 -3
- package/dist/server/app-page-request.d.ts +0 -1
- package/dist/server/app-page-request.js +3 -12
- package/dist/server/app-page-response.d.ts +2 -0
- package/dist/server/app-page-response.js +6 -1
- package/dist/server/app-page-route-wiring.d.ts +3 -2
- package/dist/server/app-page-route-wiring.js +61 -13
- package/dist/server/app-pages-bridge.d.ts +2 -1
- package/dist/server/app-pages-bridge.js +2 -1
- package/dist/server/app-rsc-cache-busting.d.ts +7 -0
- package/dist/server/app-rsc-cache-busting.js +6 -0
- package/dist/server/app-rsc-handler.d.ts +2 -0
- package/dist/server/app-rsc-handler.js +9 -4
- package/dist/server/app-rsc-render-mode.d.ts +3 -2
- package/dist/server/app-rsc-render-mode.js +4 -1
- package/dist/server/app-rsc-request-normalization.js +65 -4
- package/dist/server/app-server-action-execution.d.ts +1 -1
- package/dist/server/app-visited-response-cache.d.ts +6 -1
- package/dist/server/app-visited-response-cache.js +49 -1
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +61 -38
- package/dist/server/edge-api-runtime.d.ts +10 -1
- package/dist/server/edge-api-runtime.js +29 -1
- package/dist/server/headers.d.ts +3 -1
- package/dist/server/headers.js +3 -1
- package/dist/server/pages-api-route.d.ts +2 -0
- package/dist/server/pages-api-route.js +2 -2
- package/dist/server/pages-asset-tags.d.ts +1 -0
- package/dist/server/pages-asset-tags.js +6 -4
- package/dist/server/pages-document-asset-props.d.ts +26 -0
- package/dist/server/pages-document-asset-props.js +69 -0
- package/dist/server/pages-node-compat.js +3 -3
- package/dist/server/pages-page-handler.d.ts +1 -0
- package/dist/server/pages-page-handler.js +4 -1
- package/dist/server/pages-page-response.d.ts +2 -0
- package/dist/server/pages-page-response.js +19 -8
- package/dist/server/pages-router-entry.js +1 -1
- package/dist/server/prerender-manifest.d.ts +1 -0
- package/dist/server/prod-server.js +2 -1
- package/dist/server/seed-cache.js +1 -1
- package/dist/server/worker-revalidation-context.js +1 -0
- package/dist/shims/app-router-scroll-state.d.ts +2 -1
- package/dist/shims/app-router-scroll-state.js +7 -2
- package/dist/shims/document.js +15 -2
- package/dist/shims/hash-scroll.d.ts +1 -1
- package/dist/shims/hash-scroll.js +2 -1
- package/dist/shims/link.d.ts +2 -0
- package/dist/shims/link.js +59 -18
- package/dist/shims/metadata.d.ts +11 -17
- package/dist/shims/metadata.js +24 -32
- package/dist/shims/navigation.d.ts +18 -2
- package/dist/shims/navigation.js +69 -20
- package/dist/shims/request-context.d.ts +2 -1
- package/dist/shims/slot.d.ts +2 -1
- package/dist/shims/slot.js +6 -2
- package/dist/shims/streamed-icons.d.ts +9 -0
- package/dist/shims/streamed-icons.js +33 -0
- 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
|
|
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
|
-
|
|
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 ||
|
|
258
|
-
docHtml = docHtml.replace("<!-- __NEXT_SCRIPTS__ -->",
|
|
259
|
-
if (!docHtml.includes("__NEXT_DATA__")) docHtml = docHtml.replace("</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
|
|
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
|
-
${
|
|
279
|
+
${protectAssetTags(fontHeadHTML)}${protectAssetTags(headHTML)}
|
|
280
|
+
${generatedAssetHeadHTML}
|
|
266
281
|
</head>
|
|
267
282
|
<body>
|
|
268
283
|
<div id="__next">${STREAM_BODY_MARKER}</div>
|
|
269
|
-
${
|
|
284
|
+
${generatedScripts}
|
|
270
285
|
</body>
|
|
271
286
|
</html>`;
|
|
272
|
-
|
|
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
|
|
439
|
-
if (
|
|
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
|
|
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, "&").replace(/"/g, """);
|
|
779
800
|
const safeType = type.replace(/&/g, "&").replace(/"/g, """);
|
|
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 =
|
|
788
|
-
const appModuleSource =
|
|
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
|
|
839
|
+
const docFilePath = findFileWithExts(pagesDir, "_document", matcher);
|
|
819
840
|
let DocumentComponent = null;
|
|
820
|
-
if (
|
|
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 (
|
|
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
|
|
1012
|
-
if (
|
|
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">${
|
|
1084
|
-
${
|
|
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 };
|
package/dist/server/headers.d.ts
CHANGED
|
@@ -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 };
|
package/dist/server/headers.js
CHANGED
|
@@ -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 });
|
|
@@ -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) + "\"
|
|
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) + "\"
|
|
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:
|
|
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
|
-
|
|
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;
|