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.
- package/README.md +1 -0
- package/dist/check.js +15 -3
- package/dist/check.js.map +1 -1
- package/dist/client/navigation-runtime.d.ts +1 -0
- package/dist/client/navigation-runtime.js +1 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/config/next-config.d.ts +14 -1
- package/dist/config/next-config.js +24 -4
- package/dist/config/next-config.js.map +1 -1
- package/dist/config/tsconfig-paths.d.ts +12 -3
- package/dist/config/tsconfig-paths.js +55 -24
- package/dist/config/tsconfig-paths.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +2 -1
- package/dist/entries/app-rsc-entry.js +12 -0
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-rsc-manifest.js +22 -5
- package/dist/entries/app-rsc-manifest.js.map +1 -1
- package/dist/entries/pages-server-entry.js +41 -4
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.js +81 -39
- package/dist/index.js.map +1 -1
- package/dist/plugins/import-meta-url.d.ts +16 -0
- package/dist/plugins/import-meta-url.js +193 -0
- package/dist/plugins/import-meta-url.js.map +1 -0
- package/dist/server/app-browser-action-result.d.ts +9 -16
- package/dist/server/app-browser-action-result.js +25 -14
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +171 -45
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-browser-mpa-navigation.d.ts +16 -0
- package/dist/server/app-browser-mpa-navigation.js +36 -0
- package/dist/server/app-browser-mpa-navigation.js.map +1 -0
- package/dist/server/app-browser-popstate.d.ts +3 -1
- package/dist/server/app-browser-popstate.js +15 -1
- package/dist/server/app-browser-popstate.js.map +1 -1
- package/dist/server/app-browser-state.js +2 -1
- package/dist/server/app-browser-state.js.map +1 -1
- package/dist/server/app-layout-param-observation.d.ts +30 -0
- package/dist/server/app-layout-param-observation.js +130 -0
- package/dist/server/app-layout-param-observation.js.map +1 -0
- package/dist/server/app-page-boundary-render.js +2 -2
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-dispatch.js +1 -1
- package/dist/server/app-page-params.d.ts +2 -1
- package/dist/server/app-page-params.js +14 -1
- package/dist/server/app-page-params.js.map +1 -1
- package/dist/server/app-page-probe.d.ts +12 -1
- package/dist/server/app-page-probe.js +116 -1
- package/dist/server/app-page-probe.js.map +1 -1
- package/dist/server/app-route-handler-response.js +1 -1
- package/dist/server/app-route-handler-response.js.map +1 -1
- package/dist/server/app-rsc-cache-busting.d.ts +3 -2
- package/dist/server/app-rsc-cache-busting.js +9 -7
- package/dist/server/app-rsc-cache-busting.js.map +1 -1
- package/dist/server/app-rsc-handler.js +11 -1
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-segment-config.d.ts +1 -1
- package/dist/server/app-segment-config.js +4 -1
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +5 -0
- package/dist/server/app-server-action-execution.js +198 -22
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/artifact-compatibility.d.ts +2 -1
- package/dist/server/artifact-compatibility.js +10 -1
- package/dist/server/artifact-compatibility.js.map +1 -1
- package/dist/server/client-reuse-manifest.d.ts +9 -4
- package/dist/server/client-reuse-manifest.js +2 -1
- package/dist/server/client-reuse-manifest.js.map +1 -1
- package/dist/server/dev-server.js +52 -10
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/document-initial-head.d.ts +7 -0
- package/dist/server/document-initial-head.js +35 -0
- package/dist/server/document-initial-head.js.map +1 -0
- package/dist/server/pages-document-initial-props.d.ts +84 -2
- package/dist/server/pages-document-initial-props.js +127 -1
- package/dist/server/pages-document-initial-props.js.map +1 -1
- package/dist/server/pages-node-compat.js +1 -1
- package/dist/server/pages-page-response.d.ts +14 -0
- package/dist/server/pages-page-response.js +31 -8
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prod-server.js +13 -6
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/skip-cache-proof.d.ts +23 -2
- package/dist/server/skip-cache-proof.js +81 -12
- package/dist/server/skip-cache-proof.js.map +1 -1
- package/dist/server/static-layout-client-reuse-proof.d.ts +16 -0
- package/dist/server/static-layout-client-reuse-proof.js +35 -0
- package/dist/server/static-layout-client-reuse-proof.js.map +1 -0
- package/dist/shims/cache.d.ts +21 -1
- package/dist/shims/cache.js +101 -6
- package/dist/shims/cache.js.map +1 -1
- package/dist/shims/document.d.ts +6 -0
- package/dist/shims/document.js +7 -8
- package/dist/shims/document.js.map +1 -1
- package/dist/shims/error-boundary.d.ts +4 -4
- package/dist/shims/error-boundary.js +27 -28
- package/dist/shims/error-boundary.js.map +1 -1
- package/dist/shims/fetch-cache.d.ts +3 -1
- package/dist/shims/fetch-cache.js +16 -5
- package/dist/shims/fetch-cache.js.map +1 -1
- package/dist/shims/hash-scroll.d.ts +4 -1
- package/dist/shims/hash-scroll.js +13 -1
- package/dist/shims/hash-scroll.js.map +1 -1
- package/dist/shims/head-state.d.ts +1 -0
- package/dist/shims/head-state.js +18 -3
- package/dist/shims/head-state.js.map +1 -1
- package/dist/shims/head.d.ts +35 -1
- package/dist/shims/head.js +113 -14
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/internal/pages-data-fetch-dedup.d.ts +56 -0
- package/dist/shims/internal/pages-data-fetch-dedup.js +70 -0
- package/dist/shims/internal/pages-data-fetch-dedup.js.map +1 -0
- package/dist/shims/link.js +28 -2
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/navigation.d.ts +39 -1
- package/dist/shims/navigation.js +61 -13
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.js +37 -17
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/thenable-params.d.ts +5 -2
- package/dist/shims/thenable-params.js +25 -1
- package/dist/shims/thenable-params.js.map +1 -1
- package/dist/shims/unified-request-context.js +3 -0
- package/dist/shims/unified-request-context.js.map +1 -1
- package/dist/utils/client-build-manifest.d.ts +15 -0
- package/dist/utils/client-build-manifest.js +54 -0
- package/dist/utils/client-build-manifest.js.map +1 -0
- package/dist/utils/hash.js +1 -1
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/lazy-chunks.d.ts +1 -1
- package/dist/utils/lazy-chunks.js.map +1 -1
- package/dist/utils/vite-version.d.ts +11 -0
- package/dist/utils/vite-version.js +36 -0
- package/dist/utils/vite-version.js.map +1 -0
- 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
|
|
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
|
-
|
|
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
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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 ?? "/";
|