vinext 0.0.39 → 0.0.41

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 (100) hide show
  1. package/README.md +1 -1
  2. package/dist/build/standalone.js +7 -0
  3. package/dist/build/standalone.js.map +1 -1
  4. package/dist/check.js +2 -2
  5. package/dist/check.js.map +1 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/entries/app-rsc-entry.d.ts +2 -1
  8. package/dist/entries/app-rsc-entry.js +185 -264
  9. package/dist/entries/app-rsc-entry.js.map +1 -1
  10. package/dist/entries/pages-server-entry.js +205 -199
  11. package/dist/entries/pages-server-entry.js.map +1 -1
  12. package/dist/index.d.ts +32 -1
  13. package/dist/index.js +81 -6
  14. package/dist/index.js.map +1 -1
  15. package/dist/init.d.ts +1 -1
  16. package/dist/init.js +2 -2
  17. package/dist/init.js.map +1 -1
  18. package/dist/plugins/fonts.js +1 -0
  19. package/dist/plugins/fonts.js.map +1 -1
  20. package/dist/plugins/server-externals-manifest.d.ts +11 -1
  21. package/dist/plugins/server-externals-manifest.js +10 -3
  22. package/dist/plugins/server-externals-manifest.js.map +1 -1
  23. package/dist/routing/app-router.d.ts +10 -2
  24. package/dist/routing/app-router.js +37 -22
  25. package/dist/routing/app-router.js.map +1 -1
  26. package/dist/server/app-page-boundary-render.d.ts +1 -0
  27. package/dist/server/app-page-boundary-render.js +1 -0
  28. package/dist/server/app-page-boundary-render.js.map +1 -1
  29. package/dist/server/app-page-render.d.ts +1 -0
  30. package/dist/server/app-page-render.js +2 -0
  31. package/dist/server/app-page-render.js.map +1 -1
  32. package/dist/server/app-page-response.d.ts +4 -1
  33. package/dist/server/app-page-response.js +14 -8
  34. package/dist/server/app-page-response.js.map +1 -1
  35. package/dist/server/app-page-route-wiring.d.ts +79 -0
  36. package/dist/server/app-page-route-wiring.js +167 -0
  37. package/dist/server/app-page-route-wiring.js.map +1 -0
  38. package/dist/server/app-page-stream.d.ts +4 -1
  39. package/dist/server/app-page-stream.js +5 -1
  40. package/dist/server/app-page-stream.js.map +1 -1
  41. package/dist/server/app-route-handler-response.js +6 -2
  42. package/dist/server/app-route-handler-response.js.map +1 -1
  43. package/dist/server/app-router-entry.d.ts +6 -1
  44. package/dist/server/app-router-entry.js +9 -2
  45. package/dist/server/app-router-entry.js.map +1 -1
  46. package/dist/server/app-ssr-entry.d.ts +3 -1
  47. package/dist/server/app-ssr-entry.js +17 -17
  48. package/dist/server/app-ssr-entry.js.map +1 -1
  49. package/dist/server/app-ssr-stream.d.ts +1 -1
  50. package/dist/server/app-ssr-stream.js +4 -4
  51. package/dist/server/app-ssr-stream.js.map +1 -1
  52. package/dist/server/csp.d.ts +12 -0
  53. package/dist/server/csp.js +46 -0
  54. package/dist/server/csp.js.map +1 -0
  55. package/dist/server/dev-server.js +20 -14
  56. package/dist/server/dev-server.js.map +1 -1
  57. package/dist/server/html.d.ts +4 -1
  58. package/dist/server/html.js +11 -1
  59. package/dist/server/html.js.map +1 -1
  60. package/dist/server/middleware-response-headers.d.ts +12 -0
  61. package/dist/server/middleware-response-headers.js +23 -0
  62. package/dist/server/middleware-response-headers.js.map +1 -0
  63. package/dist/server/pages-page-data.d.ts +1 -0
  64. package/dist/server/pages-page-data.js +2 -2
  65. package/dist/server/pages-page-data.js.map +1 -1
  66. package/dist/server/pages-page-response.d.ts +2 -1
  67. package/dist/server/pages-page-response.js +16 -14
  68. package/dist/server/pages-page-response.js.map +1 -1
  69. package/dist/server/prod-server.d.ts +1 -1
  70. package/dist/server/prod-server.js +41 -14
  71. package/dist/server/prod-server.js.map +1 -1
  72. package/dist/server/request-pipeline.d.ts +14 -1
  73. package/dist/server/request-pipeline.js +55 -1
  74. package/dist/server/request-pipeline.js.map +1 -1
  75. package/dist/server/worker-utils.d.ts +4 -1
  76. package/dist/server/worker-utils.js +31 -1
  77. package/dist/server/worker-utils.js.map +1 -1
  78. package/dist/shims/error-boundary.d.ts +14 -5
  79. package/dist/shims/error-boundary.js +23 -3
  80. package/dist/shims/error-boundary.js.map +1 -1
  81. package/dist/shims/head.js.map +1 -1
  82. package/dist/shims/navigation.d.ts +16 -1
  83. package/dist/shims/navigation.js +18 -3
  84. package/dist/shims/navigation.js.map +1 -1
  85. package/dist/shims/router.js +127 -38
  86. package/dist/shims/router.js.map +1 -1
  87. package/dist/shims/script-nonce-context.d.ts +12 -0
  88. package/dist/shims/script-nonce-context.js +17 -0
  89. package/dist/shims/script-nonce-context.js.map +1 -0
  90. package/dist/shims/script.js +41 -10
  91. package/dist/shims/script.js.map +1 -1
  92. package/dist/shims/server.d.ts +17 -4
  93. package/dist/shims/server.js +97 -74
  94. package/dist/shims/server.js.map +1 -1
  95. package/dist/shims/slot.d.ts +28 -0
  96. package/dist/shims/slot.js +49 -0
  97. package/dist/shims/slot.js.map +1 -0
  98. package/dist/shims/url-safety.js +25 -4
  99. package/dist/shims/url-safety.js.map +1 -1
  100. package/package.json +7 -8
