vinext 0.0.54 → 0.0.55

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 (135) hide show
  1. package/README.md +1 -0
  2. package/dist/check.js +15 -3
  3. package/dist/check.js.map +1 -1
  4. package/dist/client/navigation-runtime.d.ts +1 -0
  5. package/dist/client/navigation-runtime.js +1 -1
  6. package/dist/client/navigation-runtime.js.map +1 -1
  7. package/dist/config/next-config.d.ts +14 -1
  8. package/dist/config/next-config.js +24 -4
  9. package/dist/config/next-config.js.map +1 -1
  10. package/dist/config/tsconfig-paths.d.ts +12 -3
  11. package/dist/config/tsconfig-paths.js +55 -24
  12. package/dist/config/tsconfig-paths.js.map +1 -1
  13. package/dist/entries/app-rsc-entry.d.ts +2 -1
  14. package/dist/entries/app-rsc-entry.js +12 -0
  15. package/dist/entries/app-rsc-entry.js.map +1 -1
  16. package/dist/entries/app-rsc-manifest.js +22 -5
  17. package/dist/entries/app-rsc-manifest.js.map +1 -1
  18. package/dist/entries/pages-server-entry.js +41 -4
  19. package/dist/entries/pages-server-entry.js.map +1 -1
  20. package/dist/index.js +81 -39
  21. package/dist/index.js.map +1 -1
  22. package/dist/plugins/import-meta-url.d.ts +16 -0
  23. package/dist/plugins/import-meta-url.js +193 -0
  24. package/dist/plugins/import-meta-url.js.map +1 -0
  25. package/dist/server/app-browser-action-result.d.ts +9 -16
  26. package/dist/server/app-browser-action-result.js +25 -14
  27. package/dist/server/app-browser-action-result.js.map +1 -1
  28. package/dist/server/app-browser-entry.js +171 -45
  29. package/dist/server/app-browser-entry.js.map +1 -1
  30. package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
  31. package/dist/server/app-browser-mpa-navigation.js +36 -0
  32. package/dist/server/app-browser-mpa-navigation.js.map +1 -0
  33. package/dist/server/app-browser-popstate.d.ts +3 -1
  34. package/dist/server/app-browser-popstate.js +15 -1
  35. package/dist/server/app-browser-popstate.js.map +1 -1
  36. package/dist/server/app-browser-state.js +2 -1
  37. package/dist/server/app-browser-state.js.map +1 -1
  38. package/dist/server/app-layout-param-observation.d.ts +30 -0
  39. package/dist/server/app-layout-param-observation.js +130 -0
  40. package/dist/server/app-layout-param-observation.js.map +1 -0
  41. package/dist/server/app-page-boundary-render.js +2 -2
  42. package/dist/server/app-page-boundary-render.js.map +1 -1
  43. package/dist/server/app-page-dispatch.js +1 -1
  44. package/dist/server/app-page-params.d.ts +2 -1
  45. package/dist/server/app-page-params.js +14 -1
  46. package/dist/server/app-page-params.js.map +1 -1
  47. package/dist/server/app-page-probe.d.ts +12 -1
  48. package/dist/server/app-page-probe.js +116 -1
  49. package/dist/server/app-page-probe.js.map +1 -1
  50. package/dist/server/app-route-handler-response.js +1 -1
  51. package/dist/server/app-route-handler-response.js.map +1 -1
  52. package/dist/server/app-rsc-cache-busting.d.ts +3 -2
  53. package/dist/server/app-rsc-cache-busting.js +9 -7
  54. package/dist/server/app-rsc-cache-busting.js.map +1 -1
  55. package/dist/server/app-rsc-handler.js +11 -1
  56. package/dist/server/app-rsc-handler.js.map +1 -1
  57. package/dist/server/app-segment-config.d.ts +1 -1
  58. package/dist/server/app-segment-config.js +4 -1
  59. package/dist/server/app-segment-config.js.map +1 -1
  60. package/dist/server/app-server-action-execution.d.ts +5 -0
  61. package/dist/server/app-server-action-execution.js +198 -22
  62. package/dist/server/app-server-action-execution.js.map +1 -1
  63. package/dist/server/artifact-compatibility.d.ts +2 -1
  64. package/dist/server/artifact-compatibility.js +10 -1
  65. package/dist/server/artifact-compatibility.js.map +1 -1
  66. package/dist/server/client-reuse-manifest.d.ts +9 -4
  67. package/dist/server/client-reuse-manifest.js +2 -1
  68. package/dist/server/client-reuse-manifest.js.map +1 -1
  69. package/dist/server/dev-server.js +52 -10
  70. package/dist/server/dev-server.js.map +1 -1
  71. package/dist/server/document-initial-head.d.ts +7 -0
  72. package/dist/server/document-initial-head.js +35 -0
  73. package/dist/server/document-initial-head.js.map +1 -0
  74. package/dist/server/pages-document-initial-props.d.ts +84 -2
  75. package/dist/server/pages-document-initial-props.js +127 -1
  76. package/dist/server/pages-document-initial-props.js.map +1 -1
  77. package/dist/server/pages-node-compat.js +1 -1
  78. package/dist/server/pages-page-response.d.ts +14 -0
  79. package/dist/server/pages-page-response.js +31 -8
  80. package/dist/server/pages-page-response.js.map +1 -1
  81. package/dist/server/prod-server.js +13 -6
  82. package/dist/server/prod-server.js.map +1 -1
  83. package/dist/server/skip-cache-proof.d.ts +23 -2
  84. package/dist/server/skip-cache-proof.js +81 -12
  85. package/dist/server/skip-cache-proof.js.map +1 -1
  86. package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
  87. package/dist/server/static-layout-client-reuse-proof.js +35 -0
  88. package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
  89. package/dist/shims/cache.d.ts +21 -1
  90. package/dist/shims/cache.js +101 -6
  91. package/dist/shims/cache.js.map +1 -1
  92. package/dist/shims/document.d.ts +6 -0
  93. package/dist/shims/document.js +7 -8
  94. package/dist/shims/document.js.map +1 -1
  95. package/dist/shims/error-boundary.d.ts +4 -4
  96. package/dist/shims/error-boundary.js +27 -28
  97. package/dist/shims/error-boundary.js.map +1 -1
  98. package/dist/shims/fetch-cache.d.ts +3 -1
  99. package/dist/shims/fetch-cache.js +16 -5
  100. package/dist/shims/fetch-cache.js.map +1 -1
  101. package/dist/shims/hash-scroll.d.ts +4 -1
  102. package/dist/shims/hash-scroll.js +13 -1
  103. package/dist/shims/hash-scroll.js.map +1 -1
  104. package/dist/shims/head-state.d.ts +1 -0
  105. package/dist/shims/head-state.js +18 -3
  106. package/dist/shims/head-state.js.map +1 -1
  107. package/dist/shims/head.d.ts +35 -1
  108. package/dist/shims/head.js +113 -14
  109. package/dist/shims/head.js.map +1 -1
  110. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
  111. package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
  112. package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
  113. package/dist/shims/link.js +28 -2
  114. package/dist/shims/link.js.map +1 -1
  115. package/dist/shims/navigation.d.ts +39 -1
  116. package/dist/shims/navigation.js +61 -13
  117. package/dist/shims/navigation.js.map +1 -1
  118. package/dist/shims/router.js +37 -17
  119. package/dist/shims/router.js.map +1 -1
  120. package/dist/shims/thenable-params.d.ts +5 -2
  121. package/dist/shims/thenable-params.js +25 -1
  122. package/dist/shims/thenable-params.js.map +1 -1
  123. package/dist/shims/unified-request-context.js +3 -0
  124. package/dist/shims/unified-request-context.js.map +1 -1
  125. package/dist/utils/client-build-manifest.d.ts +15 -0
  126. package/dist/utils/client-build-manifest.js +54 -0
  127. package/dist/utils/client-build-manifest.js.map +1 -0
  128. package/dist/utils/hash.js +1 -1
  129. package/dist/utils/hash.js.map +1 -1
  130. package/dist/utils/lazy-chunks.d.ts +1 -1
  131. package/dist/utils/lazy-chunks.js.map +1 -1
  132. package/dist/utils/vite-version.d.ts +11 -0
  133. package/dist/utils/vite-version.js +36 -0
  134. package/dist/utils/vite-version.js.map +1 -0
  135. package/package.json +2 -2
@@ -1,7 +1,89 @@
1
- import { ComponentType } from "react";
1
+ import React, { ComponentType, ReactNode } from "react";
2
2
 
3
3
  //#region src/server/pages-document-initial-props.d.ts
4
4
  declare function loadUserDocumentInitialProps(DocumentComponent: ComponentType): Promise<Record<string, unknown> | null>;
5
+ /** Options accepted by a `ctx.renderPage()` call (Pages Router contract). */
6
+ type RenderPageEnhancers = {
7
+ enhanceApp?: (App: ComponentType<{
8
+ children?: ReactNode;
9
+ }>) => any;
10
+ enhanceComponent?: (Comp: ComponentType<unknown>) => any;
11
+ };
12
+ type DocumentRenderPageInput = {
13
+ /** The user `_document` component (may define `getInitialProps`). */DocumentComponent: ComponentType | null;
14
+ /**
15
+ * Build the page React tree with optional App/Component enhancers applied.
16
+ * Callers MUST NOT apply `withScriptNonce` themselves — this helper owns the
17
+ * nonce responsibility so the prod and dev paths stay symmetric.
18
+ */
19
+ enhancePageElement?: ((opts: RenderPageEnhancers) => ReactNode) | undefined; /** Render a React tree to a UTF-8 byte stream (prod/dev specific). */
20
+ renderToReadableStream: (element: React.ReactElement) => Promise<ReadableStream<Uint8Array>>; /** Render the document `styles` element to an HTML string. */
21
+ renderStylesToString: (element: React.ReactElement) => Promise<string>; /** Per-request CSP nonce applied to the enhanced page tree, if any. */
22
+ scriptNonce?: string | undefined; /** Extra `DocumentContext` fields (pathname/query/asPath). */
23
+ context?: Record<string, unknown> | undefined;
24
+ };
25
+ /**
26
+ * Run a user `_document.getInitialProps()` with a `ctx.renderPage()` that
27
+ * applies optional `enhanceApp` / `enhanceComponent` wrappers around the page
28
+ * React tree, mirroring Next.js's Pages Router contract.
29
+ *
30
+ * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the
31
+ * App/Component tree so styles can be collected during SSR. Shared between the
32
+ * prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so
33
+ * the `getInitialProps` + `renderPage` contract lives in one place.
34
+ *
35
+ * @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)
36
+ *
37
+ * Result of attempting the renderPage contract:
38
+ * - `skipped` — `getInitialProps` was NOT invoked (no override, or no
39
+ * `enhancePageElement` wired up). Callers should run the
40
+ * normal `loadUserDocumentInitialProps` fast path, which may
41
+ * invoke `getInitialProps` itself.
42
+ * - `rendered` — `renderPage` produced the body. `bodyHtml` is the rendered
43
+ * page string, `stylesHTML` the rendered `styles`, `docProps`
44
+ * the remaining props to spread onto `<Document>`, and `head`
45
+ * the head nodes returned by `getInitialProps` (forward them to
46
+ * `setDocumentInitialHead()` — do NOT call
47
+ * `callDocumentGetInitialProps()` as well).
48
+ * - `consumed` — `getInitialProps` WAS invoked but no body was produced
49
+ * (it never called `renderPage`, returned no `{ html }`, or
50
+ * threw). Callers must NOT re-invoke `getInitialProps` (that
51
+ * would call it a second time) — render the streaming body,
52
+ * spread `docProps` (possibly empty) onto `<Document>`, and
53
+ * forward `head` to `setDocumentInitialHead()`.
54
+ */
55
+ type RunDocumentRenderPageResult = {
56
+ status: "skipped";
57
+ } | {
58
+ status: "rendered";
59
+ bodyHtml: string;
60
+ stylesHTML: string;
61
+ docProps: Record<string, unknown>;
62
+ head: ReactNode[];
63
+ } | {
64
+ status: "consumed";
65
+ docProps: Record<string, unknown>;
66
+ head: ReactNode[];
67
+ };
68
+ /**
69
+ * Run a user `_document.getInitialProps()` with a `ctx.renderPage()` that
70
+ * applies optional `enhanceApp` / `enhanceComponent` wrappers around the page
71
+ * React tree, mirroring Next.js's Pages Router contract.
72
+ *
73
+ * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the
74
+ * App/Component tree so styles can be collected during SSR. Shared between the
75
+ * prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so
76
+ * the `getInitialProps` + `renderPage` contract lives in one place.
77
+ *
78
+ * `getInitialProps` is invoked at most once here. When this returns `consumed`
79
+ * or `rendered`, callers MUST treat that as the single invocation and must not
80
+ * call `loadUserDocumentInitialProps` (which would invoke it again — and, for a
81
+ * throwing override, surface the error as a 500 rather than the clean fallback
82
+ * this contract guarantees).
83
+ *
84
+ * @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)
85
+ */
86
+ declare function runDocumentRenderPage(input: DocumentRenderPageInput): Promise<RunDocumentRenderPageResult>;
5
87
  //#endregion
6
- export { loadUserDocumentInitialProps };
88
+ export { RenderPageEnhancers, loadUserDocumentInitialProps, runDocumentRenderPage };
7
89
  //# sourceMappingURL=pages-document-initial-props.d.ts.map
@@ -1,5 +1,47 @@
1
+ import { withScriptNonce } from "../shims/script-nonce-context.js";
1
2
  import Document from "../shims/document.js";
3
+ import { readStreamAsText } from "../utils/text-stream.js";
4
+ import React from "react";
2
5
  //#region src/server/pages-document-initial-props.ts
6
+ /**
7
+ * Pages Router `_document.tsx` `getInitialProps` helper.
8
+ *
9
+ * Next.js's `pages/_document.tsx` may override
10
+ * `static async getInitialProps(ctx)` to inject extra props onto the
11
+ * Document element (the classic pattern is
12
+ * `await Document.getInitialProps(ctx)` + spread, see Next.js's
13
+ * `test/e2e/async-modules/pages/_document.jsx`). The SSR pipeline invokes
14
+ * that hook and then renders the Document with the resolved props:
15
+ *
16
+ * <Document {...htmlProps} {...docProps} />
17
+ *
18
+ * Reference:
19
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx
20
+ * (search for `loadDocumentInitialProps` and `documentElement`).
21
+ *
22
+ * vinext only forwards `docProps`. The full `DocumentContext`
23
+ * (`renderPage`, `defaultGetInitialProps`, `pathname`, `query`, `req`, `res`,
24
+ * `err`, `asPath`) is not yet plumbed through. The common upstream pattern
25
+ *
26
+ * static async getInitialProps(ctx) {
27
+ * const initialProps = await Document.getInitialProps(ctx)
28
+ * return { ...initialProps, docValue }
29
+ * }
30
+ *
31
+ * works because the base `Document.getInitialProps` shim in
32
+ * `shims/document.tsx` returns `{ html: "" }` and ignores `ctx`. User
33
+ * overrides that *only* read `ctx` will see `undefined` fields — that is a
34
+ * separate gap tracked alongside the shim TODO.
35
+ *
36
+ * Returns `null` when the user did not override the base shim (the static
37
+ * `getInitialProps` reference still points at the shim's stub) so callers
38
+ * skip the spread and render the bare Document element on the fast path.
39
+ *
40
+ * Errors from a user `getInitialProps` propagate to the caller. Next.js's
41
+ * `loadGetInitialProps` does not catch — a throw becomes a 500 — and vinext
42
+ * matches that contract so user bugs surface as the loud failures Next.js
43
+ * apps already debug against.
44
+ */
3
45
  const BASE_GET_INITIAL_PROPS = Document.getInitialProps;