@@ -1 +1 @@
1
- {"version":3,"file":"pages-page-response.js","names":[],"sources":["../../src/server/pages-page-response.ts"],"sourcesContent":["import React, { type ComponentType, type ReactNode } from \"react\";\n\nexport type PagesFontPreload = {\n href: string;\n type: string;\n};\n\nexport type PagesI18nRenderContext = {\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n domainLocales?: unknown;\n};\n\nexport type PagesGsspResponse = {\n statusCode: number;\n getHeaders(): Record<string, string | number | boolean | string[]>;\n};\n\ntype PagesStreamedHtmlResponse = {\n __vinextStreamedHtmlResponse?: boolean;\n} & Response;\n\nexport type RenderPagesPageResponseOptions = {\n assetTags: string;\n buildId: string | null;\n clearSsrContext: () => void;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n DocumentComponent: ComponentType | null;\n flushPreloads?: (() => Promise<void> | void) | undefined;\n fontLinkHeader: string;\n fontPreloads: PagesFontPreload[];\n getFontLinks: () => string[];\n getFontStyles: () => string[];\n getSSRHeadHTML?: (() => string) | undefined;\n gsspRes: PagesGsspResponse | null;\n isrCacheKey: (router: string, pathname: string) => string;\n isrRevalidateSeconds: number | null;\n isrSet: (\n key: string,\n data: {\n kind: \"PAGES\";\n html: string;\n pageData: Record<string, unknown>;\n headers: undefined;\n status: undefined;\n },\n revalidateSeconds: number,\n ) => Promise<void>;\n i18n: PagesI18nRenderContext;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderDocumentToString: (element: ReactNode) => Promise<string>;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n renderToReadableStream: (element: ReactNode) => Promise<ReadableStream<Uint8Array>>;\n resetSSRHead?: (() => void) | undefined;\n routePattern: string;\n routeUrl: string;\n safeJsonStringify: (value: unknown) => string;\n};\n\nfunction escapeAttr(value: string): string {\n return value.replace(/&/g, \"&amp;\").replace(/\"/g, \"&quot;\");\n}\n\nfunction buildPagesFontHeadHtml(\n fontLinks: string[],\n fontPreloads: PagesFontPreload[],\n fontStyles: string[],\n): string {\n let html = \"\";\n\n for (const link of fontLinks) {\n html += `<link rel=\"stylesheet\" href=\"${escapeAttr(link)}\" />\\n `;\n }\n\n for (const preload of fontPreloads) {\n html += `<link rel=\"preload\" href=\"${escapeAttr(preload.href)}\" as=\"font\" type=\"${escapeAttr(preload.type)}\" crossorigin />\\n `;\n }\n\n if (fontStyles.length > 0) {\n html += `<style data-vinext-fonts>${fontStyles.join(\"\\n\")}</style>\\n `;\n }\n\n return html;\n}\n\nexport function buildPagesNextDataScript(\n options: Pick<\n RenderPagesPageResponseOptions,\n \"buildId\" | \"i18n\" | \"pageProps\" | \"params\" | \"routePattern\" | \"safeJsonStringify\"\n >,\n): string {\n const nextDataPayload: Record<string, unknown> = {\n props: { pageProps: options.pageProps },\n page: options.routePattern,\n query: options.params,\n buildId: options.buildId,\n isFallback: false,\n };\n\n if (options.i18n.locales) {\n nextDataPayload.locale = options.i18n.locale;\n nextDataPayload.locales = options.i18n.locales;\n nextDataPayload.defaultLocale = options.i18n.defaultLocale;\n nextDataPayload.domainLocales = options.i18n.domainLocales;\n }\n\n const localeGlobals = options.i18n.locales\n ? `;window.__VINEXT_LOCALE__=${options.safeJsonStringify(options.i18n.locale)}` +\n `;window.__VINEXT_LOCALES__=${options.safeJsonStringify(options.i18n.locales)}` +\n `;window.__VINEXT_DEFAULT_LOCALE__=${options.safeJsonStringify(options.i18n.defaultLocale)}`\n : \"\";\n\n return `<script>window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}</script>`;\n}\n\nasync function buildPagesShellHtml(\n bodyMarker: string,\n fontHeadHTML: string,\n nextDataScript: string,\n options: Pick<\n RenderPagesPageResponseOptions,\n \"assetTags\" | \"DocumentComponent\" | \"renderDocumentToString\"\n > & {\n ssrHeadHTML: string;\n },\n): Promise<string> {\n if (options.DocumentComponent) {\n let html = await options.renderDocumentToString(React.createElement(options.DocumentComponent));\n html = html.replace(\"__NEXT_MAIN__\", bodyMarker);\n if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) {\n html = html.replace(\n \"</head>\",\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n ${options.assetTags}\\n</head>`,\n );\n }\n html = html.replace(\"<!-- __NEXT_SCRIPTS__ -->\", nextDataScript);\n if (!html.includes(\"__NEXT_DATA__\")) {\n html = html.replace(\"</body>\", ` ${nextDataScript}\\n</body>`);\n }\n return html;\n }\n\n return (\n \"<!DOCTYPE html>\\n<html>\\n<head>\\n\" +\n ' <meta charset=\"utf-8\" />\\n' +\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\\n' +\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n` +\n ` ${options.assetTags}\\n` +\n \"</head>\\n<body>\\n\" +\n ` <div id=\"__next\">${bodyMarker}</div>\\n` +\n ` ${nextDataScript}\\n` +\n \"</body>\\n</html>\"\n );\n}\n\nasync function buildPagesCompositeStream(\n bodyStream: ReadableStream<Uint8Array>,\n shellPrefix: string,\n shellSuffix: string,\n): Promise<ReadableStream<Uint8Array>> {\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n controller.enqueue(encoder.encode(shellPrefix));\n const reader = bodyStream.getReader();\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n break;\n }\n controller.enqueue(chunk.value);\n }\n } finally {\n reader.releaseLock();\n }\n controller.enqueue(encoder.encode(shellSuffix));\n controller.close();\n },\n });\n}\n\nfunction applyGsspHeaders(headers: Headers, gsspRes: PagesGsspResponse | null): number {\n if (!gsspRes) {\n return 200;\n }\n\n const gsspHeaders = gsspRes.getHeaders();\n for (const key of Object.keys(gsspHeaders)) {\n const value = gsspHeaders[key];\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" && Array.isArray(value)) {\n for (const cookie of value) {\n headers.append(\"set-cookie\", String(cookie));\n }\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(key, value.join(\", \"));\n continue;\n }\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n headers.set(key, String(value));\n }\n }\n headers.set(\"Content-Type\", \"text/html\");\n return gsspRes.statusCode;\n}\n\nexport async function renderPagesPageResponse(\n options: RenderPagesPageResponseOptions,\n): Promise<Response> {\n const pageElement = options.createPageElement(options.pageProps);\n\n options.resetSSRHead?.();\n await options.flushPreloads?.();\n\n const fontHeadHTML = buildPagesFontHeadHtml(\n options.getFontLinks(),\n options.fontPreloads,\n options.getFontStyles(),\n );\n const nextDataScript = buildPagesNextDataScript({\n buildId: options.buildId,\n i18n: options.i18n,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n });\n const bodyMarker = \"<!--VINEXT_STREAM_BODY-->\";\n const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML: options.getSSRHeadHTML?.() ?? \"\",\n });\n\n options.clearSsrContext();\n\n const markerIndex = shellHtml.indexOf(bodyMarker);\n const shellPrefix = shellHtml.slice(0, markerIndex);\n const shellSuffix = shellHtml.slice(markerIndex + bodyMarker.length);\n const bodyStream = await options.renderToReadableStream(pageElement);\n const compositeStream = await buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix);\n\n if (options.isrRevalidateSeconds !== null && options.isrRevalidateSeconds > 0) {\n const isrElement = options.createPageElement(options.pageProps);\n const isrHtml = await options.renderIsrPassToStringAsync(isrElement);\n const fullHtml = shellPrefix + isrHtml + shellSuffix;\n const isrPathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", isrPathname);\n await options.isrSet(\n cacheKey,\n {\n kind: \"PAGES\",\n html: fullHtml,\n pageData: options.pageProps,\n headers: undefined,\n status: undefined,\n },\n options.isrRevalidateSeconds,\n );\n }\n\n const responseHeaders = new Headers({ \"Content-Type\": \"text/html\" });\n const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes);\n\n if (options.isrRevalidateSeconds) {\n responseHeaders.set(\n \"Cache-Control\",\n `s-maxage=${options.isrRevalidateSeconds}, stale-while-revalidate`,\n );\n responseHeaders.set(\"X-Vinext-Cache\", \"MISS\");\n }\n if (options.fontLinkHeader) {\n responseHeaders.set(\"Link\", options.fontLinkHeader);\n }\n\n const response = new Response(compositeStream, {\n status: finalStatus,\n headers: responseHeaders,\n }) as PagesStreamedHtmlResponse;\n // Mark the normal streamed HTML render so the Node prod server can strip\n // stale Content-Length only for this path, not for custom gSSP responses.\n response.__vinextStreamedHtmlResponse = true;\n return response;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pages-page-response.js","names":[],"sources":["../../src/server/pages-page-response.ts"],"sourcesContent":["import React, { type ComponentType, type ReactNode } from \"react\";\nimport { withScriptNonce } from \"../shims/script-nonce-context.js\";\nimport { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr } from \"./html.js\";\n\nexport type PagesFontPreload = {\n href: string;\n type: string;\n};\n\nexport type PagesI18nRenderContext = {\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n domainLocales?: unknown;\n};\n\nexport type PagesGsspResponse = {\n statusCode: number;\n getHeaders(): Record<string, string | number | boolean | string[]>;\n};\n\ntype PagesStreamedHtmlResponse = {\n __vinextStreamedHtmlResponse?: boolean;\n} & Response;\n\nexport type RenderPagesPageResponseOptions = {\n assetTags: string;\n buildId: string | null;\n clearSsrContext: () => void;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n DocumentComponent: ComponentType | null;\n flushPreloads?: (() => Promise<void> | void) | undefined;\n fontLinkHeader: string;\n fontPreloads: PagesFontPreload[];\n getFontLinks: () => string[];\n getFontStyles: () => string[];\n getSSRHeadHTML?: (() => string) | undefined;\n gsspRes: PagesGsspResponse | null;\n isrCacheKey: (router: string, pathname: string) => string;\n isrRevalidateSeconds: number | null;\n isrSet: (\n key: string,\n data: {\n kind: \"PAGES\";\n html: string;\n pageData: Record<string, unknown>;\n headers: undefined;\n status: undefined;\n },\n revalidateSeconds: number,\n ) => Promise<void>;\n i18n: PagesI18nRenderContext;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderDocumentToString: (element: ReactNode) => Promise<string>;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n renderToReadableStream: (element: ReactNode) => Promise<ReadableStream<Uint8Array>>;\n resetSSRHead?: (() => void) | undefined;\n routePattern: string;\n routeUrl: string;\n safeJsonStringify: (value: unknown) => string;\n scriptNonce?: string;\n};\n\nfunction buildPagesFontHeadHtml(\n fontLinks: string[],\n fontPreloads: PagesFontPreload[],\n fontStyles: string[],\n scriptNonce?: string,\n): string {\n let html = \"\";\n const nonceAttr = createNonceAttribute(scriptNonce);\n\n for (const link of fontLinks) {\n html += `<link rel=\"stylesheet\"${nonceAttr} href=\"${escapeHtmlAttr(link)}\" />\\n `;\n }\n\n for (const preload of fontPreloads) {\n html += `<link rel=\"preload\"${nonceAttr} href=\"${escapeHtmlAttr(preload.href)}\" as=\"font\" type=\"${escapeHtmlAttr(preload.type)}\" crossorigin />\\n `;\n }\n\n if (fontStyles.length > 0) {\n html += `<style data-vinext-fonts${nonceAttr}>${fontStyles.join(\"\\n\")}</style>\\n `;\n }\n\n return html;\n}\n\nexport function buildPagesNextDataScript(\n options: Pick<\n RenderPagesPageResponseOptions,\n | \"buildId\"\n | \"i18n\"\n | \"pageProps\"\n | \"params\"\n | \"routePattern\"\n | \"safeJsonStringify\"\n | \"scriptNonce\"\n >,\n): string {\n const nextDataPayload: Record<string, unknown> = {\n props: { pageProps: options.pageProps },\n page: options.routePattern,\n query: options.params,\n buildId: options.buildId,\n isFallback: false,\n };\n\n if (options.i18n.locales) {\n nextDataPayload.locale = options.i18n.locale;\n nextDataPayload.locales = options.i18n.locales;\n nextDataPayload.defaultLocale = options.i18n.defaultLocale;\n nextDataPayload.domainLocales = options.i18n.domainLocales;\n }\n\n const localeGlobals = options.i18n.locales\n ? `;window.__VINEXT_LOCALE__=${options.safeJsonStringify(options.i18n.locale)}` +\n `;window.__VINEXT_LOCALES__=${options.safeJsonStringify(options.i18n.locales)}` +\n `;window.__VINEXT_DEFAULT_LOCALE__=${options.safeJsonStringify(options.i18n.defaultLocale)}`\n : \"\";\n\n return createInlineScriptTag(\n `window.__NEXT_DATA__ = ${options.safeJsonStringify(nextDataPayload)}${localeGlobals}`,\n options.scriptNonce,\n );\n}\n\nasync function buildPagesShellHtml(\n bodyMarker: string,\n fontHeadHTML: string,\n nextDataScript: string,\n options: Pick<\n RenderPagesPageResponseOptions,\n \"assetTags\" | \"DocumentComponent\" | \"renderDocumentToString\"\n > & {\n ssrHeadHTML: string;\n },\n): Promise<string> {\n if (options.DocumentComponent) {\n let html = await options.renderDocumentToString(React.createElement(options.DocumentComponent));\n html = html.replace(\"__NEXT_MAIN__\", bodyMarker);\n if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) {\n html = html.replace(\n \"</head>\",\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n ${options.assetTags}\\n</head>`,\n );\n }\n html = html.replace(\"<!-- __NEXT_SCRIPTS__ -->\", nextDataScript);\n if (!html.includes(\"__NEXT_DATA__\")) {\n html = html.replace(\"</body>\", ` ${nextDataScript}\\n</body>`);\n }\n return html;\n }\n\n return (\n \"<!DOCTYPE html>\\n<html>\\n<head>\\n\" +\n ' <meta charset=\"utf-8\" />\\n' +\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\\n' +\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n` +\n ` ${options.assetTags}\\n` +\n \"</head>\\n<body>\\n\" +\n ` <div id=\"__next\">${bodyMarker}</div>\\n` +\n ` ${nextDataScript}\\n` +\n \"</body>\\n</html>\"\n );\n}\n\nasync function buildPagesCompositeStream(\n bodyStream: ReadableStream<Uint8Array>,\n shellPrefix: string,\n shellSuffix: string,\n): Promise<ReadableStream<Uint8Array>> {\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n controller.enqueue(encoder.encode(shellPrefix));\n const reader = bodyStream.getReader();\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n break;\n }\n controller.enqueue(chunk.value);\n }\n } finally {\n reader.releaseLock();\n }\n controller.enqueue(encoder.encode(shellSuffix));\n controller.close();\n },\n });\n}\n\nfunction applyGsspHeaders(headers: Headers, gsspRes: PagesGsspResponse | null): number {\n if (!gsspRes) {\n return 200;\n }\n\n const gsspHeaders = gsspRes.getHeaders();\n for (const key of Object.keys(gsspHeaders)) {\n const value = gsspHeaders[key];\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" && Array.isArray(value)) {\n for (const cookie of value) {\n headers.append(\"set-cookie\", String(cookie));\n }\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(key, value.join(\", \"));\n continue;\n }\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n headers.set(key, String(value));\n }\n }\n headers.set(\"Content-Type\", \"text/html\");\n return gsspRes.statusCode;\n}\n\nexport async function renderPagesPageResponse(\n options: RenderPagesPageResponseOptions,\n): Promise<Response> {\n const pageElement = withScriptNonce(\n React.createElement(React.Fragment, null, options.createPageElement(options.pageProps)),\n options.scriptNonce,\n );\n\n options.resetSSRHead?.();\n await options.flushPreloads?.();\n\n const fontHeadHTML = buildPagesFontHeadHtml(\n options.getFontLinks(),\n options.fontPreloads,\n options.getFontStyles(),\n options.scriptNonce,\n );\n const nextDataScript = buildPagesNextDataScript({\n buildId: options.buildId,\n i18n: options.i18n,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n scriptNonce: options.scriptNonce,\n });\n const bodyMarker = \"<!--VINEXT_STREAM_BODY-->\";\n const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML: options.getSSRHeadHTML?.() ?? \"\",\n });\n\n options.clearSsrContext();\n\n const markerIndex = shellHtml.indexOf(bodyMarker);\n const shellPrefix = shellHtml.slice(0, markerIndex);\n const shellSuffix = shellHtml.slice(markerIndex + bodyMarker.length);\n const bodyStream = await options.renderToReadableStream(pageElement);\n const compositeStream = await buildPagesCompositeStream(bodyStream, shellPrefix, shellSuffix);\n\n if (\n // Keep nonce-bearing pages out of ISR writes: rewritePagesCachedHtml()\n // later matches the cached __NEXT_DATA__ block via a bare <script> marker.\n !options.scriptNonce &&\n options.isrRevalidateSeconds !== null &&\n options.isrRevalidateSeconds > 0\n ) {\n const isrElement = React.createElement(\n React.Fragment,\n null,\n options.createPageElement(options.pageProps),\n );\n const isrHtml = await options.renderIsrPassToStringAsync(isrElement);\n const fullHtml = shellPrefix + isrHtml + shellSuffix;\n const isrPathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", isrPathname);\n await options.isrSet(\n cacheKey,\n {\n kind: \"PAGES\",\n html: fullHtml,\n pageData: options.pageProps,\n headers: undefined,\n status: undefined,\n },\n options.isrRevalidateSeconds,\n );\n }\n\n const responseHeaders = new Headers({ \"Content-Type\": \"text/html\" });\n const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes);\n\n if (options.scriptNonce) {\n responseHeaders.set(\"Cache-Control\", \"no-store, must-revalidate\");\n } else if (options.isrRevalidateSeconds) {\n responseHeaders.set(\n \"Cache-Control\",\n `s-maxage=${options.isrRevalidateSeconds}, stale-while-revalidate`,\n );\n responseHeaders.set(\"X-Vinext-Cache\", \"MISS\");\n }\n if (options.fontLinkHeader) {\n responseHeaders.set(\"Link\", options.fontLinkHeader);\n }\n\n const response = new Response(compositeStream, {\n status: finalStatus,\n headers: responseHeaders,\n }) as PagesStreamedHtmlResponse;\n // Mark the normal streamed HTML render so the Node prod server can strip\n // stale Content-Length only for this path, not for custom gSSP responses.\n response.__vinextStreamedHtmlResponse = true;\n return response;\n}\n"],"mappings":"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"}
@@ -43,7 +43,7 @@ declare function sendCompressed(req: IncomingMessage, res: ServerResponse, body:
43
43
  * Without a cache, falls back to async filesystem probing (still non-blocking,
44
44
  * unlike the old sync existsSync/statSync approach).
45
45
  */
46
- declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>): Promise<boolean>;
46
+ declare function tryServeStatic(req: IncomingMessage, res: ServerResponse, clientDir: string, pathname: string, compress: boolean, cache?: StaticFileCache, extraHeaders?: Record<string, string | string[]>, statusCode?: number): Promise<boolean>;
47
47
  /**
48
48
  * Resolve the host for a request, ignoring X-Forwarded-Host to prevent
49
49
  * host header poisoning attacks (open redirects, cache poisoning).
@@ -248,8 +248,10 @@ function sendCompressed(req, res, body, contentType, statusCode, extraHeaders =
248
248
  * Without a cache, falls back to async filesystem probing (still non-blocking,
249
249
  * unlike the old sync existsSync/statSync approach).
250
250
  */
251
- async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders) {
251
+ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, extraHeaders, statusCode) {
252
252
  if (pathname === "/") return false;
253
+ const responseStatus = statusCode ?? 200;
254
+ const omitBody = isNoBodyResponseStatus(responseStatus);
253
255
  if (cache) {
254
256
  let lookupPath;
255
257
  if (pathname.includes("%")) {
@@ -266,7 +268,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
266
268
  const entry = cache.lookup(lookupPath);
267
269
  if (!entry) return false;
268
270
  const ifNoneMatch = req.headers["if-none-match"];
269
- if (typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
271
+ if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, entry.etag)) {
270
272
  if (extraHeaders) res.writeHead(304, {
271
273
  ...entry.notModifiedHeaders,
272
274
  ...extraHeaders
@@ -278,12 +280,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
278
280
  const rawAe = compress ? req.headers["accept-encoding"] : void 0;
279
281
  const ae = typeof rawAe === "string" ? rawAe.toLowerCase() : void 0;
280
282
  const variant = ae ? ae.includes("zstd") && entry.zst || ae.includes("br") && entry.br || ae.includes("gzip") && entry.gz || entry.original : entry.original;
281
- if (extraHeaders) res.writeHead(200, {
283
+ if (extraHeaders) res.writeHead(responseStatus, {
282
284
  ...variant.headers,
283
285
  ...extraHeaders
284
286
  });
285
- else res.writeHead(200, variant.headers);
286
- if (req.method === "HEAD") {
287
+ else res.writeHead(responseStatus, variant.headers);
288
+ if (omitBody || req.method === "HEAD") {
287
289
  res.end();
288
290
  return true;
289
291
  }
@@ -316,7 +318,7 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
316
318
  const baseType = ct.split(";")[0].trim();
317
319
  const isCompressible = compress && COMPRESSIBLE_TYPES.has(baseType);
318
320
  const ifNoneMatch = req.headers["if-none-match"];
319
- if (typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
321
+ if (responseStatus === 200 && typeof ifNoneMatch === "string" && matchesIfNoneMatchHeader(ifNoneMatch, etag)) {
320
322
  const notModifiedHeaders = {
321
323
  ETag: etag,
322
324
  "Cache-Control": cacheControl,
@@ -336,12 +338,12 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
336
338
  if (isCompressible) {
337
339
  const encoding = negotiateEncoding(req);
338
340
  if (encoding) {
339
- res.writeHead(200, {
341
+ res.writeHead(responseStatus, {
340
342
  ...baseHeaders,
341
343
  "Content-Encoding": encoding,
342
344
  Vary: "Accept-Encoding"
343
345
  });
344
- if (req.method === "HEAD") {
346
+ if (omitBody || req.method === "HEAD") {
345
347
  res.end();
346
348
  return true;
347
349
  }
@@ -355,11 +357,11 @@ async function tryServeStatic(req, res, clientDir, pathname, compress, cache, ex
355
357
  return true;
356
358
  }
357
359
  }
358
- res.writeHead(200, {
360
+ res.writeHead(responseStatus, {
359
361
  ...baseHeaders,
360
362
  "Content-Length": String(resolved.size)
361
363
  });
362
- if (req.method === "HEAD") {
364
+ if (omitBody || req.method === "HEAD") {
363
365
  res.end();
364
366
  return true;
365
367
  }
@@ -644,7 +646,31 @@ async function startAppRouterServer(options) {
644
646
  }
645
647
  try {
646
648
  const qs = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
647
- await sendWebResponse(await rscHandler(nodeToWebRequest(req, pathname + qs)), req, res, compress);
649
+ const response = await rscHandler(nodeToWebRequest(req, pathname + qs));
650
+ const staticFileSignal = response.headers.get("x-vinext-static-file");
651
+ if (staticFileSignal) {
652
+ let staticFilePath = "/";
653
+ try {
654
+ staticFilePath = decodeURIComponent(staticFileSignal);
655
+ } catch {
656
+ staticFilePath = staticFileSignal;
657
+ }
658
+ const staticResponseHeaders = omitHeadersCaseInsensitive(mergeResponseHeaders({}, response), [
659
+ "x-vinext-static-file",
660
+ "content-encoding",
661
+ "content-length",
662
+ "content-type"
663
+ ]);
664
+ const served = await tryServeStatic(req, res, clientDir, staticFilePath, compress, staticCache, staticResponseHeaders, response.status);
665
+ cancelResponseBody(response);
666
+ if (served) return;
667
+ await sendWebResponse(new Response("Not Found", {
668
+ status: 404,
669
+ headers: toWebHeaders(staticResponseHeaders)
670
+ }), req, res, compress);
671
+ return;
672
+ }
673
+ await sendWebResponse(response, req, res, compress);
648
674
  } catch (e) {
649
675
  console.error("[vinext] Server error:", e);
650
676
  if (!res.headersSent) {
@@ -671,7 +697,7 @@ async function startAppRouterServer(options) {
671
697
  * Start the Pages Router production server.
672
698
  *
673
699
  * Uses the server entry (dist/server/entry.js) which exports:
674
- * - renderPage(request, url, manifest) — SSR rendering (Web Request → Response)
700
+ * - renderPage(request, url, manifest, ctx?, middlewareHeaders?) — SSR rendering (Web Request → Response)
675
701
  * - handleApiRoute(request, url) — API route handling (Web Request → Response)
676
702
  * - runMiddleware(request, ctx?) — middleware execution (ctx optional; pass for ctx.waitUntil() on Workers)
677
703
  * - vinextConfig — embedded next.config.js settings
@@ -927,7 +953,8 @@ async function startPagesRouterServer(options) {
927
953
  }
928
954
  let response;
929
955
  if (typeof renderPage === "function") {
930
- response = await renderPage(webRequest, resolvedUrl, ssrManifest);
956
+ const middlewareResponseHeaders = toWebHeaders(middlewareHeaders);
957
+ response = await renderPage(webRequest, resolvedUrl, ssrManifest, void 0, middlewareResponseHeaders);
931
958
  if (response && response.status === 404 && configRewrites.fallback?.length) {
932
959
  const fallbackRewrite = matchRewrite(resolvedPathname, configRewrites.fallback, postMwReqCtx);
933
960
  if (fallbackRewrite) {
@@ -935,7 +962,7 @@ async function startPagesRouterServer(options) {
935
962
  await sendWebResponse(await proxyExternalRequest(webRequest, fallbackRewrite), req, res, compress);
936
963
  return;
937
964
  }
938
- response = await renderPage(webRequest, fallbackRewrite, ssrManifest);
965
+ response = await renderPage(webRequest, fallbackRewrite, ssrManifest, void 0, middlewareResponseHeaders);
939
966
  }
940
967
  }
941
968
  }