4
46
  async function loadUserDocumentInitialProps(DocumentComponent) {
5
47
  const getInitialProps = DocumentComponent.getInitialProps;
@@ -8,7 +50,91 @@ async function loadUserDocumentInitialProps(DocumentComponent) {
8
50
  const result = await getInitialProps({});
9
51
  return result && typeof result === "object" ? result : null;
10
52
  }
53
+ /**
54
+ * Run a user `_document.getInitialProps()` with a `ctx.renderPage()` that
55
+ * applies optional `enhanceApp` / `enhanceComponent` wrappers around the page
56
+ * React tree, mirroring Next.js's Pages Router contract.
57
+ *
58
+ * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the
59
+ * App/Component tree so styles can be collected during SSR. Shared between the
60
+ * prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so
61
+ * the `getInitialProps` + `renderPage` contract lives in one place.
62
+ *
63
+ * `getInitialProps` is invoked at most once here. When this returns `consumed`
64
+ * or `rendered`, callers MUST treat that as the single invocation and must not
65
+ * call `loadUserDocumentInitialProps` (which would invoke it again — and, for a
66
+ * throwing override, surface the error as a 500 rather than the clean fallback
67
+ * this contract guarantees).
68
+ *
69
+ * @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)
70
+ */
71
+ async function runDocumentRenderPage(input) {
72
+ const DocCtor = input.DocumentComponent;
73
+ if (!DocCtor || typeof DocCtor.getInitialProps !== "function") return { status: "skipped" };
74
+ if (DocCtor.getInitialProps === BASE_GET_INITIAL_PROPS) return { status: "skipped" };
75
+ if (!input.enhancePageElement) return { status: "skipped" };
76
+ const enhancePageElement = input.enhancePageElement;
77
+ let renderPageCalled = false;
78
+ const renderPage = async (opts = {}) => {
79
+ renderPageCalled = true;
80
+ const wrapped = withScriptNonce(enhancePageElement(opts), input.scriptNonce);
81
+ return {
82
+ html: await readStreamAsText(await input.renderToReadableStream(wrapped)),
83
+ head: []
84
+ };
85
+ };
86
+ let docInitialProps;
87
+ try {
88
+ docInitialProps = await DocCtor.getInitialProps({
89
+ renderPage,
90
+ defaultGetInitialProps: async (ctx) => {
91
+ const result = await (ctx.renderPage ?? renderPage)({ enhanceApp: (App) => (props) => React.createElement(App, props) });
92
+ return {
93
+ html: result.html,
94
+ head: result.head ?? [],
95
+ styles: void 0
96
+ };
97
+ },
98
+ ...input.context
99
+ });
100
+ } catch (err) {
101
+ console.error("[vinext] _document.getInitialProps() threw:", err);
102
+ return {
103
+ status: "consumed",
104
+ docProps: {},
105
+ head: []
106
+ };
107
+ }
108
+ const { html: _html, head: rawHead, styles: _styles, ...docProps } = docInitialProps ?? {};
109
+ const head = Array.isArray(rawHead) ? rawHead : [];
110
+ if (!renderPageCalled) return {
111
+ status: "consumed",
112
+ docProps,
113
+ head
114
+ };
115
+ if (!docInitialProps || typeof docInitialProps.html !== "string") {
116
+ console.error(`[vinext] "${DocCtor.displayName ?? DocCtor.name ?? "Document"}.getInitialProps()" did not return an object with a string "html" prop`);
117
+ return {
118
+ status: "consumed",
119
+ docProps,
120
+ head
121
+ };
122
+ }
123
+ let stylesHTML = "";
124
+ if (docInitialProps.styles != null) try {
125
+ stylesHTML = await input.renderStylesToString(React.createElement(React.Fragment, null, docInitialProps.styles));
126
+ } catch (err) {
127
+ console.error("[vinext] Failed to render _document.getInitialProps() styles:", err);
128
+ }
129
+ return {
130
+ status: "rendered",
131
+ bodyHtml: docInitialProps.html,
132
+ stylesHTML,
133
+ docProps,
134
+ head
135
+ };
136
+ }
11
137
  //#endregion
12
- export { loadUserDocumentInitialProps };
138
+ export { loadUserDocumentInitialProps, runDocumentRenderPage };
13
139
 
14
140
  //# sourceMappingURL=pages-document-initial-props.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pages-document-initial-props.js","names":["BaseDocument"],"sources":["../../src/server/pages-document-initial-props.ts"],"sourcesContent":["/**\n * Pages Router `_document.tsx` `getInitialProps` helper.\n *\n * Next.js's `pages/_document.tsx` may override\n * `static async getInitialProps(ctx)` to inject extra props onto the\n * Document element (the classic pattern is\n * `await Document.getInitialProps(ctx)` + spread, see Next.js's\n * `test/e2e/async-modules/pages/_document.jsx`). The SSR pipeline invokes\n * that hook and then renders the Document with the resolved props:\n *\n * <Document {...htmlProps} {...docProps} />\n *\n * Reference:\n * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx\n * (search for `loadDocumentInitialProps` and `documentElement`).\n *\n * vinext only forwards `docProps`. The full `DocumentContext`\n * (`renderPage`, `defaultGetInitialProps`, `pathname`, `query`, `req`, `res`,\n * `err`, `asPath`) is not yet plumbed through. The common upstream pattern\n *\n * static async getInitialProps(ctx) {\n * const initialProps = await Document.getInitialProps(ctx)\n * return { ...initialProps, docValue }\n * }\n *\n * works because the base `Document.getInitialProps` shim in\n * `shims/document.tsx` returns `{ html: \"\" }` and ignores `ctx`. User\n * overrides that *only* read `ctx` will see `undefined` fields — that is a\n * separate gap tracked alongside the shim TODO.\n *\n * Returns `null` when the user did not override the base shim (the static\n * `getInitialProps` reference still points at the shim's stub) so callers\n * skip the spread and render the bare Document element on the fast path.\n *\n * Errors from a user `getInitialProps` propagate to the caller. Next.js's\n * `loadGetInitialProps` does not catch — a throw becomes a 500 — and vinext\n * matches that contract so user bugs surface as the loud failures Next.js\n * apps already debug against.\n */\nimport type { ComponentType } from \"react\";\n// Static import so the identity comparison below is established once at\n// module evaluation. A previous version used `await import(...)` per request\n// and was flagged by reviewers as unnecessary work — and worse, it left a\n// per-request `await` on the fast path where the user had no override.\nimport BaseDocument from \"vinext/shims/document\";\n\nconst BASE_GET_INITIAL_PROPS = (\n BaseDocument as unknown as {\n getInitialProps?: unknown;\n }\n).getInitialProps;\n\nexport async function loadUserDocumentInitialProps(\n DocumentComponent: ComponentType,\n): Promise<Record<string, unknown> | null> {\n const getInitialProps = (\n DocumentComponent as unknown as {\n getInitialProps?: (\n ctx: unknown,\n ) => Promise<Record<string, unknown>> | Record<string, unknown>;\n }\n ).getInitialProps;\n if (typeof getInitialProps !== \"function\") return null;\n\n // Identity check: if the user did not override `static getInitialProps`,\n // the inherited reference is the shim's stub. Skip the call so the\n // fast path keeps the same number of awaits as before this helper landed.\n if (getInitialProps === BASE_GET_INITIAL_PROPS) return null;\n\n // Pass ctx as `{}`. Most upstream overrides only use ctx to delegate\n // back to `Document.getInitialProps`, which the shim ignores. Errors\n // propagate — matching Next.js's `loadGetInitialProps`, which has no\n // catch and surfaces user bugs as 500s.\n const result = await getInitialProps({});\n return result && typeof result === \"object\" ? (result as Record<string, unknown>) : null;\n}\n"],"mappings":";;AA8CA,MAAM,yBACJA,SAGA;AAEF,eAAsB,6BACpB,mBACyC;CACzC,MAAM,kBACJ,kBAKA;CACF,IAAI,OAAO,oBAAoB,YAAY,OAAO;CAKlD,IAAI,oBAAoB,wBAAwB,OAAO;CAMvD,MAAM,SAAS,MAAM,gBAAgB,EAAE,CAAC;CACxC,OAAO,UAAU,OAAO,WAAW,WAAY,SAAqC"}
1
+ {"version":3,"file":"pages-document-initial-props.js","names":["BaseDocument"],"sources":["../../src/server/pages-document-initial-props.ts"],"sourcesContent":["/**\n * Pages Router `_document.tsx` `getInitialProps` helper.\n *\n * Next.js's `pages/_document.tsx` may override\n * `static async getInitialProps(ctx)` to inject extra props onto the\n * Document element (the classic pattern is\n * `await Document.getInitialProps(ctx)` + spread, see Next.js's\n * `test/e2e/async-modules/pages/_document.jsx`). The SSR pipeline invokes\n * that hook and then renders the Document with the resolved props:\n *\n * <Document {...htmlProps} {...docProps} />\n *\n * Reference:\n * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx\n * (search for `loadDocumentInitialProps` and `documentElement`).\n *\n * vinext only forwards `docProps`. The full `DocumentContext`\n * (`renderPage`, `defaultGetInitialProps`, `pathname`, `query`, `req`, `res`,\n * `err`, `asPath`) is not yet plumbed through. The common upstream pattern\n *\n * static async getInitialProps(ctx) {\n * const initialProps = await Document.getInitialProps(ctx)\n * return { ...initialProps, docValue }\n * }\n *\n * works because the base `Document.getInitialProps` shim in\n * `shims/document.tsx` returns `{ html: \"\" }` and ignores `ctx`. User\n * overrides that *only* read `ctx` will see `undefined` fields — that is a\n * separate gap tracked alongside the shim TODO.\n *\n * Returns `null` when the user did not override the base shim (the static\n * `getInitialProps` reference still points at the shim's stub) so callers\n * skip the spread and render the bare Document element on the fast path.\n *\n * Errors from a user `getInitialProps` propagate to the caller. Next.js's\n * `loadGetInitialProps` does not catch — a throw becomes a 500 — and vinext\n * matches that contract so user bugs surface as the loud failures Next.js\n * apps already debug against.\n */\nimport React, { type ComponentType, type ReactNode } from \"react\";\nimport { withScriptNonce } from \"vinext/shims/script-nonce-context\";\n// Static import so the identity comparison below is established once at\n// module evaluation. A previous version used `await import(...)` per request\n// and was flagged by reviewers as unnecessary work — and worse, it left a\n// per-request `await` on the fast path where the user had no override.\nimport BaseDocument from \"vinext/shims/document\";\nimport { readStreamAsText } from \"../utils/text-stream.js\";\n\nconst BASE_GET_INITIAL_PROPS = (\n BaseDocument as unknown as {\n getInitialProps?: unknown;\n }\n).getInitialProps;\n\nexport async function loadUserDocumentInitialProps(\n DocumentComponent: ComponentType,\n): Promise<Record<string, unknown> | null> {\n const getInitialProps = (\n DocumentComponent as unknown as {\n getInitialProps?: (\n ctx: unknown,\n ) => Promise<Record<string, unknown>> | Record<string, unknown>;\n }\n ).getInitialProps;\n if (typeof getInitialProps !== \"function\") return null;\n\n // Identity check: if the user did not override `static getInitialProps`,\n // the inherited reference is the shim's stub. Skip the call so the\n // fast path keeps the same number of awaits as before this helper landed.\n if (getInitialProps === BASE_GET_INITIAL_PROPS) return null;\n\n // Pass ctx as `{}`. Most upstream overrides only use ctx to delegate\n // back to `Document.getInitialProps`, which the shim ignores. Errors\n // propagate — matching Next.js's `loadGetInitialProps`, which has no\n // catch and surfaces user bugs as 500s.\n const result = await getInitialProps({});\n return result && typeof result === \"object\" ? (result as Record<string, unknown>) : null;\n}\n\n/** Options accepted by a `ctx.renderPage()` call (Pages Router contract). */\nexport type RenderPageEnhancers = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n enhanceApp?: (App: ComponentType<{ children?: ReactNode }>) => any;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n enhanceComponent?: (Comp: ComponentType<unknown>) => any;\n};\n\ntype DocumentInitialProps = {\n html: string;\n head?: ReactNode[];\n styles?: ReactNode;\n};\n\ntype DocumentRenderPageInput = {\n /** The user `_document` component (may define `getInitialProps`). */\n DocumentComponent: ComponentType | null;\n /**\n * Build the page React tree with optional App/Component enhancers applied.\n * Callers MUST NOT apply `withScriptNonce` themselves — this helper owns the\n * nonce responsibility so the prod and dev paths stay symmetric.\n */\n enhancePageElement?: ((opts: RenderPageEnhancers) => ReactNode) | undefined;\n /** Render a React tree to a UTF-8 byte stream (prod/dev specific). */\n renderToReadableStream: (element: React.ReactElement) => Promise<ReadableStream<Uint8Array>>;\n /** Render the document `styles` element to an HTML string. */\n renderStylesToString: (element: React.ReactElement) => Promise<string>;\n /** Per-request CSP nonce applied to the enhanced page tree, if any. */\n scriptNonce?: string | undefined;\n /** Extra `DocumentContext` fields (pathname/query/asPath). */\n context?: Record<string, unknown> | undefined;\n};\n\n/**\n * Run a user `_document.getInitialProps()` with a `ctx.renderPage()` that\n * applies optional `enhanceApp` / `enhanceComponent` wrappers around the page\n * React tree, mirroring Next.js's Pages Router contract.\n *\n * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the\n * App/Component tree so styles can be collected during SSR. Shared between the\n * prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so\n * the `getInitialProps` + `renderPage` contract lives in one place.\n *\n * @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)\n *\n * Result of attempting the renderPage contract:\n * - `skipped` — `getInitialProps` was NOT invoked (no override, or no\n * `enhancePageElement` wired up). Callers should run the\n * normal `loadUserDocumentInitialProps` fast path, which may\n * invoke `getInitialProps` itself.\n * - `rendered` — `renderPage` produced the body. `bodyHtml` is the rendered\n * page string, `stylesHTML` the rendered `styles`, `docProps`\n * the remaining props to spread onto `<Document>`, and `head`\n * the head nodes returned by `getInitialProps` (forward them to\n * `setDocumentInitialHead()` — do NOT call\n * `callDocumentGetInitialProps()` as well).\n * - `consumed` — `getInitialProps` WAS invoked but no body was produced\n * (it never called `renderPage`, returned no `{ html }`, or\n * threw). Callers must NOT re-invoke `getInitialProps` (that\n * would call it a second time) — render the streaming body,\n * spread `docProps` (possibly empty) onto `<Document>`, and\n * forward `head` to `setDocumentInitialHead()`.\n */\ntype RunDocumentRenderPageResult =\n | { status: \"skipped\" }\n | {\n status: \"rendered\";\n bodyHtml: string;\n stylesHTML: string;\n docProps: Record<string, unknown>;\n head: ReactNode[];\n }\n | { status: \"consumed\"; docProps: Record<string, unknown>; head: ReactNode[] };\n\n/**\n * Run a user `_document.getInitialProps()` with a `ctx.renderPage()` that\n * applies optional `enhanceApp` / `enhanceComponent` wrappers around the page\n * React tree, mirroring Next.js's Pages Router contract.\n *\n * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the\n * App/Component tree so styles can be collected during SSR. Shared between the\n * prod (`pages-page-response.ts`) and dev (`dev-server.ts`) SSR pipelines so\n * the `getInitialProps` + `renderPage` contract lives in one place.\n *\n * `getInitialProps` is invoked at most once here. When this returns `consumed`\n * or `rendered`, callers MUST treat that as the single invocation and must not\n * call `loadUserDocumentInitialProps` (which would invoke it again — and, for a\n * throwing override, surface the error as a 500 rather than the clean fallback\n * this contract guarantees).\n *\n * @see .nextjs-ref/packages/next/src/server/render.tsx (search `renderPage`)\n */\nexport async function runDocumentRenderPage(\n input: DocumentRenderPageInput,\n): Promise<RunDocumentRenderPageResult> {\n const DocCtor = input.DocumentComponent as\n | (ComponentType & {\n getInitialProps?: (ctx: unknown) => Promise<DocumentInitialProps>;\n displayName?: string;\n })\n | null;\n if (!DocCtor || typeof DocCtor.getInitialProps !== \"function\") return { status: \"skipped\" };\n // Identity check (mirrors `loadUserDocumentInitialProps`): if the user did\n // not override `static getInitialProps`, the inherited reference is the\n // shim's stub. Skip the renderPage work so the fast path stays cheap and the\n // caller falls through to the bare Document render.\n if (DocCtor.getInitialProps === BASE_GET_INITIAL_PROPS) return { status: \"skipped\" };\n if (!input.enhancePageElement) return { status: \"skipped\" };\n const enhancePageElement = input.enhancePageElement;\n\n let renderPageCalled = false;\n const renderPage = async (\n opts: RenderPageEnhancers = {},\n ): Promise<{ html: string; head: ReactNode[] }> => {\n renderPageCalled = true;\n const enhancedElement = enhancePageElement(opts);\n // Nonce responsibility lives here so prod and dev produce identical\n // output — callers' `enhancePageElement` must not apply it themselves.\n const wrapped = withScriptNonce(enhancedElement as React.ReactElement, input.scriptNonce);\n const stream = await input.renderToReadableStream(wrapped);\n const html = await readStreamAsText(stream);\n return { html, head: [] };\n };\n\n let docInitialProps: DocumentInitialProps;\n try {\n docInitialProps = await DocCtor.getInitialProps({\n // Minimal `DocumentContext` shim — vinext does not yet thread the full\n // context (req/res/AppTree/locale). Subclasses that just forward to\n // `ctx.renderPage` (the styled-components / emotion pattern) work\n // without those fields.\n renderPage,\n defaultGetInitialProps: async (ctx: { renderPage?: typeof renderPage }) => {\n // Mirrors Next.js's `ctx.defaultGetInitialProps`: wrap App in an\n // identity enhancer so renderPage is still invoked even when a user\n // doesn't pass any enhancers themselves.\n const inner = ctx.renderPage ?? renderPage;\n const result = await inner({\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n enhanceApp: (App) => (props: any) => React.createElement(App, props),\n });\n return { html: result.html, head: result.head ?? [], styles: undefined };\n },\n ...input.context,\n });\n } catch (err) {\n // Falls back cleanly: render the streaming body and a bare Document.\n // `getInitialProps` was already invoked, so the caller must not re-call it.\n console.error(\"[vinext] _document.getInitialProps() threw:\", err);\n return { status: \"consumed\", docProps: {}, head: [] };\n }\n\n // Strip the contract fields the pipeline consumes itself so the rest can be\n // spread onto `<Document>` like Next.js does. `html` is the body; `head`/\n // `styles` are merged into the SSR head. `head` is surfaced back to the\n // caller so it can be folded into the dedupe pipeline via\n // `setDocumentInitialHead()` — `getInitialProps` is only ever invoked once\n // (here), so the standalone `callDocumentGetInitialProps()` path must not\n // run again for the same render.\n const { html: _html, head: rawHead, styles: _styles, ...docProps } = docInitialProps ?? {};\n const head: ReactNode[] = Array.isArray(rawHead) ? (rawHead as ReactNode[]) : [];\n\n // If the user implemented getInitialProps but never invoked renderPage\n // (uncommon — but possible if they only return head/styles), fall back to\n // the streaming render so the body content is produced normally.\n if (!renderPageCalled) return { status: \"consumed\", docProps, head };\n\n if (!docInitialProps || typeof docInitialProps.html !== \"string\") {\n console.error(\n `[vinext] \"${DocCtor.displayName ?? DocCtor.name ?? \"Document\"}.getInitialProps()\" did not return an object with a string \"html\" prop`,\n );\n return { status: \"consumed\", docProps, head };\n }\n\n // Render `styles` returned by `getInitialProps()` (e.g. collected\n // styled-components / emotion <style> tags) to a string ready for the SSR\n // head. Matches Next.js's render.tsx where `styles` flows into the head.\n // Failures are swallowed so a buggy styles element doesn't crash the render.\n let stylesHTML = \"\";\n if (docInitialProps.styles != null) {\n try {\n stylesHTML = await input.renderStylesToString(\n React.createElement(React.Fragment, null, docInitialProps.styles),\n );\n } catch (err) {\n console.error(\"[vinext] Failed to render _document.getInitialProps() styles:\", err);\n }\n }\n\n return { status: \"rendered\", bodyHtml: docInitialProps.html, stylesHTML, docProps, head };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,MAAM,yBACJA,SAGA;AAEF,eAAsB,6BACpB,mBACyC;CACzC,MAAM,kBACJ,kBAKA;CACF,IAAI,OAAO,oBAAoB,YAAY,OAAO;CAKlD,IAAI,oBAAoB,wBAAwB,OAAO;CAMvD,MAAM,SAAS,MAAM,gBAAgB,EAAE,CAAC;CACxC,OAAO,UAAU,OAAO,WAAW,WAAY,SAAqC;;;;;;;;;;;;;;;;;;;;AA+FtF,eAAsB,sBACpB,OACsC;CACtC,MAAM,UAAU,MAAM;CAMtB,IAAI,CAAC,WAAW,OAAO,QAAQ,oBAAoB,YAAY,OAAO,EAAE,QAAQ,WAAW;CAK3F,IAAI,QAAQ,oBAAoB,wBAAwB,OAAO,EAAE,QAAQ,WAAW;CACpF,IAAI,CAAC,MAAM,oBAAoB,OAAO,EAAE,QAAQ,WAAW;CAC3D,MAAM,qBAAqB,MAAM;CAEjC,IAAI,mBAAmB;CACvB,MAAM,aAAa,OACjB,OAA4B,EAAE,KACmB;EACjD,mBAAmB;EAInB,MAAM,UAAU,gBAHQ,mBAAmB,KAGI,EAAwB,MAAM,YAAY;EAGzF,OAAO;GAAE,MAAA,MADU,iBAAiB,MADf,MAAM,uBAAuB,QAAQ,CACf;GAC5B,MAAM,EAAE;GAAE;;CAG3B,IAAI;CACJ,IAAI;EACF,kBAAkB,MAAM,QAAQ,gBAAgB;GAK9C;GACA,wBAAwB,OAAO,QAA4C;IAKzE,MAAM,SAAS,OADD,IAAI,cAAc,YACL,EAEzB,aAAa,SAAS,UAAe,MAAM,cAAc,KAAK,MAAM,EACrE,CAAC;IACF,OAAO;KAAE,MAAM,OAAO;KAAM,MAAM,OAAO,QAAQ,EAAE;KAAE,QAAQ,KAAA;KAAW;;GAE1E,GAAG,MAAM;GACV,CAAC;UACK,KAAK;EAGZ,QAAQ,MAAM,+CAA+C,IAAI;EACjE,OAAO;GAAE,QAAQ;GAAY,UAAU,EAAE;GAAE,MAAM,EAAE;GAAE;;CAUvD,MAAM,EAAE,MAAM,OAAO,MAAM,SAAS,QAAQ,SAAS,GAAG,aAAa,mBAAmB,EAAE;CAC1F,MAAM,OAAoB,MAAM,QAAQ,QAAQ,GAAI,UAA0B,EAAE;CAKhF,IAAI,CAAC,kBAAkB,OAAO;EAAE,QAAQ;EAAY;EAAU;EAAM;CAEpE,IAAI,CAAC,mBAAmB,OAAO,gBAAgB,SAAS,UAAU;EAChE,QAAQ,MACN,aAAa,QAAQ,eAAe,QAAQ,QAAQ,WAAW,wEAChE;EACD,OAAO;GAAE,QAAQ;GAAY;GAAU;GAAM;;CAO/C,IAAI,aAAa;CACjB,IAAI,gBAAgB,UAAU,MAC5B,IAAI;EACF,aAAa,MAAM,MAAM,qBACvB,MAAM,cAAc,MAAM,UAAU,MAAM,gBAAgB,OAAO,CAClE;UACM,KAAK;EACZ,QAAQ,MAAM,iEAAiE,IAAI;;CAIvF,OAAO;EAAE,QAAQ;EAAY,UAAU,gBAAgB;EAAM;EAAY;EAAU;EAAM"}
@@ -1,7 +1,7 @@
1
1
  import { parseCookies } from "../config/config-matchers.js";
2
+ import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
2
3
  import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
3
4
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT } from "./pages-body-parser-config.js";
4
- import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
5
5
  import { decode } from "node:querystring";
6
6
  //#region src/server/pages-node-compat.ts
7
7
  const MAX_PAGES_API_BODY_SIZE = DEFAULT_PAGES_API_BODY_SIZE_LIMIT;
@@ -1,5 +1,6 @@
1
1
  import { VinextNextData } from "../client/vinext-next-data.js";
2
2
  import { CachedPagesValue } from "../shims/cache.js";
3
+ import { RenderPageEnhancers } from "./pages-document-initial-props.js";
3
4
  import { ComponentType, ReactNode } from "react";
4
5
 
5
6
  //#region src/server/pages-page-response.d.ts
@@ -22,6 +23,18 @@ type RenderPagesPageResponseOptions = {
22
23
  buildId: string | null;
23
24
  clearSsrContext: () => void;
24
25
  createPageElement: (pageProps: Record<string, unknown>) => ReactNode;
26
+ /**
27
+ * Build the page React tree with optional App/Component enhancers applied,
28
+ * supporting the Pages Router `_document.getInitialProps` contract:
29
+ *
30
+ * ctx.renderPage({ enhanceApp, enhanceComponent })
31
+ *
32
+ * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the
33
+ * App/Component tree so styles can be collected during SSR. When omitted,
34
+ * `renderPage` falls back to rendering the plain `createPageElement` tree
35
+ * (enhancers are ignored).
36
+ */
37
+ enhancePageElement?: ((opts: RenderPageEnhancers) => ReactNode) | undefined;
25
38
  DocumentComponent: ComponentType | null;
26
39
  flushPreloads?: (() => Promise<void> | void) | undefined;
27
40
  fontLinkHeader: string;
@@ -35,6 +48,7 @@ type RenderPagesPageResponseOptions = {
35
48
  * head. Undefined or empty disables emission.
36
49
  */
37
50
  clientTraceMetadata?: readonly string[] | undefined;
51
+ setDocumentInitialHead?: ((head: ReactNode[]) => void) | undefined;
38
52
  gsspRes: PagesGsspResponse | null;
39
53
  isrCacheKey: (router: string, pathname: string) => string;
40
54
  expireSeconds?: number;
@@ -4,8 +4,9 @@ import { setCacheStateHeaders } from "./cache-headers.js";
4
4
  import { withScriptNonce } from "../shims/script-nonce-context.js";
5
5
  import { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr } from "./html.js";
6
6
  import { getClientTraceMetadataHTML } from "./client-trace-metadata.js";
7
- import { loadUserDocumentInitialProps } from "./pages-document-initial-props.js";
8
7
  import { readStreamAsText } from "../utils/text-stream.js";
8
+ import { loadUserDocumentInitialProps, runDocumentRenderPage } from "./pages-document-initial-props.js";
9
+ import { callDocumentGetInitialProps } from "./document-initial-head.js";
9
10
  import { buildRevalidateCacheControl } from "./cache-control.js";
10
11
  import React from "react";
11
12
  //#region src/server/pages-page-response.ts
@@ -37,7 +38,7 @@ function buildPagesNextDataScript(options) {
37
38
  }
38
39
  async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, options) {
39
40
  if (options.DocumentComponent) {
40
- const docProps = await loadUserDocumentInitialProps(options.DocumentComponent);
41
+ const docProps = options.resolvedDocProps ?? await loadUserDocumentInitialProps(options.DocumentComponent);
41
42
  const docElement = docProps ? React.createElement(options.DocumentComponent, docProps) : React.createElement(options.DocumentComponent);
42
43
  let html = await options.renderDocumentToString(docElement);
43
44
  html = html.replace("__NEXT_MAIN__", bodyMarker);
@@ -49,8 +50,6 @@ async function buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, opt
49
50
  return `<!DOCTYPE html>
50
51
  <html>
51
52
  <head>
52
- <meta charset="utf-8" />
53
- <meta name="viewport" content="width=device-width, initial-scale=1" />
54
53
  ${fontHeadHTML}${options.ssrHeadHTML}\n ${options.assetTags}\n</head>
55
54
  <body>
56
55
  <div id="__next">${bodyMarker}</div>\n ${nextDataScript}\n</body>
@@ -117,7 +116,6 @@ function applyGsspHeaders(headers, gsspRes, statusCode) {
117
116
  return statusCode ?? gsspRes.statusCode;
118
117
  }
119
118
  async function renderPagesPageResponse(options) {
120
- const pageElement = withScriptNonce(React.createElement(React.Fragment, null, options.createPageElement(options.pageProps)), options.scriptNonce);
121
119
  options.resetSSRHead?.();
122
120
  await options.flushPreloads?.();
123
121
  const fontHeadHTML = buildPagesFontHeadHtml(options.getFontLinks(), options.fontPreloads, options.getFontStyles(), options.scriptNonce);
@@ -133,15 +131,40 @@ async function renderPagesPageResponse(options) {
133
131
  vinext: options.vinext
134
132
  });
135
133
  const bodyMarker = "<!--VINEXT_STREAM_BODY-->";
136
- const bodyStream = await options.renderToReadableStream(pageElement);
134
+ const documentRenderPage = await runDocumentRenderPage({
135
+ DocumentComponent: options.DocumentComponent,
136
+ enhancePageElement: options.enhancePageElement,
137
+ renderToReadableStream: options.renderToReadableStream,
138
+ renderStylesToString: async (element) => readStreamAsText(await options.renderToReadableStream(element)),
139
+ scriptNonce: options.scriptNonce,
140
+ context: {
141
+ pathname: options.routePattern,
142
+ query: options.params,
143
+ asPath: options.routeUrl
144
+ }
145
+ });
146
+ let bodyStream;
147
+ if (documentRenderPage.status === "rendered") bodyStream = new ReadableStream({ start(controller) {
148
+ controller.enqueue(new TextEncoder().encode(documentRenderPage.bodyHtml));
149
+ controller.close();
150
+ } });
151
+ else {
152
+ const pageElement = withScriptNonce(React.createElement(React.Fragment, null, options.createPageElement(options.pageProps)), options.scriptNonce);
153
+ bodyStream = await options.renderToReadableStream(pageElement);
154
+ }
155
+ if (documentRenderPage.status === "skipped") await callDocumentGetInitialProps(options.DocumentComponent, options.setDocumentInitialHead);
156
+ else options.setDocumentInitialHead?.(documentRenderPage.head);
137
157
  const headFromShim = options.getSSRHeadHTML?.() ?? "";
138
158
  const traceMetaHTML = getClientTraceMetadataHTML(options.clientTraceMetadata);
139
- const ssrHeadHTML = traceMetaHTML ? `${headFromShim}\n ${traceMetaHTML}` : headFromShim;
159
+ let ssrHeadHTML = headFromShim;
160
+ if (traceMetaHTML) ssrHeadHTML += `\n ${traceMetaHTML}`;
161
+ if (documentRenderPage.status === "rendered" && documentRenderPage.stylesHTML) ssrHeadHTML += `\n ${documentRenderPage.stylesHTML}`;
140
162
  const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {
141
163
  assetTags: options.assetTags,
142
164
  DocumentComponent: options.DocumentComponent,
143
165
  renderDocumentToString: options.renderDocumentToString,
144
- ssrHeadHTML
166
+ ssrHeadHTML,
167
+ resolvedDocProps: documentRenderPage.status === "skipped" ? null : documentRenderPage.docProps
145
168
  });
146
169
  options.clearSsrContext();
147
170
  const markerIndex = shellHtml.indexOf(bodyMarker);
@@ -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\";\nimport type { VinextNextData } from \"../client/vinext-next-data.js\";\nimport type { CachedPagesValue } from \"vinext/shims/cache\";\nimport { withScriptNonce } from \"vinext/shims/script-nonce-context\";\nimport { getRequestExecutionContext } from \"vinext/shims/request-context\";\nimport { buildRevalidateCacheControl } from \"./cache-control.js\";\nimport { setCacheStateHeaders } from \"./cache-headers.js\";\nimport { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr } from \"./html.js\";\nimport { getClientTraceMetadataHTML } from \"./client-trace-metadata.js\";\nimport { reportRequestError } from \"./instrumentation.js\";\nimport { loadUserDocumentInitialProps } from \"./pages-document-initial-props.js\";\nimport { readStreamAsText } from \"../utils/text-stream.js\";\n\ntype 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\ntype 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 /**\n * Allow-list of OpenTelemetry propagation keys (from\n * `experimental.clientTraceMetadata`) to emit as `<meta>` tags in the SSR\n * head. Undefined or empty disables emission.\n */\n clientTraceMetadata?: readonly string[] | undefined;\n gsspRes: PagesGsspResponse | null;\n isrCacheKey: (router: string, pathname: string) => string;\n expireSeconds?: number;\n isrRevalidateSeconds: number | null;\n isrSet: (\n key: string,\n data: CachedPagesValue,\n revalidateSeconds: number,\n tags?: string[],\n expireSeconds?: number,\n ) => Promise<void>;\n i18n: PagesI18nRenderContext;\n /**\n * True when rendering a `getStaticPaths` fallback shell for a path that\n * isn't pre-rendered (`fallback: true` + unlisted path). Forwarded to\n * `buildPagesNextDataScript` so the client serialises `isFallback: true`\n * into `__NEXT_DATA__`, then later hydrates by fetching the data URL.\n */\n isFallback?: boolean;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderDocumentToString: (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 statusCode?: number;\n vinext?: VinextNextData[\"__vinext\"];\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 | \"isFallback\"\n | \"pageProps\"\n | \"params\"\n | \"routePattern\"\n | \"safeJsonStringify\"\n | \"scriptNonce\"\n > & {\n vinext?: VinextNextData[\"__vinext\"];\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: options.isFallback === true,\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 if (options.vinext) {\n nextDataPayload.__vinext = options.vinext;\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 const docProps = await loadUserDocumentInitialProps(options.DocumentComponent);\n const docElement = docProps\n ? React.createElement(options.DocumentComponent, docProps)\n : React.createElement(options.DocumentComponent);\n let html = await options.renderDocumentToString(docElement);\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\nasync function reportPagesIsrCacheWriteError(\n error: unknown,\n cacheKey: string,\n routePattern: string,\n): Promise<void> {\n console.error(`[vinext] Pages ISR cache write failed for ${cacheKey}:`, error);\n try {\n await reportRequestError(\n error instanceof Error ? error : new Error(String(error)),\n { path: cacheKey, method: \"GET\", headers: {} },\n {\n routerKind: \"Pages Router\",\n routePath: routePattern,\n routeType: \"render\",\n },\n );\n } catch {\n // Cache-write failure reporting must never make the background task reject.\n }\n}\n\nfunction schedulePagesIsrCacheWrite(options: {\n cacheKey: string;\n expireSeconds?: number;\n pageData: Record<string, unknown>;\n revalidateSeconds: number;\n routePattern: string;\n shellPrefix: string;\n shellSuffix: string;\n status: number;\n stream: ReadableStream<Uint8Array>;\n setCache: RenderPagesPageResponseOptions[\"isrSet\"];\n}): void {\n const cacheWritePromise = readStreamAsText(options.stream)\n .then((bodyHtml) =>\n options.setCache(\n options.cacheKey,\n {\n kind: \"PAGES\",\n html: options.shellPrefix + bodyHtml + options.shellSuffix,\n pageData: options.pageData,\n headers: undefined,\n status: options.status,\n },\n options.revalidateSeconds,\n undefined,\n options.expireSeconds,\n ),\n )\n .catch((error: unknown) =>\n reportPagesIsrCacheWriteError(error, options.cacheKey, options.routePattern),\n );\n\n getRequestExecutionContext()?.waitUntil(cacheWritePromise);\n}\n\nfunction applyGsspHeaders(\n headers: Headers,\n gsspRes: PagesGsspResponse | null,\n statusCode?: number,\n): number {\n if (!gsspRes) {\n return statusCode ?? 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 statusCode ?? 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 isFallback: options.isFallback,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n scriptNonce: options.scriptNonce,\n vinext: options.vinext,\n });\n const bodyMarker = \"<!--VINEXT_STREAM_BODY-->\";\n // Render the page FIRST so that <Head> and other SSR state collectors\n // (e.g. styled-jsx, useServerInsertedHTML) are populated before we read\n // them. This fixes a race condition where head styles were silently dropped\n // because they were collected before the page had finished rendering.\n // Mirrors Next.js fix: vercel/next.js@9853944\n const bodyStream = await options.renderToReadableStream(pageElement);\n\n const headFromShim = options.getSSRHeadHTML?.() ?? \"\";\n // Trace meta tags from the active OpenTelemetry context. When the\n // allow-list is unset (the common case) or OTel is not installed,\n // `getClientTraceMetadataHTML` returns \"\" and we forward the head HTML\n // verbatim — keeping the no-op path zero-overhead.\n const traceMetaHTML = getClientTraceMetadataHTML(options.clientTraceMetadata);\n const ssrHeadHTML = traceMetaHTML ? `${headFromShim}\\n ${traceMetaHTML}` : headFromShim;\n const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML,\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 responseHeaders = new Headers({ \"Content-Type\": \"text/html\" });\n const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes, options.statusCode);\n\n let responseBodyStream = bodyStream;\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 cacheBodyStreamPair = bodyStream.tee();\n responseBodyStream = cacheBodyStreamPair[0];\n const cacheBodyStream = cacheBodyStreamPair[1];\n const isrPathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", isrPathname);\n\n schedulePagesIsrCacheWrite({\n cacheKey,\n expireSeconds: options.expireSeconds,\n pageData: options.pageProps,\n revalidateSeconds: options.isrRevalidateSeconds,\n routePattern: options.routePattern,\n setCache: options.isrSet,\n shellPrefix,\n shellSuffix,\n status: finalStatus,\n stream: cacheBodyStream,\n });\n }\n\n const compositeStream = await buildPagesCompositeStream(\n responseBodyStream,\n shellPrefix,\n shellSuffix,\n );\n\n // Capture user-set Cache-Control (from getServerSideProps's res.setHeader)\n // so a downstream user override survives the gssp default below, and only\n // the default, never ISR/nonce Cache-Control which the runtime owns. Matches\n // Next.js's pages-handler.ts: `if (!res.getHeader('Cache-Control'))`.\n // responseHeaders/finalStatus are declared above so finalStatus can also feed\n // the ISR cache write; applyGsspHeaders is the only Cache-Control writer before\n // this point, so the captured value matches main's original capture site.\n const userSetCacheControl = responseHeaders.has(\"Cache-Control\");\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 buildRevalidateCacheControl(options.isrRevalidateSeconds, options.expireSeconds),\n );\n setCacheStateHeaders(responseHeaders, \"MISS\");\n } else if (options.gsspRes && !userSetCacheControl) {\n // Default for getServerSideProps responses, matching Next.js\n // pages-handler.ts (revalidate: 0 → getCacheControlHeader). Without this,\n // CDNs and browsers could cache per-request gssp responses.\n responseHeaders.set(\"Cache-Control\", \"private, no-cache, no-store, max-age=0, must-revalidate\");\n }\n if (options.fontLinkHeader) {\n responseHeaders.set(\"Link\", options.fontLinkHeader);\n }\n\n const response: PagesStreamedHtmlResponse = Object.assign(\n new Response(compositeStream, {\n status: finalStatus,\n headers: responseHeaders,\n }),\n {\n __vinextStreamedHtmlResponse: true,\n },\n );\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 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 type { VinextNextData } from \"../client/vinext-next-data.js\";\nimport type { CachedPagesValue } from \"vinext/shims/cache\";\nimport { withScriptNonce } from \"vinext/shims/script-nonce-context\";\nimport { getRequestExecutionContext } from \"vinext/shims/request-context\";\nimport { buildRevalidateCacheControl } from \"./cache-control.js\";\nimport { setCacheStateHeaders } from \"./cache-headers.js\";\nimport { createInlineScriptTag, createNonceAttribute, escapeHtmlAttr } from \"./html.js\";\nimport { getClientTraceMetadataHTML } from \"./client-trace-metadata.js\";\nimport { reportRequestError } from \"./instrumentation.js\";\nimport {\n loadUserDocumentInitialProps,\n type RenderPageEnhancers,\n runDocumentRenderPage,\n} from \"./pages-document-initial-props.js\";\nimport { readStreamAsText } from \"../utils/text-stream.js\";\nimport { callDocumentGetInitialProps } from \"./document-initial-head.js\";\n\ntype 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\ntype RenderPagesPageResponseOptions = {\n assetTags: string;\n buildId: string | null;\n clearSsrContext: () => void;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n /**\n * Build the page React tree with optional App/Component enhancers applied,\n * supporting the Pages Router `_document.getInitialProps` contract:\n *\n * ctx.renderPage({ enhanceApp, enhanceComponent })\n *\n * Used by CSS-in-JS libraries (styled-components, emotion) to wrap the\n * App/Component tree so styles can be collected during SSR. When omitted,\n * `renderPage` falls back to rendering the plain `createPageElement` tree\n * (enhancers are ignored).\n */\n enhancePageElement?: ((opts: RenderPageEnhancers) => ReactNode) | undefined;\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 /**\n * Allow-list of OpenTelemetry propagation keys (from\n * `experimental.clientTraceMetadata`) to emit as `<meta>` tags in the SSR\n * head. Undefined or empty disables emission.\n */\n clientTraceMetadata?: readonly string[] | undefined;\n setDocumentInitialHead?: ((head: ReactNode[]) => void) | undefined;\n gsspRes: PagesGsspResponse | null;\n isrCacheKey: (router: string, pathname: string) => string;\n expireSeconds?: number;\n isrRevalidateSeconds: number | null;\n isrSet: (\n key: string,\n data: CachedPagesValue,\n revalidateSeconds: number,\n tags?: string[],\n expireSeconds?: number,\n ) => Promise<void>;\n i18n: PagesI18nRenderContext;\n /**\n * True when rendering a `getStaticPaths` fallback shell for a path that\n * isn't pre-rendered (`fallback: true` + unlisted path). Forwarded to\n * `buildPagesNextDataScript` so the client serialises `isFallback: true`\n * into `__NEXT_DATA__`, then later hydrates by fetching the data URL.\n */\n isFallback?: boolean;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderDocumentToString: (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 statusCode?: number;\n vinext?: VinextNextData[\"__vinext\"];\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 | \"isFallback\"\n | \"pageProps\"\n | \"params\"\n | \"routePattern\"\n | \"safeJsonStringify\"\n | \"scriptNonce\"\n > & {\n vinext?: VinextNextData[\"__vinext\"];\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: options.isFallback === true,\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 if (options.vinext) {\n nextDataPayload.__vinext = options.vinext;\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 * Document props already resolved by `runDocumentRenderPage`. When set,\n * `getInitialProps` was consumed by the renderPage path and must not be\n * re-invoked via `loadUserDocumentInitialProps` (which would call it a\n * second time). `null` means use the normal fast path.\n */\n resolvedDocProps?: Record<string, unknown> | null;\n },\n): Promise<string> {\n if (options.DocumentComponent) {\n const docProps =\n options.resolvedDocProps ?? (await loadUserDocumentInitialProps(options.DocumentComponent));\n const docElement = docProps\n ? React.createElement(options.DocumentComponent, docProps)\n : React.createElement(options.DocumentComponent);\n let html = await options.renderDocumentToString(docElement);\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 // charset + viewport are emitted via getSSRHeadHTML() (next/head's\n // defaultHead seeds them with data-next-head=\"\"), matching Next.js's\n // canonical ordering. Don't duplicate them here.\n return (\n \"<!DOCTYPE html>\\n<html>\\n<head>\\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\nasync function reportPagesIsrCacheWriteError(\n error: unknown,\n cacheKey: string,\n routePattern: string,\n): Promise<void> {\n console.error(`[vinext] Pages ISR cache write failed for ${cacheKey}:`, error);\n try {\n await reportRequestError(\n error instanceof Error ? error : new Error(String(error)),\n { path: cacheKey, method: \"GET\", headers: {} },\n {\n routerKind: \"Pages Router\",\n routePath: routePattern,\n routeType: \"render\",\n },\n );\n } catch {\n // Cache-write failure reporting must never make the background task reject.\n }\n}\n\nfunction schedulePagesIsrCacheWrite(options: {\n cacheKey: string;\n expireSeconds?: number;\n pageData: Record<string, unknown>;\n revalidateSeconds: number;\n routePattern: string;\n shellPrefix: string;\n shellSuffix: string;\n status: number;\n stream: ReadableStream<Uint8Array>;\n setCache: RenderPagesPageResponseOptions[\"isrSet\"];\n}): void {\n const cacheWritePromise = readStreamAsText(options.stream)\n .then((bodyHtml) =>\n options.setCache(\n options.cacheKey,\n {\n kind: \"PAGES\",\n html: options.shellPrefix + bodyHtml + options.shellSuffix,\n pageData: options.pageData,\n headers: undefined,\n status: options.status,\n },\n options.revalidateSeconds,\n undefined,\n options.expireSeconds,\n ),\n )\n .catch((error: unknown) =>\n reportPagesIsrCacheWriteError(error, options.cacheKey, options.routePattern),\n );\n\n getRequestExecutionContext()?.waitUntil(cacheWritePromise);\n}\n\nfunction applyGsspHeaders(\n headers: Headers,\n gsspRes: PagesGsspResponse | null,\n statusCode?: number,\n): number {\n if (!gsspRes) {\n return statusCode ?? 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 statusCode ?? gsspRes.statusCode;\n}\n\nexport async function renderPagesPageResponse(\n options: RenderPagesPageResponseOptions,\n): Promise<Response> {\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 isFallback: options.isFallback,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n scriptNonce: options.scriptNonce,\n vinext: options.vinext,\n });\n const bodyMarker = \"<!--VINEXT_STREAM_BODY-->\";\n\n // Custom `_document.getInitialProps()` may opt in to wrapping the page tree\n // via `ctx.renderPage({ enhanceApp, enhanceComponent })` (e.g. for\n // styled-components / emotion style collection). When that contract is in\n // use the body must be a single complete string before `_document` renders\n // — Next.js does this in `loadDocumentInitialProps` and we mirror it here.\n // The streaming path stays as the default for the common case where the\n // user does not define `getInitialProps`. The contract (including\n // `withScriptNonce` and `styles` rendering) lives in the shared helper so\n // prod and dev stay in lockstep.\n const documentRenderPage = await runDocumentRenderPage({\n DocumentComponent: options.DocumentComponent,\n enhancePageElement: options.enhancePageElement,\n renderToReadableStream: options.renderToReadableStream,\n // Render the collected `styles` fragment with the plain stream renderer\n // rather than the full `<Document>` shell renderer — the styles tree is a\n // standalone fragment, so it doesn't need the heavier document pipeline.\n // Mirrors the dev path, which passes its `renderToStringAsync` wrapper.\n renderStylesToString: async (element) =>\n readStreamAsText(await options.renderToReadableStream(element)),\n scriptNonce: options.scriptNonce,\n context: {\n pathname: options.routePattern,\n query: options.params,\n asPath: options.routeUrl,\n },\n });\n\n let bodyStream: ReadableStream<Uint8Array>;\n if (documentRenderPage.status === \"rendered\") {\n bodyStream = new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(new TextEncoder().encode(documentRenderPage.bodyHtml));\n controller.close();\n },\n });\n } else {\n // Render the page FIRST so that <Head> and other SSR state collectors\n // (e.g. styled-jsx, useServerInsertedHTML) are populated before we read\n // them. This fixes a race condition where head styles were silently dropped\n // because they were collected before the page had finished rendering.\n // Mirrors Next.js fix: vercel/next.js@9853944\n //\n // Built lazily here: when the renderPage contract produced the body\n // (`rendered`), this element is never used, so there's no point\n // constructing the tree on that path.\n const pageElement = withScriptNonce(\n React.createElement(React.Fragment, null, options.createPageElement(options.pageProps)),\n options.scriptNonce,\n );\n bodyStream = await options.renderToReadableStream(pageElement);\n }\n\n // Fold any head tags returned by `_document.getInitialProps()` into the\n // dedupe pipeline before getSSRHeadHTML serialises the final <head>. Mirrors\n // Next.js's `_document` contract. `runDocumentRenderPage` already invokes\n // `getInitialProps` for the renderPage contract (rendered/consumed), so reuse\n // the head it surfaced rather than calling it a second time. Only the\n // `skipped` path (no override, or no `enhancePageElement` wired) falls back to\n // the standalone helper — which itself skips the unmodified default shim.\n if (documentRenderPage.status === \"skipped\") {\n await callDocumentGetInitialProps(options.DocumentComponent, options.setDocumentInitialHead);\n } else {\n options.setDocumentInitialHead?.(documentRenderPage.head);\n }\n\n const headFromShim = options.getSSRHeadHTML?.() ?? \"\";\n // Trace meta tags from the active OpenTelemetry context. When the\n // allow-list is unset (the common case) or OTel is not installed,\n // `getClientTraceMetadataHTML` returns \"\" and we forward the head HTML\n // verbatim — keeping the no-op path zero-overhead.\n const traceMetaHTML = getClientTraceMetadataHTML(options.clientTraceMetadata);\n let ssrHeadHTML = headFromShim;\n if (traceMetaHTML) ssrHeadHTML += `\\n ${traceMetaHTML}`;\n // `styles` returned by `_document.getInitialProps()` (e.g. collected\n // styled-components / emotion <style> tags) is already rendered to a string\n // by the shared helper, ready to merge into the SSR head.\n if (documentRenderPage.status === \"rendered\" && documentRenderPage.stylesHTML) {\n ssrHeadHTML += `\\n ${documentRenderPage.stylesHTML}`;\n }\n const shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML,\n // When the renderPage path already invoked getInitialProps (rendered or\n // consumed), reuse its resolved props instead of calling it a second time.\n // `skipped` means it was never invoked → fall through to the fast path.\n resolvedDocProps: documentRenderPage.status === \"skipped\" ? null : documentRenderPage.docProps,\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 responseHeaders = new Headers({ \"Content-Type\": \"text/html\" });\n const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes, options.statusCode);\n\n let responseBodyStream = bodyStream;\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 cacheBodyStreamPair = bodyStream.tee();\n responseBodyStream = cacheBodyStreamPair[0];\n const cacheBodyStream = cacheBodyStreamPair[1];\n const isrPathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", isrPathname);\n\n schedulePagesIsrCacheWrite({\n cacheKey,\n expireSeconds: options.expireSeconds,\n pageData: options.pageProps,\n revalidateSeconds: options.isrRevalidateSeconds,\n routePattern: options.routePattern,\n setCache: options.isrSet,\n shellPrefix,\n shellSuffix,\n status: finalStatus,\n stream: cacheBodyStream,\n });\n }\n\n const compositeStream = await buildPagesCompositeStream(\n responseBodyStream,\n shellPrefix,\n shellSuffix,\n );\n\n // Capture user-set Cache-Control (from getServerSideProps's res.setHeader)\n // so a downstream user override survives the gssp default below, and only\n // the default, never ISR/nonce Cache-Control which the runtime owns. Matches\n // Next.js's pages-handler.ts: `if (!res.getHeader('Cache-Control'))`.\n // responseHeaders/finalStatus are declared above so finalStatus can also feed\n // the ISR cache write; applyGsspHeaders is the only Cache-Control writer before\n // this point, so the captured value matches main's original capture site.\n const userSetCacheControl = responseHeaders.has(\"Cache-Control\");\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 buildRevalidateCacheControl(options.isrRevalidateSeconds, options.expireSeconds),\n );\n setCacheStateHeaders(responseHeaders, \"MISS\");\n } else if (options.gsspRes && !userSetCacheControl) {\n // Default for getServerSideProps responses, matching Next.js\n // pages-handler.ts (revalidate: 0 → getCacheControlHeader). Without this,\n // CDNs and browsers could cache per-request gssp responses.\n responseHeaders.set(\"Cache-Control\", \"private, no-cache, no-store, max-age=0, must-revalidate\");\n }\n if (options.fontLinkHeader) {\n responseHeaders.set(\"Link\", options.fontLinkHeader);\n }\n\n const response: PagesStreamedHtmlResponse = Object.assign(\n new Response(compositeStream, {\n status: finalStatus,\n headers: responseHeaders,\n }),\n {\n __vinextStreamedHtmlResponse: true,\n },\n );\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 return response;\n}\n"],"mappings":"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"}
@@ -10,13 +10,14 @@ import { normalizeDefaultLocalePathname, stripI18nLocaleForApiRoute } from "./pa
10
10
  import { resolveRequestHost, resolveRequestProtocol, trustProxy, trustedHosts } from "./proxy-trust.js";
11
11
  import { DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isImageOptimizationPath, isSafeImageContentType, parseImageParams } from "./image-optimization.js";
12
12
  import { installSocketErrorBackstop } from "./socket-error-backstop.js";
13
- import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix } from "../utils/asset-prefix.js";
13
+ import { ASSET_PREFIX_URL_DIR, assetPrefixPathname, isAbsoluteAssetPrefix, resolveAssetsDir } from "../utils/asset-prefix.js";
14
14
  import { CONTENT_TYPES, StaticFileCache, etagFromFilenameHash } from "./static-file-cache.js";
15
15
  import { buildNextDataNotFoundResponse, isNextDataPathname, parseNextDataPathname } from "./pages-data-route.js";
16
16
  import { collectInlineCssManifest } from "../build/inline-css.js";
17
17
  import { manifestFileWithBase } from "../utils/manifest-paths.js";
18
18
  import { readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader } from "./prerender-route-params.js";
19
19
  import { computeLazyChunks } from "../utils/lazy-chunks.js";
20
+ import { findClientEntryFile, readClientBuildManifest } from "../utils/client-build-manifest.js";
20
21
  import { readPrerenderSecret } from "../build/server-manifest.js";
21
22
  import { seedMemoryCacheFromPrerender } from "./seed-cache.js";
22
23
  import fs from "node:fs";
@@ -813,11 +814,17 @@ async function startPagesRouterServer(options) {
813
814
  let ssrManifest = {};
814
815
  const manifestPath = path.join(clientDir, ".vite", "ssr-manifest.json");
815
816
  if (fs.existsSync(manifestPath)) ssrManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
816
- const buildManifestPath = path.join(clientDir, ".vite", "manifest.json");
817
- if (fs.existsSync(buildManifestPath)) try {
818
- const lazyChunks = computeLazyChunks(JSON.parse(fs.readFileSync(buildManifestPath, "utf-8"))).map((file) => manifestFileWithBase(file, assetBase));
819
- if (lazyChunks.length > 0) globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks;
820
- } catch {}
817
+ const buildManifest = readClientBuildManifest(path.join(clientDir, ".vite", "manifest.json"));
818
+ globalThis.__VINEXT_CLIENT_ENTRY__ = findClientEntryFile({
819
+ buildManifest,
820
+ clientDir,
821
+ assetsSubdir: resolveAssetsDir(assetPrefix),
822
+ assetBase
823
+ });
824
+ if (buildManifest) {
825
+ const lazyChunks = computeLazyChunks(buildManifest).map((file) => manifestFileWithBase(file, assetBase));
826
+ globalThis.__VINEXT_LAZY_CHUNKS__ = lazyChunks.length > 0 ? lazyChunks : void 0;
827
+ } else globalThis.__VINEXT_LAZY_CHUNKS__ = void 0;
821
828
  const staticCache = await StaticFileCache.create(clientDir);
822
829
  const handleRequest = async (req, res) => {
823
830
  const rawUrl = req.url ?? "/";