vinext 0.0.52 → 0.0.53
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 -1
- package/dist/build/clean-output.d.ts +14 -0
- package/dist/build/clean-output.js +36 -0
- package/dist/build/clean-output.js.map +1 -0
- package/dist/build/prerender.d.ts +6 -2
- package/dist/build/prerender.js +49 -11
- package/dist/build/prerender.js.map +1 -1
- package/dist/build/run-prerender.js +10 -1
- package/dist/build/run-prerender.js.map +1 -1
- package/dist/build/static-export.d.ts +5 -0
- package/dist/build/static-export.js +8 -3
- package/dist/build/static-export.js.map +1 -1
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/client/instrumentation-client-inject.d.ts +34 -0
- package/dist/client/instrumentation-client-inject.js +57 -0
- package/dist/client/instrumentation-client-inject.js.map +1 -0
- package/dist/client/navigation-runtime.d.ts +14 -1
- package/dist/client/navigation-runtime.js +16 -1
- package/dist/client/navigation-runtime.js.map +1 -1
- package/dist/client/vinext-next-data.d.ts +2 -1
- package/dist/client/vinext-next-data.js.map +1 -1
- package/dist/client/window-next.d.ts +10 -2
- package/dist/client/window-next.js.map +1 -1
- package/dist/cloudflare/tpr.js +1 -1
- package/dist/cloudflare/tpr.js.map +1 -1
- package/dist/config/config-matchers.js +2 -1
- package/dist/config/config-matchers.js.map +1 -1
- package/dist/config/next-config.d.ts +12 -3
- package/dist/config/next-config.js +44 -14
- package/dist/config/next-config.js.map +1 -1
- package/dist/deploy.js +29 -7
- package/dist/deploy.js.map +1 -1
- package/dist/entries/app-rsc-entry.d.ts +4 -2
- package/dist/entries/app-rsc-entry.js +23 -3
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/pages-client-entry.js +22 -1
- package/dist/entries/pages-client-entry.js.map +1 -1
- package/dist/entries/pages-server-entry.js +211 -31
- package/dist/entries/pages-server-entry.js.map +1 -1
- package/dist/index.js +29 -6
- package/dist/index.js.map +1 -1
- package/dist/plugins/fonts.js +25 -2
- package/dist/plugins/fonts.js.map +1 -1
- package/dist/routing/route-trie.js +13 -18
- package/dist/routing/route-trie.js.map +1 -1
- package/dist/routing/utils.d.ts +11 -1
- package/dist/routing/utils.js +15 -1
- package/dist/routing/utils.js.map +1 -1
- package/dist/server/api-handler.js +18 -9
- package/dist/server/api-handler.js.map +1 -1
- package/dist/server/app-browser-action-result.d.ts +16 -1
- package/dist/server/app-browser-action-result.js +15 -1
- package/dist/server/app-browser-action-result.js.map +1 -1
- package/dist/server/app-browser-entry.js +22 -12
- package/dist/server/app-browser-entry.js.map +1 -1
- package/dist/server/app-elements.js +1 -1
- package/dist/server/app-fallback-renderer.d.ts +12 -3
- package/dist/server/app-fallback-renderer.js +10 -5
- package/dist/server/app-fallback-renderer.js.map +1 -1
- package/dist/server/app-history-state.js +6 -2
- package/dist/server/app-history-state.js.map +1 -1
- package/dist/server/app-interception-context-header.d.ts +33 -0
- package/dist/server/app-interception-context-header.js +44 -0
- package/dist/server/app-interception-context-header.js.map +1 -0
- package/dist/server/app-mounted-slots-header.d.ts +19 -0
- package/dist/server/app-mounted-slots-header.js +40 -1
- package/dist/server/app-mounted-slots-header.js.map +1 -1
- package/dist/server/app-optimistic-routing.js +26 -18
- package/dist/server/app-optimistic-routing.js.map +1 -1
- package/dist/server/app-page-boundary-render.d.ts +1 -0
- package/dist/server/app-page-boundary-render.js +2 -0
- package/dist/server/app-page-boundary-render.js.map +1 -1
- package/dist/server/app-page-boundary.d.ts +1 -0
- package/dist/server/app-page-boundary.js +2 -0
- package/dist/server/app-page-boundary.js.map +1 -1
- package/dist/server/app-page-cache.d.ts +2 -0
- package/dist/server/app-page-cache.js +7 -1
- package/dist/server/app-page-cache.js.map +1 -1
- package/dist/server/app-page-dispatch.d.ts +3 -0
- package/dist/server/app-page-dispatch.js +11 -4
- package/dist/server/app-page-dispatch.js.map +1 -1
- package/dist/server/app-page-element-builder.d.ts +2 -1
- package/dist/server/app-page-element-builder.js +5 -2
- package/dist/server/app-page-element-builder.js.map +1 -1
- package/dist/server/app-page-execution.d.ts +1 -0
- package/dist/server/app-page-execution.js +2 -0
- package/dist/server/app-page-execution.js.map +1 -1
- package/dist/server/app-page-head.d.ts +1 -0
- package/dist/server/app-page-head.js +8 -0
- package/dist/server/app-page-head.js.map +1 -1
- package/dist/server/app-page-render-observation.js +1 -1
- package/dist/server/app-page-render.d.ts +1 -0
- package/dist/server/app-page-render.js +5 -2
- package/dist/server/app-page-render.js.map +1 -1
- package/dist/server/app-page-response.d.ts +11 -1
- package/dist/server/app-page-response.js +14 -2
- package/dist/server/app-page-response.js.map +1 -1
- package/dist/server/app-page-route-wiring.d.ts +1 -0
- package/dist/server/app-page-route-wiring.js +19 -6
- package/dist/server/app-page-route-wiring.js.map +1 -1
- package/dist/server/app-page-stream.d.ts +1 -0
- package/dist/server/app-page-stream.js +2 -0
- package/dist/server/app-page-stream.js.map +1 -1
- package/dist/server/app-route-handler-dispatch.d.ts +1 -0
- package/dist/server/app-route-handler-dispatch.js +3 -0
- package/dist/server/app-route-handler-dispatch.js.map +1 -1
- package/dist/server/app-route-handler-execution.d.ts +1 -0
- package/dist/server/app-route-handler-execution.js +1 -0
- package/dist/server/app-route-handler-execution.js.map +1 -1
- package/dist/server/app-route-handler-response.js +1 -1
- package/dist/server/app-rsc-handler.d.ts +2 -0
- package/dist/server/app-rsc-handler.js +18 -9
- package/dist/server/app-rsc-handler.js.map +1 -1
- package/dist/server/app-rsc-request-normalization.js +3 -2
- package/dist/server/app-rsc-request-normalization.js.map +1 -1
- package/dist/server/app-segment-config.d.ts +4 -1
- package/dist/server/app-segment-config.js +6 -1
- package/dist/server/app-segment-config.js.map +1 -1
- package/dist/server/app-server-action-execution.d.ts +1 -0
- package/dist/server/app-server-action-execution.js +4 -0
- package/dist/server/app-server-action-execution.js.map +1 -1
- package/dist/server/app-ssr-entry.js +39 -3
- package/dist/server/app-ssr-entry.js.map +1 -1
- package/dist/server/app-ssr-stream.d.ts +24 -1
- package/dist/server/app-ssr-stream.js +78 -5
- package/dist/server/app-ssr-stream.js.map +1 -1
- package/dist/server/app-static-generation.d.ts +1 -0
- package/dist/server/app-static-generation.js +2 -1
- package/dist/server/app-static-generation.js.map +1 -1
- package/dist/server/default-not-found-module.d.ts +20 -0
- package/dist/server/default-not-found-module.js +20 -0
- package/dist/server/default-not-found-module.js.map +1 -0
- package/dist/server/dev-server.d.ts +1 -1
- package/dist/server/dev-server.js +23 -7
- package/dist/server/dev-server.js.map +1 -1
- package/dist/server/headers.d.ts +5 -1
- package/dist/server/headers.js +5 -1
- package/dist/server/headers.js.map +1 -1
- package/dist/server/image-optimization.d.ts +13 -4
- package/dist/server/image-optimization.js +15 -4
- package/dist/server/image-optimization.js.map +1 -1
- package/dist/server/middleware.js +1 -1
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/pages-api-route.d.ts +18 -0
- package/dist/server/pages-api-route.js +3 -1
- package/dist/server/pages-api-route.js.map +1 -1
- package/dist/server/pages-body-parser-config.d.ts +60 -0
- package/dist/server/pages-body-parser-config.js +79 -0
- package/dist/server/pages-body-parser-config.js.map +1 -0
- package/dist/server/pages-data-route.js +1 -0
- package/dist/server/pages-data-route.js.map +1 -1
- package/dist/server/pages-default-404.d.ts +31 -0
- package/dist/server/pages-default-404.js +40 -0
- package/dist/server/pages-default-404.js.map +1 -0
- package/dist/server/pages-node-compat.d.ts +10 -0
- package/dist/server/pages-node-compat.js +12 -1
- package/dist/server/pages-node-compat.js.map +1 -1
- package/dist/server/pages-page-data.d.ts +40 -0
- package/dist/server/pages-page-data.js +16 -14
- package/dist/server/pages-page-data.js.map +1 -1
- package/dist/server/pages-page-response.d.ts +2 -0
- package/dist/server/pages-page-response.js +11 -8
- package/dist/server/pages-page-response.js.map +1 -1
- package/dist/server/prerender-route-params.d.ts +14 -0
- package/dist/server/prerender-route-params.js +94 -0
- package/dist/server/prerender-route-params.js.map +1 -0
- package/dist/server/prod-server.d.ts +3 -23
- package/dist/server/prod-server.js +40 -57
- package/dist/server/prod-server.js.map +1 -1
- package/dist/server/proxy-trust.d.ts +41 -0
- package/dist/server/proxy-trust.js +70 -0
- package/dist/server/proxy-trust.js.map +1 -0
- package/dist/server/request-pipeline.d.ts +3 -3
- package/dist/server/request-pipeline.js +5 -4
- package/dist/server/request-pipeline.js.map +1 -1
- package/dist/server/seed-cache.js +12 -6
- package/dist/server/seed-cache.js.map +1 -1
- package/dist/server/static-file-cache.js +1 -1
- package/dist/server/static-file-cache.js.map +1 -1
- package/dist/server/streaming-metadata.d.ts +5 -0
- package/dist/server/streaming-metadata.js +10 -0
- package/dist/server/streaming-metadata.js.map +1 -0
- package/dist/shims/app-router-scroll-state.d.ts +12 -0
- package/dist/shims/app-router-scroll-state.js +38 -0
- package/dist/shims/app-router-scroll-state.js.map +1 -0
- package/dist/shims/app-router-scroll.d.ts +14 -0
- package/dist/shims/app-router-scroll.js +100 -0
- package/dist/shims/app-router-scroll.js.map +1 -0
- package/dist/shims/before-interactive-context.d.ts +30 -0
- package/dist/shims/before-interactive-context.js +10 -0
- package/dist/shims/before-interactive-context.js.map +1 -0
- package/dist/shims/cache-runtime.d.ts +1 -1
- package/dist/shims/cache-runtime.js +14 -1
- package/dist/shims/cache-runtime.js.map +1 -1
- package/dist/shims/default-not-found.d.ts +12 -0
- package/dist/shims/default-not-found.js +61 -0
- package/dist/shims/default-not-found.js.map +1 -0
- package/dist/shims/font-local.d.ts +5 -0
- package/dist/shims/font-local.js +6 -2
- package/dist/shims/font-local.js.map +1 -1
- package/dist/shims/head.js +4 -4
- package/dist/shims/head.js.map +1 -1
- package/dist/shims/headers.d.ts +6 -2
- package/dist/shims/headers.js +64 -21
- package/dist/shims/headers.js.map +1 -1
- package/dist/shims/image.d.ts +1 -1
- package/dist/shims/image.js +4 -4
- package/dist/shims/image.js.map +1 -1
- package/dist/shims/internal/pages-data-target.d.ts +58 -0
- package/dist/shims/internal/pages-data-target.js +91 -0
- package/dist/shims/internal/pages-data-target.js.map +1 -0
- package/dist/shims/internal/pages-data-url.d.ts +42 -0
- package/dist/shims/internal/pages-data-url.js +73 -0
- package/dist/shims/internal/pages-data-url.js.map +1 -0
- package/dist/shims/link.js +59 -9
- package/dist/shims/link.js.map +1 -1
- package/dist/shims/metadata.d.ts +2 -1
- package/dist/shims/metadata.js +61 -2
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/navigation.js +32 -9
- package/dist/shims/navigation.js.map +1 -1
- package/dist/shims/router.js +376 -77
- package/dist/shims/router.js.map +1 -1
- package/dist/shims/script.js +86 -12
- package/dist/shims/script.js.map +1 -1
- package/dist/shims/server.js +1 -0
- package/dist/shims/server.js.map +1 -1
- package/dist/shims/url-utils.d.ts +2 -1
- package/dist/shims/url-utils.js +15 -4
- package/dist/shims/url-utils.js.map +1 -1
- package/dist/utils/html-limited-bots.d.ts +5 -0
- package/dist/utils/html-limited-bots.js +15 -0
- package/dist/utils/html-limited-bots.js.map +1 -0
- package/dist/utils/query.d.ts +6 -0
- package/dist/utils/query.js +10 -1
- package/dist/utils/query.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Route } from "../routing/pages-router.js";
|
|
2
|
+
import { VinextNextData } from "../client/vinext-next-data.js";
|
|
2
3
|
import { CachedPagesValue } from "../shims/cache.js";
|
|
3
4
|
import { ISRCacheEntry } from "./isr-cache.js";
|
|
4
5
|
import { PagesGsspResponse, PagesI18nRenderContext } from "./pages-page-response.js";
|
|
@@ -64,6 +65,18 @@ type PagesPageModule = {
|
|
|
64
65
|
locale?: string;
|
|
65
66
|
locales?: string[];
|
|
66
67
|
defaultLocale?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Indicates why `getStaticProps` was invoked.
|
|
70
|
+
*
|
|
71
|
+
* - `"build"`: initial build-time prerender (before runtime traffic).
|
|
72
|
+
* - `"on-demand"`: triggered by `res.revalidate()` from an API route.
|
|
73
|
+
* - `"stale"`: stale-while-revalidate background regeneration.
|
|
74
|
+
*
|
|
75
|
+
* Mirrors Next.js `render.tsx`'s `revalidateReason` on the
|
|
76
|
+
* `GetStaticPropsContext` type — see
|
|
77
|
+
* `.nextjs-ref/packages/next/src/types.ts`.
|
|
78
|
+
*/
|
|
79
|
+
revalidateReason?: "build" | "on-demand" | "stale";
|
|
67
80
|
}) => Promise<PagesPagePropsResult> | PagesPagePropsResult;
|
|
68
81
|
};
|
|
69
82
|
type RenderPagesIsrHtmlOptions = {
|
|
@@ -76,6 +89,7 @@ type RenderPagesIsrHtmlOptions = {
|
|
|
76
89
|
renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;
|
|
77
90
|
routePattern: string;
|
|
78
91
|
safeJsonStringify: (value: unknown) => string;
|
|
92
|
+
vinext?: VinextNextData["__vinext"];
|
|
79
93
|
};
|
|
80
94
|
type ResolvePagesPageDataOptions = {
|
|
81
95
|
applyRequestContexts: () => void;
|
|
@@ -97,6 +111,30 @@ type ResolvePagesPageDataOptions = {
|
|
|
97
111
|
isrGet: (key: string) => Promise<ISRCacheEntry | null>;
|
|
98
112
|
isrSet: (key: string, data: CachedPagesValue, revalidateSeconds: number, tags?: string[], expireSeconds?: number) => Promise<void>;
|
|
99
113
|
expireSeconds?: number;
|
|
114
|
+
/**
|
|
115
|
+
* When true, this dispatch corresponds to a build-time prerender (the
|
|
116
|
+
* `vinext` build phase fetches each statically generated page through the
|
|
117
|
+
* production server). Maps to `revalidateReason: "build"` when
|
|
118
|
+
* `getStaticProps` is invoked. Mirrors Next.js's
|
|
119
|
+
* `renderOpts.isBuildTimePrerendering` flag — see
|
|
120
|
+
* `.nextjs-ref/packages/next/src/server/render.tsx`.
|
|
121
|
+
*/
|
|
122
|
+
isBuildTimePrerendering?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* When true, this dispatch was triggered by an on-demand revalidation
|
|
125
|
+
* request (e.g. `res.revalidate()` in a Pages Router API route, or an
|
|
126
|
+
* equivalent webhook). Maps to `revalidateReason: "on-demand"` when
|
|
127
|
+
* `getStaticProps` is invoked. Mirrors Next.js's
|
|
128
|
+
* `renderOpts.isOnDemandRevalidate` flag — see
|
|
129
|
+
* `.nextjs-ref/packages/next/src/server/render.tsx`.
|
|
130
|
+
*
|
|
131
|
+
* Forward-looking plumbing: no caller currently sets this — `res.revalidate()`
|
|
132
|
+
* is not yet implemented in vinext. The `"on-demand"` branch in the
|
|
133
|
+
* `revalidateReason` resolver is intentionally unreachable today; keeping the
|
|
134
|
+
* typed contract here means wiring it up will be a one-line change once the
|
|
135
|
+
* trigger lands.
|
|
136
|
+
*/
|
|
137
|
+
isOnDemandRevalidate?: boolean;
|
|
100
138
|
pageModule: PagesPageModule;
|
|
101
139
|
params: Record<string, unknown>;
|
|
102
140
|
query: Record<string, unknown>;
|
|
@@ -107,12 +145,14 @@ type ResolvePagesPageDataOptions = {
|
|
|
107
145
|
safeJsonStringify: (value: unknown) => string;
|
|
108
146
|
sanitizeDestination: (destination: string) => string;
|
|
109
147
|
scriptNonce?: string;
|
|
148
|
+
statusCode?: number;
|
|
110
149
|
triggerBackgroundRegeneration: (key: string, renderFn: () => Promise<void>, errorContext?: {
|
|
111
150
|
routerKind: "Pages Router";
|
|
112
151
|
routePath: string;
|
|
113
152
|
routeType: "render";
|
|
114
153
|
}) => void;
|
|
115
154
|
renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;
|
|
155
|
+
vinext?: VinextNextData["__vinext"];
|
|
116
156
|
};
|
|
117
157
|
type ResolvePagesPageDataRenderResult = {
|
|
118
158
|
kind: "render";
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { normalizeStaticPathname } from "../routing/route-pattern.js";
|
|
2
2
|
import { buildCacheStateHeaders } from "./cache-headers.js";
|
|
3
3
|
import { buildPagesCacheValue } from "./isr-cache.js";
|
|
4
|
+
import { buildDefaultPagesNotFoundResponse } from "./pages-default-404.js";
|
|
4
5
|
import { buildCachedRevalidateCacheControl } from "./cache-control.js";
|
|
5
6
|
import { buildPagesNextDataScript } from "./pages-page-response.js";
|
|
6
7
|
//#region src/server/pages-page-data.ts
|
|
7
8
|
function buildPagesNotFoundResponse() {
|
|
8
|
-
return
|
|
9
|
-
status: 404,
|
|
10
|
-
headers: { "Content-Type": "text/html" }
|
|
11
|
-
});
|
|
9
|
+
return buildDefaultPagesNotFoundResponse();
|
|
12
10
|
}
|
|
13
11
|
function buildPagesDataNotFoundResponse() {
|
|
14
12
|
return new Response("{}", {
|
|
@@ -44,7 +42,7 @@ function matchesPagesStaticPath(pathEntry, params, routeUrl) {
|
|
|
44
42
|
return String(value) === String(actual);
|
|
45
43
|
});
|
|
46
44
|
}
|
|
47
|
-
function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSeconds, expireSeconds, cacheControl) {
|
|
45
|
+
function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSeconds, expireSeconds, cacheControl, status) {
|
|
48
46
|
const effectiveRevalidateSeconds = cacheControl?.revalidate ?? revalidateSeconds ?? 60;
|
|
49
47
|
const effectiveExpireSeconds = cacheControl === void 0 ? void 0 : cacheControl.expire ?? expireSeconds;
|
|
50
48
|
const headers = {
|
|
@@ -54,7 +52,7 @@ function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSec
|
|
|
54
52
|
};
|
|
55
53
|
if (fontLinkHeader) headers.Link = fontLinkHeader;
|
|
56
54
|
return new Response(html, {
|
|
57
|
-
status: 200,
|
|
55
|
+
status: status ?? 200,
|
|
58
56
|
headers
|
|
59
57
|
});
|
|
60
58
|
}
|
|
@@ -80,7 +78,8 @@ async function renderPagesIsrHtml(options) {
|
|
|
80
78
|
pageProps: options.pageProps,
|
|
81
79
|
params: options.params,
|
|
82
80
|
routePattern: options.routePattern,
|
|
83
|
-
safeJsonStringify: options.safeJsonStringify
|
|
81
|
+
safeJsonStringify: options.safeJsonStringify,
|
|
82
|
+
vinext: options.vinext
|
|
84
83
|
});
|
|
85
84
|
return rewritePagesCachedHtml(options.cachedHtml, freshBody, nextDataScript);
|
|
86
85
|
}
|
|
@@ -125,7 +124,7 @@ async function resolvePagesPageData(options) {
|
|
|
125
124
|
kind: "response",
|
|
126
125
|
response: await responsePromise
|
|
127
126
|
};
|
|
128
|
-
if (result?.props) pageProps = result.props;
|
|
127
|
+
if (result?.props) pageProps = await Promise.resolve(result.props);
|
|
129
128
|
if (result?.redirect) return {
|
|
130
129
|
kind: "response",
|
|
131
130
|
response: new Response(null, {
|
|
@@ -147,7 +146,7 @@ async function resolvePagesPageData(options) {
|
|
|
147
146
|
const cachedValue = cached?.value.value;
|
|
148
147
|
if (cachedValue?.kind === "PAGES" && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq) return {
|
|
149
148
|
kind: "response",
|
|
150
|
-
response: buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl)
|
|
149
|
+
response: buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status)
|
|
151
150
|
};
|
|
152
151
|
if (cachedValue?.kind === "PAGES" && cached && cached.isStale && !options.scriptNonce && !options.isDataReq) {
|
|
153
152
|
options.triggerBackgroundRegeneration(cacheKey, async function() {
|
|
@@ -156,7 +155,8 @@ async function resolvePagesPageData(options) {
|
|
|
156
155
|
params: userFacingParams,
|
|
157
156
|
locale: options.i18n.locale,
|
|
158
157
|
locales: options.i18n.locales,
|
|
159
|
-
defaultLocale: options.i18n.defaultLocale
|
|
158
|
+
defaultLocale: options.i18n.defaultLocale,
|
|
159
|
+
revalidateReason: "stale"
|
|
160
160
|
});
|
|
161
161
|
if (freshResult?.props && typeof freshResult.revalidate === "number" && freshResult.revalidate > 0) {
|
|
162
162
|
options.applyRequestContexts();
|
|
@@ -169,9 +169,10 @@ async function resolvePagesPageData(options) {
|
|
|
169
169
|
params: options.params,
|
|
170
170
|
renderIsrPassToStringAsync: options.renderIsrPassToStringAsync,
|
|
171
171
|
routePattern: options.routePattern,
|
|
172
|
-
safeJsonStringify: options.safeJsonStringify
|
|
172
|
+
safeJsonStringify: options.safeJsonStringify,
|
|
173
|
+
vinext: options.vinext
|
|
173
174
|
});
|
|
174
|
-
await options.isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshResult.props), freshResult.revalidate, void 0, options.expireSeconds);
|
|
175
|
+
await options.isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshResult.props, options.statusCode), freshResult.revalidate, void 0, options.expireSeconds);
|
|
175
176
|
}
|
|
176
177
|
});
|
|
177
178
|
}, {
|
|
@@ -181,14 +182,15 @@ async function resolvePagesPageData(options) {
|
|
|
181
182
|
});
|
|
182
183
|
return {
|
|
183
184
|
kind: "response",
|
|
184
|
-
response: buildPagesCacheResponse(cachedValue.html, "STALE", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl)
|
|
185
|
+
response: buildPagesCacheResponse(cachedValue.html, "STALE", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status)
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
const result = await options.pageModule.getStaticProps({
|
|
188
189
|
params: userFacingParams,
|
|
189
190
|
locale: options.i18n.locale,
|
|
190
191
|
locales: options.i18n.locales,
|
|
191
|
-
defaultLocale: options.i18n.defaultLocale
|
|
192
|
+
defaultLocale: options.i18n.defaultLocale,
|
|
193
|
+
revalidateReason: options.isOnDemandRevalidate ? "on-demand" : options.isBuildTimePrerendering ? "build" : "stale"
|
|
192
194
|
});
|
|
193
195
|
if (result?.props) pageProps = result.props;
|
|
194
196
|
if (result?.redirect) return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pages-page-data.js","names":[],"sources":["../../src/server/pages-page-data.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Route } from \"../routing/pages-router.js\";\nimport { normalizeStaticPathname } from \"../routing/route-pattern.js\";\nimport type { CachedPagesValue, CacheControlMetadata } from \"vinext/shims/cache\";\nimport { buildCachedRevalidateCacheControl } from \"./cache-control.js\";\nimport { buildCacheStateHeaders } from \"./cache-headers.js\";\nimport { buildPagesCacheValue, type ISRCacheEntry } from \"./isr-cache.js\";\nimport {\n buildPagesNextDataScript,\n type PagesGsspResponse,\n type PagesI18nRenderContext,\n} from \"./pages-page-response.js\";\n\ntype PagesRedirectResult = {\n destination: string;\n permanent?: boolean;\n statusCode?: number;\n};\n\n// Next.js allows `paths` entries to be either an object with a `params` key\n// or a raw string path. We keep a local variant of `StaticPathsEntry` here\n// because at request time we compare against the actual request `params`\n// (whose value type is `unknown` from the route matcher) rather than the\n// `string | string[]` shape used at build time. The shared\n// `normalizeStaticPathname` helper from `../routing/route-pattern.js` is used\n// to canonicalize the string-entry comparison.\ntype PagesStaticPathsEntry = string | { params?: Record<string, unknown>; locale?: string };\n\ntype PagesStaticPathsResult = {\n fallback?: boolean | \"blocking\";\n paths?: PagesStaticPathsEntry[];\n};\n\ntype PagesPagePropsResult = {\n props?: Record<string, unknown>;\n redirect?: PagesRedirectResult;\n notFound?: boolean;\n revalidate?: number;\n};\n\nexport type PagesMutableGsspResponse = {\n headersSent: boolean;\n} & PagesGsspResponse;\n\nexport type PagesGsspContextResponse = {\n req: unknown;\n res: PagesMutableGsspResponse;\n responsePromise: Promise<Response>;\n};\n\nexport type PagesPageModule = {\n default?: unknown;\n getStaticPaths?: (context: {\n locales: string[];\n defaultLocale: string;\n }) => Promise<PagesStaticPathsResult> | PagesStaticPathsResult;\n /**\n * Pages Router data-fetching context.\n *\n * `params` is `null` for non-dynamic routes (no `[param]` segments) to\n * match Next.js. User code typically falls back via `params || null`, so\n * passing `null` (rather than `{}`) is required for the value to be\n * observable as `null` once the data flows through to the page props.\n *\n * See: test/e2e/edge-pages-support/index.test.ts in Next.js for the\n * authoritative assertion (`expect(props.params).toBe(null)`).\n */\n getServerSideProps?: (context: {\n params: Record<string, unknown> | null;\n req: unknown;\n res: PagesMutableGsspResponse;\n query: Record<string, unknown>;\n resolvedUrl: string;\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n }) => Promise<PagesPagePropsResult> | PagesPagePropsResult;\n getStaticProps?: (context: {\n params: Record<string, unknown> | null;\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n }) => Promise<PagesPagePropsResult> | PagesPagePropsResult;\n};\n\ntype RenderPagesIsrHtmlOptions = {\n buildId: string | null;\n cachedHtml: string;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n i18n: PagesI18nRenderContext;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n routePattern: string;\n safeJsonStringify: (value: unknown) => string;\n};\n\nexport type ResolvePagesPageDataOptions = {\n applyRequestContexts: () => void;\n buildId: string | null;\n /**\n * When true, this is a `/_next/data/<buildId>/<page>.json` request. Callers\n * that respond with a JSON envelope (`{ pageProps }`) instead of HTML must\n * bypass the HTML ISR cache: a cached HTML body cannot be reshaped into the\n * expected JSON shape, and storing JSON in the HTML cache would corrupt\n * subsequent HTML hits. Next.js handles this the same way — see\n * `isNextDataRequest` checks in `packages/next/src/server/base-server.ts`.\n */\n isDataReq?: boolean;\n createGsspReqRes: () => PagesGsspContextResponse;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n fontLinkHeader: string;\n i18n: PagesI18nRenderContext;\n isrCacheKey: (router: string, pathname: string) => string;\n isrGet: (key: string) => Promise<ISRCacheEntry | null>;\n isrSet: (\n key: string,\n data: CachedPagesValue,\n revalidateSeconds: number,\n tags?: string[],\n expireSeconds?: number,\n ) => Promise<void>;\n expireSeconds?: number;\n pageModule: PagesPageModule;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n route: Pick<Route, \"isDynamic\">;\n routePattern: string;\n routeUrl: string;\n runInFreshUnifiedContext: <T>(callback: () => Promise<T>) => Promise<T>;\n safeJsonStringify: (value: unknown) => string;\n sanitizeDestination: (destination: string) => string;\n scriptNonce?: string;\n triggerBackgroundRegeneration: (\n key: string,\n renderFn: () => Promise<void>,\n errorContext?: { routerKind: \"Pages Router\"; routePath: string; routeType: \"render\" },\n ) => void;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n};\n\ntype ResolvePagesPageDataRenderResult = {\n kind: \"render\";\n gsspRes: PagesGsspResponse | null;\n isrRevalidateSeconds: number | null;\n pageProps: Record<string, unknown>;\n /**\n * True when `getStaticPaths` returned `fallback: true` AND the requested path\n * is not in the pre-rendered list. The caller renders a loading shell with\n * empty props and `useRouter().isFallback === true` (matching Next.js's\n * `render.tsx` — `getStaticProps` is skipped on the fallback render).\n */\n isFallback: boolean;\n};\n\ntype ResolvePagesPageDataResponseResult = {\n kind: \"response\";\n response: Response;\n};\n\ntype ResolvePagesPageDataResult =\n | ResolvePagesPageDataRenderResult\n | ResolvePagesPageDataResponseResult;\n\nfunction buildPagesNotFoundResponse(): Response {\n return new Response(\"<!DOCTYPE html><html><body><h1>404 - Page not found</h1></body></html>\", {\n status: 404,\n headers: { \"Content-Type\": \"text/html\" },\n });\n}\n\nfunction buildPagesDataNotFoundResponse(): Response {\n // Matches Next.js: `/_next/data/<buildId>/<page>.json` 404 responses use\n // application/json with an empty object body so clients can call\n // `res.json()` without throwing before inspecting the status code.\n return new Response(\"{}\", {\n status: 404,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction resolvePagesRedirectStatus(redirect: PagesRedirectResult): number {\n return redirect.statusCode != null ? redirect.statusCode : redirect.permanent ? 308 : 307;\n}\n\n/**\n * Compare a `getStaticPaths` entry against the actual request params.\n *\n * Handles both shapes Next.js allows:\n * - { params: { ... } }\n * - \"string-path\"\n *\n * For a string entry, compare the entry against the current request URL using\n * the shared `normalizeStaticPathname` helper from\n * `../routing/route-pattern.ts` (which mirrors the Next.js\n * `removeTrailingSlash` behaviour in\n * `.nextjs-ref/packages/next/src/build/static-paths/pages.ts`). For an object\n * entry with a missing `params` key, return false rather than throwing — the\n * caller will respond with a 404 just like Next.js does for unlisted paths.\n */\nfunction matchesPagesStaticPath(\n pathEntry: PagesStaticPathsEntry,\n params: Record<string, unknown>,\n routeUrl: string,\n): boolean {\n if (typeof pathEntry === \"string\") {\n return normalizeStaticPathname(pathEntry) === normalizeStaticPathname(routeUrl);\n }\n const entryParams = pathEntry.params;\n if (entryParams === undefined || entryParams === null) {\n return false;\n }\n return Object.entries(entryParams).every(([key, value]) => {\n const actual = params[key];\n if (Array.isArray(value)) {\n return Array.isArray(actual) && value.join(\"/\") === actual.join(\"/\");\n }\n return String(value) === String(actual);\n });\n}\n\nfunction buildPagesCacheResponse(\n html: string,\n cacheState: \"HIT\" | \"STALE\",\n fontLinkHeader: string,\n revalidateSeconds?: number,\n expireSeconds?: number,\n cacheControl?: CacheControlMetadata,\n): Response {\n // Legacy cache entries written before cacheControl metadata existed can still\n // hit this path without a persisted revalidate value; keep the historic\n // 60-second fallback for that migration window.\n const effectiveRevalidateSeconds = cacheControl?.revalidate ?? revalidateSeconds ?? 60;\n const effectiveExpireSeconds =\n cacheControl === undefined ? undefined : (cacheControl.expire ?? expireSeconds);\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/html\",\n ...buildCacheStateHeaders(cacheState),\n \"Cache-Control\": buildCachedRevalidateCacheControl(\n cacheState,\n effectiveRevalidateSeconds,\n effectiveExpireSeconds,\n ),\n };\n\n if (fontLinkHeader) {\n headers.Link = fontLinkHeader;\n }\n\n return new Response(html, {\n status: 200,\n headers,\n });\n}\n\nfunction rewritePagesCachedHtml(\n cachedHtml: string,\n freshBody: string,\n nextDataScript: string,\n): string {\n const bodyMarker = '<div id=\"__next\">';\n const bodyStart = cachedHtml.indexOf(bodyMarker);\n const contentStart = bodyStart >= 0 ? bodyStart + bodyMarker.length : -1;\n // This intentionally looks for the bare inline __NEXT_DATA__ marker.\n // Pages responses with scriptNonce are excluded from ISR writes, so cached\n // HTML should never contain nonce-prefixed __NEXT_DATA__ scripts here.\n const nextDataMarker = \"<script>window.__NEXT_DATA__\";\n const nextDataStart = cachedHtml.indexOf(nextDataMarker);\n\n if (contentStart >= 0 && nextDataStart >= 0) {\n const region = cachedHtml.slice(contentStart, nextDataStart);\n const lastCloseDiv = region.lastIndexOf(\"</div>\");\n const gap = lastCloseDiv >= 0 ? region.slice(lastCloseDiv + 6) : \"\";\n const nextDataEnd = cachedHtml.indexOf(\"</script>\", nextDataStart) + 9;\n const tail = cachedHtml.slice(nextDataEnd);\n\n return cachedHtml.slice(0, contentStart) + freshBody + \"</div>\" + gap + nextDataScript + tail;\n }\n\n return (\n '<!DOCTYPE html>\\n<html>\\n<head>\\n</head>\\n<body>\\n <div id=\"__next\">' +\n freshBody +\n \"</div>\\n \" +\n nextDataScript +\n \"\\n</body>\\n</html>\"\n );\n}\n\nexport async function renderPagesIsrHtml(options: RenderPagesIsrHtmlOptions): Promise<string> {\n const freshBody = await options.renderIsrPassToStringAsync(\n options.createPageElement(options.pageProps),\n );\n const nextDataScript = buildPagesNextDataScript({\n buildId: options.buildId,\n i18n: options.i18n,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n });\n\n return rewritePagesCachedHtml(options.cachedHtml, freshBody, nextDataScript);\n}\n\nexport async function resolvePagesPageData(\n options: ResolvePagesPageDataOptions,\n): Promise<ResolvePagesPageDataResult> {\n // Next.js passes `params: null` (effectively) to gSSP/gSP context for\n // non-dynamic routes — see render.tsx's `...(pageIsDynamic ? { params } : undefined)`.\n // Internal bookkeeping (route param hydration, ISR HTML, getStaticPaths\n // validation) still uses the matched-but-empty object — only user-facing\n // data-fetching contexts surface `null`.\n const userFacingParams: Record<string, unknown> | null = options.route.isDynamic\n ? options.params\n : null;\n\n // Set when `getStaticPaths: { fallback: true }` is configured and the\n // requested path is NOT in the pre-rendered list. When true, we render the\n // loading shell with empty props and `useRouter().isFallback === true`,\n // skipping `getStaticProps`. Matches Next.js `render.tsx`'s\n // `if (isSSG && !isFallback)` gate around `getStaticProps`. Data requests\n // (`/_next/data/...json`) still call `getStaticProps` so the client can\n // hydrate the page after the fallback shell ships.\n let isFallback = false;\n\n if (typeof options.pageModule.getStaticPaths === \"function\" && options.route.isDynamic) {\n const pathsResult = await options.pageModule.getStaticPaths({\n locales: options.i18n.locales ?? [],\n defaultLocale: options.i18n.defaultLocale ?? \"\",\n });\n const fallback = pathsResult?.fallback ?? false;\n const paths = pathsResult?.paths ?? [];\n const isValidPath = paths.some((pathEntry) =>\n matchesPagesStaticPath(pathEntry, options.params, options.routeUrl),\n );\n\n if (fallback === false && !isValidPath) {\n // For data requests (`/_next/data/...json`), return a JSON-shaped 404\n // so the client router can `res.json()` without blowing up — matches\n // Next.js' behavior. HTML navigations still get the HTML 404 page.\n return {\n kind: \"response\",\n response: options.isDataReq\n ? buildPagesDataNotFoundResponse()\n : buildPagesNotFoundResponse(),\n };\n }\n\n // Render the fallback shell for unlisted paths under `fallback: true`.\n // Data requests resolve props normally so the client can fill in after\n // the loading shell ships (`fallback: 'blocking'` keeps SSRing as before).\n if (fallback === true && !isValidPath && !options.isDataReq) {\n isFallback = true;\n }\n }\n\n let pageProps: Record<string, unknown> = {};\n let gsspRes: PagesMutableGsspResponse | null = null;\n\n if (isFallback) {\n return {\n kind: \"render\",\n gsspRes: null,\n isrRevalidateSeconds: null,\n pageProps,\n isFallback: true,\n };\n }\n\n if (typeof options.pageModule.getServerSideProps === \"function\") {\n const { req, res, responsePromise } = options.createGsspReqRes();\n const result = await options.pageModule.getServerSideProps({\n params: userFacingParams,\n req,\n res,\n query: options.query,\n resolvedUrl: options.routeUrl,\n locale: options.i18n.locale,\n locales: options.i18n.locales,\n defaultLocale: options.i18n.defaultLocale,\n });\n\n if (res.headersSent) {\n return {\n kind: \"response\",\n response: await responsePromise,\n };\n }\n\n if (result?.props) {\n pageProps = result.props;\n }\n\n if (result?.redirect) {\n return {\n kind: \"response\",\n response: new Response(null, {\n status: resolvePagesRedirectStatus(result.redirect),\n headers: { Location: options.sanitizeDestination(result.redirect.destination) },\n }),\n };\n }\n\n if (result?.notFound) {\n return {\n kind: \"response\",\n response: options.isDataReq\n ? buildPagesDataNotFoundResponse()\n : buildPagesNotFoundResponse(),\n };\n }\n\n gsspRes = res;\n }\n\n let isrRevalidateSeconds: number | null = null;\n\n if (typeof options.pageModule.getStaticProps === \"function\") {\n const pathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", pathname);\n const cached = await options.isrGet(cacheKey);\n const cachedValue = cached?.value.value;\n\n if (\n cachedValue?.kind === \"PAGES\" &&\n cached &&\n !cached.isStale &&\n !options.scriptNonce &&\n !options.isDataReq\n ) {\n return {\n kind: \"response\",\n response: buildPagesCacheResponse(\n cachedValue.html,\n \"HIT\",\n options.fontLinkHeader,\n undefined,\n options.expireSeconds,\n cached.value.cacheControl,\n ),\n };\n }\n\n if (\n cachedValue?.kind === \"PAGES\" &&\n cached &&\n cached.isStale &&\n !options.scriptNonce &&\n !options.isDataReq\n ) {\n options.triggerBackgroundRegeneration(\n cacheKey,\n async function () {\n return options.runInFreshUnifiedContext(async () => {\n const freshResult = await options.pageModule.getStaticProps?.({\n params: userFacingParams,\n locale: options.i18n.locale,\n locales: options.i18n.locales,\n defaultLocale: options.i18n.defaultLocale,\n });\n\n if (\n freshResult?.props &&\n typeof freshResult.revalidate === \"number\" &&\n freshResult.revalidate > 0\n ) {\n options.applyRequestContexts();\n const freshHtml = await renderPagesIsrHtml({\n buildId: options.buildId,\n cachedHtml: cachedValue.html,\n createPageElement: options.createPageElement,\n i18n: options.i18n,\n pageProps: freshResult.props,\n params: options.params,\n renderIsrPassToStringAsync: options.renderIsrPassToStringAsync,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n });\n\n await options.isrSet(\n cacheKey,\n buildPagesCacheValue(freshHtml, freshResult.props),\n freshResult.revalidate,\n undefined,\n options.expireSeconds,\n );\n }\n });\n },\n {\n routerKind: \"Pages Router\",\n routePath: options.routePattern,\n routeType: \"render\",\n },\n );\n\n return {\n kind: \"response\",\n response: buildPagesCacheResponse(\n cachedValue.html,\n \"STALE\",\n options.fontLinkHeader,\n undefined,\n options.expireSeconds,\n cached.value.cacheControl,\n ),\n };\n }\n\n const result = await options.pageModule.getStaticProps({\n params: userFacingParams,\n locale: options.i18n.locale,\n locales: options.i18n.locales,\n defaultLocale: options.i18n.defaultLocale,\n });\n\n if (result?.props) {\n pageProps = result.props;\n }\n\n if (result?.redirect) {\n return {\n kind: \"response\",\n response: new Response(null, {\n status: resolvePagesRedirectStatus(result.redirect),\n headers: { Location: options.sanitizeDestination(result.redirect.destination) },\n }),\n };\n }\n\n if (result?.notFound) {\n return {\n kind: \"response\",\n response: options.isDataReq\n ? buildPagesDataNotFoundResponse()\n : buildPagesNotFoundResponse(),\n };\n }\n\n if (typeof result?.revalidate === \"number\" && result.revalidate > 0) {\n isrRevalidateSeconds = result.revalidate;\n }\n }\n\n return {\n kind: \"render\",\n gsspRes,\n isrRevalidateSeconds,\n pageProps,\n isFallback: false,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"pages-page-data.js","names":[],"sources":["../../src/server/pages-page-data.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { VinextNextData } from \"../client/vinext-next-data.js\";\nimport type { Route } from \"../routing/pages-router.js\";\nimport { normalizeStaticPathname } from \"../routing/route-pattern.js\";\nimport type { CachedPagesValue, CacheControlMetadata } from \"vinext/shims/cache\";\nimport { buildCachedRevalidateCacheControl } from \"./cache-control.js\";\nimport { buildCacheStateHeaders } from \"./cache-headers.js\";\nimport { buildPagesCacheValue, type ISRCacheEntry } from \"./isr-cache.js\";\nimport {\n buildPagesNextDataScript,\n type PagesGsspResponse,\n type PagesI18nRenderContext,\n} from \"./pages-page-response.js\";\nimport { buildDefaultPagesNotFoundResponse } from \"./pages-default-404.js\";\n\ntype PagesRedirectResult = {\n destination: string;\n permanent?: boolean;\n statusCode?: number;\n};\n\n// Next.js allows `paths` entries to be either an object with a `params` key\n// or a raw string path. We keep a local variant of `StaticPathsEntry` here\n// because at request time we compare against the actual request `params`\n// (whose value type is `unknown` from the route matcher) rather than the\n// `string | string[]` shape used at build time. The shared\n// `normalizeStaticPathname` helper from `../routing/route-pattern.js` is used\n// to canonicalize the string-entry comparison.\ntype PagesStaticPathsEntry = string | { params?: Record<string, unknown>; locale?: string };\n\ntype PagesStaticPathsResult = {\n fallback?: boolean | \"blocking\";\n paths?: PagesStaticPathsEntry[];\n};\n\ntype PagesPagePropsResult = {\n props?: Record<string, unknown>;\n redirect?: PagesRedirectResult;\n notFound?: boolean;\n revalidate?: number;\n};\n\nexport type PagesMutableGsspResponse = {\n headersSent: boolean;\n} & PagesGsspResponse;\n\nexport type PagesGsspContextResponse = {\n req: unknown;\n res: PagesMutableGsspResponse;\n responsePromise: Promise<Response>;\n};\n\nexport type PagesPageModule = {\n default?: unknown;\n getStaticPaths?: (context: {\n locales: string[];\n defaultLocale: string;\n }) => Promise<PagesStaticPathsResult> | PagesStaticPathsResult;\n /**\n * Pages Router data-fetching context.\n *\n * `params` is `null` for non-dynamic routes (no `[param]` segments) to\n * match Next.js. User code typically falls back via `params || null`, so\n * passing `null` (rather than `{}`) is required for the value to be\n * observable as `null` once the data flows through to the page props.\n *\n * See: test/e2e/edge-pages-support/index.test.ts in Next.js for the\n * authoritative assertion (`expect(props.params).toBe(null)`).\n */\n getServerSideProps?: (context: {\n params: Record<string, unknown> | null;\n req: unknown;\n res: PagesMutableGsspResponse;\n query: Record<string, unknown>;\n resolvedUrl: string;\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n }) => Promise<PagesPagePropsResult> | PagesPagePropsResult;\n getStaticProps?: (context: {\n params: Record<string, unknown> | null;\n locale?: string;\n locales?: string[];\n defaultLocale?: string;\n /**\n * Indicates why `getStaticProps` was invoked.\n *\n * - `\"build\"`: initial build-time prerender (before runtime traffic).\n * - `\"on-demand\"`: triggered by `res.revalidate()` from an API route.\n * - `\"stale\"`: stale-while-revalidate background regeneration.\n *\n * Mirrors Next.js `render.tsx`'s `revalidateReason` on the\n * `GetStaticPropsContext` type — see\n * `.nextjs-ref/packages/next/src/types.ts`.\n */\n revalidateReason?: \"build\" | \"on-demand\" | \"stale\";\n }) => Promise<PagesPagePropsResult> | PagesPagePropsResult;\n};\n\ntype RenderPagesIsrHtmlOptions = {\n buildId: string | null;\n cachedHtml: string;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n i18n: PagesI18nRenderContext;\n pageProps: Record<string, unknown>;\n params: Record<string, unknown>;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n routePattern: string;\n safeJsonStringify: (value: unknown) => string;\n vinext?: VinextNextData[\"__vinext\"];\n};\n\nexport type ResolvePagesPageDataOptions = {\n applyRequestContexts: () => void;\n buildId: string | null;\n /**\n * When true, this is a `/_next/data/<buildId>/<page>.json` request. Callers\n * that respond with a JSON envelope (`{ pageProps }`) instead of HTML must\n * bypass the HTML ISR cache: a cached HTML body cannot be reshaped into the\n * expected JSON shape, and storing JSON in the HTML cache would corrupt\n * subsequent HTML hits. Next.js handles this the same way — see\n * `isNextDataRequest` checks in `packages/next/src/server/base-server.ts`.\n */\n isDataReq?: boolean;\n createGsspReqRes: () => PagesGsspContextResponse;\n createPageElement: (pageProps: Record<string, unknown>) => ReactNode;\n fontLinkHeader: string;\n i18n: PagesI18nRenderContext;\n isrCacheKey: (router: string, pathname: string) => string;\n isrGet: (key: string) => Promise<ISRCacheEntry | null>;\n isrSet: (\n key: string,\n data: CachedPagesValue,\n revalidateSeconds: number,\n tags?: string[],\n expireSeconds?: number,\n ) => Promise<void>;\n expireSeconds?: number;\n /**\n * When true, this dispatch corresponds to a build-time prerender (the\n * `vinext` build phase fetches each statically generated page through the\n * production server). Maps to `revalidateReason: \"build\"` when\n * `getStaticProps` is invoked. Mirrors Next.js's\n * `renderOpts.isBuildTimePrerendering` flag — see\n * `.nextjs-ref/packages/next/src/server/render.tsx`.\n */\n isBuildTimePrerendering?: boolean;\n /**\n * When true, this dispatch was triggered by an on-demand revalidation\n * request (e.g. `res.revalidate()` in a Pages Router API route, or an\n * equivalent webhook). Maps to `revalidateReason: \"on-demand\"` when\n * `getStaticProps` is invoked. Mirrors Next.js's\n * `renderOpts.isOnDemandRevalidate` flag — see\n * `.nextjs-ref/packages/next/src/server/render.tsx`.\n *\n * Forward-looking plumbing: no caller currently sets this — `res.revalidate()`\n * is not yet implemented in vinext. The `\"on-demand\"` branch in the\n * `revalidateReason` resolver is intentionally unreachable today; keeping the\n * typed contract here means wiring it up will be a one-line change once the\n * trigger lands.\n */\n isOnDemandRevalidate?: boolean;\n pageModule: PagesPageModule;\n params: Record<string, unknown>;\n query: Record<string, unknown>;\n route: Pick<Route, \"isDynamic\">;\n routePattern: string;\n routeUrl: string;\n runInFreshUnifiedContext: <T>(callback: () => Promise<T>) => Promise<T>;\n safeJsonStringify: (value: unknown) => string;\n sanitizeDestination: (destination: string) => string;\n scriptNonce?: string;\n statusCode?: number;\n triggerBackgroundRegeneration: (\n key: string,\n renderFn: () => Promise<void>,\n errorContext?: { routerKind: \"Pages Router\"; routePath: string; routeType: \"render\" },\n ) => void;\n renderIsrPassToStringAsync: (element: ReactNode) => Promise<string>;\n vinext?: VinextNextData[\"__vinext\"];\n};\n\ntype ResolvePagesPageDataRenderResult = {\n kind: \"render\";\n gsspRes: PagesGsspResponse | null;\n isrRevalidateSeconds: number | null;\n pageProps: Record<string, unknown>;\n /**\n * True when `getStaticPaths` returned `fallback: true` AND the requested path\n * is not in the pre-rendered list. The caller renders a loading shell with\n * empty props and `useRouter().isFallback === true` (matching Next.js's\n * `render.tsx` — `getStaticProps` is skipped on the fallback render).\n */\n isFallback: boolean;\n};\n\ntype ResolvePagesPageDataResponseResult = {\n kind: \"response\";\n response: Response;\n};\n\ntype ResolvePagesPageDataResult =\n | ResolvePagesPageDataRenderResult\n | ResolvePagesPageDataResponseResult;\n\nfunction buildPagesNotFoundResponse(): Response {\n return buildDefaultPagesNotFoundResponse();\n}\n\nfunction buildPagesDataNotFoundResponse(): Response {\n // Matches Next.js: `/_next/data/<buildId>/<page>.json` 404 responses use\n // application/json with an empty object body so clients can call\n // `res.json()` without throwing before inspecting the status code.\n return new Response(\"{}\", {\n status: 404,\n headers: { \"Content-Type\": \"application/json\" },\n });\n}\n\nfunction resolvePagesRedirectStatus(redirect: PagesRedirectResult): number {\n return redirect.statusCode != null ? redirect.statusCode : redirect.permanent ? 308 : 307;\n}\n\n/**\n * Compare a `getStaticPaths` entry against the actual request params.\n *\n * Handles both shapes Next.js allows:\n * - { params: { ... } }\n * - \"string-path\"\n *\n * For a string entry, compare the entry against the current request URL using\n * the shared `normalizeStaticPathname` helper from\n * `../routing/route-pattern.ts` (which mirrors the Next.js\n * `removeTrailingSlash` behaviour in\n * `.nextjs-ref/packages/next/src/build/static-paths/pages.ts`). For an object\n * entry with a missing `params` key, return false rather than throwing — the\n * caller will respond with a 404 just like Next.js does for unlisted paths.\n */\nfunction matchesPagesStaticPath(\n pathEntry: PagesStaticPathsEntry,\n params: Record<string, unknown>,\n routeUrl: string,\n): boolean {\n if (typeof pathEntry === \"string\") {\n return normalizeStaticPathname(pathEntry) === normalizeStaticPathname(routeUrl);\n }\n const entryParams = pathEntry.params;\n if (entryParams === undefined || entryParams === null) {\n return false;\n }\n return Object.entries(entryParams).every(([key, value]) => {\n const actual = params[key];\n if (Array.isArray(value)) {\n return Array.isArray(actual) && value.join(\"/\") === actual.join(\"/\");\n }\n return String(value) === String(actual);\n });\n}\n\nfunction buildPagesCacheResponse(\n html: string,\n cacheState: \"HIT\" | \"STALE\",\n fontLinkHeader: string,\n revalidateSeconds?: number,\n expireSeconds?: number,\n cacheControl?: CacheControlMetadata,\n status?: number,\n): Response {\n // Legacy cache entries written before cacheControl metadata existed can still\n // hit this path without a persisted revalidate value; keep the historic\n // 60-second fallback for that migration window.\n const effectiveRevalidateSeconds = cacheControl?.revalidate ?? revalidateSeconds ?? 60;\n const effectiveExpireSeconds =\n cacheControl === undefined ? undefined : (cacheControl.expire ?? expireSeconds);\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/html\",\n ...buildCacheStateHeaders(cacheState),\n \"Cache-Control\": buildCachedRevalidateCacheControl(\n cacheState,\n effectiveRevalidateSeconds,\n effectiveExpireSeconds,\n ),\n };\n\n if (fontLinkHeader) {\n headers.Link = fontLinkHeader;\n }\n\n return new Response(html, {\n status: status ?? 200,\n headers,\n });\n}\n\nfunction rewritePagesCachedHtml(\n cachedHtml: string,\n freshBody: string,\n nextDataScript: string,\n): string {\n const bodyMarker = '<div id=\"__next\">';\n const bodyStart = cachedHtml.indexOf(bodyMarker);\n const contentStart = bodyStart >= 0 ? bodyStart + bodyMarker.length : -1;\n // This intentionally looks for the bare inline __NEXT_DATA__ marker.\n // Pages responses with scriptNonce are excluded from ISR writes, so cached\n // HTML should never contain nonce-prefixed __NEXT_DATA__ scripts here.\n const nextDataMarker = \"<script>window.__NEXT_DATA__\";\n const nextDataStart = cachedHtml.indexOf(nextDataMarker);\n\n if (contentStart >= 0 && nextDataStart >= 0) {\n const region = cachedHtml.slice(contentStart, nextDataStart);\n const lastCloseDiv = region.lastIndexOf(\"</div>\");\n const gap = lastCloseDiv >= 0 ? region.slice(lastCloseDiv + 6) : \"\";\n const nextDataEnd = cachedHtml.indexOf(\"</script>\", nextDataStart) + 9;\n const tail = cachedHtml.slice(nextDataEnd);\n\n return cachedHtml.slice(0, contentStart) + freshBody + \"</div>\" + gap + nextDataScript + tail;\n }\n\n return (\n '<!DOCTYPE html>\\n<html>\\n<head>\\n</head>\\n<body>\\n <div id=\"__next\">' +\n freshBody +\n \"</div>\\n \" +\n nextDataScript +\n \"\\n</body>\\n</html>\"\n );\n}\n\nexport async function renderPagesIsrHtml(options: RenderPagesIsrHtmlOptions): Promise<string> {\n const freshBody = await options.renderIsrPassToStringAsync(\n options.createPageElement(options.pageProps),\n );\n const nextDataScript = buildPagesNextDataScript({\n buildId: options.buildId,\n i18n: options.i18n,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n vinext: options.vinext,\n });\n\n return rewritePagesCachedHtml(options.cachedHtml, freshBody, nextDataScript);\n}\n\nexport async function resolvePagesPageData(\n options: ResolvePagesPageDataOptions,\n): Promise<ResolvePagesPageDataResult> {\n // Next.js passes `params: null` (effectively) to gSSP/gSP context for\n // non-dynamic routes — see render.tsx's `...(pageIsDynamic ? { params } : undefined)`.\n // Internal bookkeeping (route param hydration, ISR HTML, getStaticPaths\n // validation) still uses the matched-but-empty object — only user-facing\n // data-fetching contexts surface `null`.\n const userFacingParams: Record<string, unknown> | null = options.route.isDynamic\n ? options.params\n : null;\n\n // Set when `getStaticPaths: { fallback: true }` is configured and the\n // requested path is NOT in the pre-rendered list. When true, we render the\n // loading shell with empty props and `useRouter().isFallback === true`,\n // skipping `getStaticProps`. Matches Next.js `render.tsx`'s\n // `if (isSSG && !isFallback)` gate around `getStaticProps`. Data requests\n // (`/_next/data/...json`) still call `getStaticProps` so the client can\n // hydrate the page after the fallback shell ships.\n let isFallback = false;\n\n if (typeof options.pageModule.getStaticPaths === \"function\" && options.route.isDynamic) {\n const pathsResult = await options.pageModule.getStaticPaths({\n locales: options.i18n.locales ?? [],\n defaultLocale: options.i18n.defaultLocale ?? \"\",\n });\n const fallback = pathsResult?.fallback ?? false;\n const paths = pathsResult?.paths ?? [];\n const isValidPath = paths.some((pathEntry) =>\n matchesPagesStaticPath(pathEntry, options.params, options.routeUrl),\n );\n\n if (fallback === false && !isValidPath) {\n // For data requests (`/_next/data/...json`), return a JSON-shaped 404\n // so the client router can `res.json()` without blowing up — matches\n // Next.js' behavior. HTML navigations still get the HTML 404 page.\n return {\n kind: \"response\",\n response: options.isDataReq\n ? buildPagesDataNotFoundResponse()\n : buildPagesNotFoundResponse(),\n };\n }\n\n // Render the fallback shell for unlisted paths under `fallback: true`.\n // Data requests resolve props normally so the client can fill in after\n // the loading shell ships (`fallback: 'blocking'` keeps SSRing as before).\n if (fallback === true && !isValidPath && !options.isDataReq) {\n isFallback = true;\n }\n }\n\n let pageProps: Record<string, unknown> = {};\n let gsspRes: PagesMutableGsspResponse | null = null;\n\n if (isFallback) {\n return {\n kind: \"render\",\n gsspRes: null,\n isrRevalidateSeconds: null,\n pageProps,\n isFallback: true,\n };\n }\n\n if (typeof options.pageModule.getServerSideProps === \"function\") {\n const { req, res, responsePromise } = options.createGsspReqRes();\n const result = await options.pageModule.getServerSideProps({\n params: userFacingParams,\n req,\n res,\n query: options.query,\n resolvedUrl: options.routeUrl,\n locale: options.i18n.locale,\n locales: options.i18n.locales,\n defaultLocale: options.i18n.defaultLocale,\n });\n\n if (res.headersSent) {\n return {\n kind: \"response\",\n response: await responsePromise,\n };\n }\n\n if (result?.props) {\n // Next.js explicitly supports a Promise value for `props`. Await it\n // before serialising; otherwise pageProps would be a Promise and the\n // rendered page would receive empty props. See\n // packages/next/src/server/render.tsx (deferredContent).\n pageProps = (await Promise.resolve(result.props)) as Record<string, unknown>;\n }\n\n if (result?.redirect) {\n return {\n kind: \"response\",\n response: new Response(null, {\n status: resolvePagesRedirectStatus(result.redirect),\n headers: { Location: options.sanitizeDestination(result.redirect.destination) },\n }),\n };\n }\n\n if (result?.notFound) {\n return {\n kind: \"response\",\n response: options.isDataReq\n ? buildPagesDataNotFoundResponse()\n : buildPagesNotFoundResponse(),\n };\n }\n\n gsspRes = res;\n }\n\n let isrRevalidateSeconds: number | null = null;\n\n if (typeof options.pageModule.getStaticProps === \"function\") {\n const pathname = options.routeUrl.split(\"?\")[0];\n const cacheKey = options.isrCacheKey(\"pages\", pathname);\n const cached = await options.isrGet(cacheKey);\n const cachedValue = cached?.value.value;\n\n if (\n cachedValue?.kind === \"PAGES\" &&\n cached &&\n !cached.isStale &&\n !options.scriptNonce &&\n !options.isDataReq\n ) {\n return {\n kind: \"response\",\n response: buildPagesCacheResponse(\n cachedValue.html,\n \"HIT\",\n options.fontLinkHeader,\n undefined,\n options.expireSeconds,\n cached.value.cacheControl,\n cachedValue.status,\n ),\n };\n }\n\n if (\n cachedValue?.kind === \"PAGES\" &&\n cached &&\n cached.isStale &&\n !options.scriptNonce &&\n !options.isDataReq\n ) {\n options.triggerBackgroundRegeneration(\n cacheKey,\n async function () {\n return options.runInFreshUnifiedContext(async () => {\n const freshResult = await options.pageModule.getStaticProps?.({\n params: userFacingParams,\n locale: options.i18n.locale,\n locales: options.i18n.locales,\n defaultLocale: options.i18n.defaultLocale,\n // Background regeneration for an entry that is already in the\n // cache is always a stale-while-revalidate refresh — mirrors\n // Next.js `render.tsx` (`isBuildTimeSSG ? \"build\" : \"stale\"`,\n // and we're not at build time here).\n revalidateReason: \"stale\",\n });\n\n if (\n freshResult?.props &&\n typeof freshResult.revalidate === \"number\" &&\n freshResult.revalidate > 0\n ) {\n options.applyRequestContexts();\n const freshHtml = await renderPagesIsrHtml({\n buildId: options.buildId,\n cachedHtml: cachedValue.html,\n createPageElement: options.createPageElement,\n i18n: options.i18n,\n pageProps: freshResult.props,\n params: options.params,\n renderIsrPassToStringAsync: options.renderIsrPassToStringAsync,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n vinext: options.vinext,\n });\n\n await options.isrSet(\n cacheKey,\n buildPagesCacheValue(freshHtml, freshResult.props, options.statusCode),\n freshResult.revalidate,\n undefined,\n options.expireSeconds,\n );\n }\n });\n },\n {\n routerKind: \"Pages Router\",\n routePath: options.routePattern,\n routeType: \"render\",\n },\n );\n\n return {\n kind: \"response\",\n response: buildPagesCacheResponse(\n cachedValue.html,\n \"STALE\",\n options.fontLinkHeader,\n undefined,\n options.expireSeconds,\n cached.value.cacheControl,\n cachedValue.status,\n ),\n };\n }\n\n const result = await options.pageModule.getStaticProps({\n params: userFacingParams,\n locale: options.i18n.locale,\n locales: options.i18n.locales,\n defaultLocale: options.i18n.defaultLocale,\n // Maps Next.js's resolution in `render.tsx`:\n // isOnDemandRevalidate ? \"on-demand\"\n // : isBuildTimeSSG ? \"build\"\n // : \"stale\"\n // We pick \"stale\" as the default at runtime so existing-but-missing\n // (cache evicted) entries surface as a regeneration rather than a build.\n revalidateReason: options.isOnDemandRevalidate\n ? \"on-demand\"\n : options.isBuildTimePrerendering\n ? \"build\"\n : \"stale\",\n });\n\n if (result?.props) {\n pageProps = result.props;\n }\n\n if (result?.redirect) {\n return {\n kind: \"response\",\n response: new Response(null, {\n status: resolvePagesRedirectStatus(result.redirect),\n headers: { Location: options.sanitizeDestination(result.redirect.destination) },\n }),\n };\n }\n\n if (result?.notFound) {\n return {\n kind: \"response\",\n response: options.isDataReq\n ? buildPagesDataNotFoundResponse()\n : buildPagesNotFoundResponse(),\n };\n }\n\n if (typeof result?.revalidate === \"number\" && result.revalidate > 0) {\n isrRevalidateSeconds = result.revalidate;\n }\n }\n\n return {\n kind: \"render\",\n gsspRes,\n isrRevalidateSeconds,\n pageProps,\n isFallback: false,\n };\n}\n"],"mappings":"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"}
|
|
@@ -51,6 +51,8 @@ type RenderPagesPageResponseOptions = {
|
|
|
51
51
|
routeUrl: string;
|
|
52
52
|
safeJsonStringify: (value: unknown) => string;
|
|
53
53
|
scriptNonce?: string;
|
|
54
|
+
statusCode?: number;
|
|
55
|
+
vinext?: VinextNextData["__vinext"];
|
|
54
56
|
};
|
|
55
57
|
declare function buildPagesNextDataScript(options: Pick<RenderPagesPageResponseOptions, "buildId" | "i18n" | "isFallback" | "pageProps" | "params" | "routePattern" | "safeJsonStringify" | "scriptNonce"> & {
|
|
56
58
|
vinext?: VinextNextData["__vinext"];
|
|
@@ -90,12 +90,12 @@ function schedulePagesIsrCacheWrite(options) {
|
|
|
90
90
|
html: options.shellPrefix + bodyHtml + options.shellSuffix,
|
|
91
91
|
pageData: options.pageData,
|
|
92
92
|
headers: void 0,
|
|
93
|
-
status:
|
|
93
|
+
status: options.status
|
|
94
94
|
}, options.revalidateSeconds, void 0, options.expireSeconds)).catch((error) => reportPagesIsrCacheWriteError(error, options.cacheKey, options.routePattern));
|
|
95
95
|
getRequestExecutionContext()?.waitUntil(cacheWritePromise);
|
|
96
96
|
}
|
|
97
|
-
function applyGsspHeaders(headers, gsspRes) {
|
|
98
|
-
if (!gsspRes) return 200;
|
|
97
|
+
function applyGsspHeaders(headers, gsspRes, statusCode) {
|
|
98
|
+
if (!gsspRes) return statusCode ?? 200;
|
|
99
99
|
const gsspHeaders = gsspRes.getHeaders();
|
|
100
100
|
for (const key of Object.keys(gsspHeaders)) {
|
|
101
101
|
const value = gsspHeaders[key];
|
|
@@ -110,7 +110,7 @@ function applyGsspHeaders(headers, gsspRes) {
|
|
|
110
110
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") headers.set(key, String(value));
|
|
111
111
|
}
|
|
112
112
|
headers.set("Content-Type", "text/html");
|
|
113
|
-
return gsspRes.statusCode;
|
|
113
|
+
return statusCode ?? gsspRes.statusCode;
|
|
114
114
|
}
|
|
115
115
|
async function renderPagesPageResponse(options) {
|
|
116
116
|
const pageElement = withScriptNonce(React.createElement(React.Fragment, null, options.createPageElement(options.pageProps)), options.scriptNonce);
|
|
@@ -125,7 +125,8 @@ async function renderPagesPageResponse(options) {
|
|
|
125
125
|
params: options.params,
|
|
126
126
|
routePattern: options.routePattern,
|
|
127
127
|
safeJsonStringify: options.safeJsonStringify,
|
|
128
|
-
scriptNonce: options.scriptNonce
|
|
128
|
+
scriptNonce: options.scriptNonce,
|
|
129
|
+
vinext: options.vinext
|
|
129
130
|
});
|
|
130
131
|
const bodyMarker = "<!--VINEXT_STREAM_BODY-->";
|
|
131
132
|
const bodyStream = await options.renderToReadableStream(pageElement);
|
|
@@ -139,6 +140,8 @@ async function renderPagesPageResponse(options) {
|
|
|
139
140
|
const markerIndex = shellHtml.indexOf(bodyMarker);
|
|
140
141
|
const shellPrefix = shellHtml.slice(0, markerIndex);
|
|
141
142
|
const shellSuffix = shellHtml.slice(markerIndex + 25);
|
|
143
|
+
const responseHeaders = new Headers({ "Content-Type": "text/html" });
|
|
144
|
+
const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes, options.statusCode);
|
|
142
145
|
let responseBodyStream = bodyStream;
|
|
143
146
|
if (!options.scriptNonce && options.isrRevalidateSeconds !== null && options.isrRevalidateSeconds > 0) {
|
|
144
147
|
const cacheBodyStreamPair = bodyStream.tee();
|
|
@@ -154,17 +157,17 @@ async function renderPagesPageResponse(options) {
|
|
|
154
157
|
setCache: options.isrSet,
|
|
155
158
|
shellPrefix,
|
|
156
159
|
shellSuffix,
|
|
160
|
+
status: finalStatus,
|
|
157
161
|
stream: cacheBodyStream
|
|
158
162
|
});
|
|
159
163
|
}
|
|
160
164
|
const compositeStream = await buildPagesCompositeStream(responseBodyStream, shellPrefix, shellSuffix);
|
|
161
|
-
const
|
|
162
|
-
const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes);
|
|
165
|
+
const userSetCacheControl = responseHeaders.has("Cache-Control");
|
|
163
166
|
if (options.scriptNonce) responseHeaders.set("Cache-Control", "no-store, must-revalidate");
|
|
164
167
|
else if (options.isrRevalidateSeconds) {
|
|
165
168
|
responseHeaders.set("Cache-Control", buildRevalidateCacheControl(options.isrRevalidateSeconds, options.expireSeconds));
|
|
166
169
|
setCacheStateHeaders(responseHeaders, "MISS");
|
|
167
|
-
}
|
|
170
|
+
} else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", "private, no-cache, no-store, max-age=0, must-revalidate");
|
|
168
171
|
if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
|
|
169
172
|
return Object.assign(new Response(compositeStream, {
|
|
170
173
|
status: finalStatus,
|
|
@@ -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 { reportRequestError } from \"./instrumentation.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 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};\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 let html = await options.renderDocumentToString(React.createElement(options.DocumentComponent));\n html = html.replace(\"__NEXT_MAIN__\", bodyMarker);\n if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) {\n html = html.replace(\n \"</head>\",\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n ${options.assetTags}\\n</head>`,\n );\n }\n html = html.replace(\"<!-- __NEXT_SCRIPTS__ -->\", nextDataScript);\n if (!html.includes(\"__NEXT_DATA__\")) {\n html = html.replace(\"</body>\", ` ${nextDataScript}\\n</body>`);\n }\n return html;\n }\n\n return (\n \"<!DOCTYPE html>\\n<html>\\n<head>\\n\" +\n ' <meta charset=\"utf-8\" />\\n' +\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\\n' +\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n` +\n ` ${options.assetTags}\\n` +\n \"</head>\\n<body>\\n\" +\n ` <div id=\"__next\">${bodyMarker}</div>\\n` +\n ` ${nextDataScript}\\n` +\n \"</body>\\n</html>\"\n );\n}\n\nasync function buildPagesCompositeStream(\n bodyStream: ReadableStream<Uint8Array>,\n shellPrefix: string,\n shellSuffix: string,\n): Promise<ReadableStream<Uint8Array>> {\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n controller.enqueue(encoder.encode(shellPrefix));\n const reader = bodyStream.getReader();\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n break;\n }\n controller.enqueue(chunk.value);\n }\n } finally {\n reader.releaseLock();\n }\n controller.enqueue(encoder.encode(shellSuffix));\n controller.close();\n },\n });\n}\n\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 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: undefined,\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(headers: Headers, gsspRes: PagesGsspResponse | null): number {\n if (!gsspRes) {\n return 200;\n }\n\n const gsspHeaders = gsspRes.getHeaders();\n for (const key of Object.keys(gsspHeaders)) {\n const value = gsspHeaders[key];\n const lowerKey = key.toLowerCase();\n if (lowerKey === \"set-cookie\" && Array.isArray(value)) {\n for (const cookie of value) {\n headers.append(\"set-cookie\", String(cookie));\n }\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(key, value.join(\", \"));\n continue;\n }\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n headers.set(key, String(value));\n }\n }\n headers.set(\"Content-Type\", \"text/html\");\n return gsspRes.statusCode;\n}\n\nexport async function renderPagesPageResponse(\n options: RenderPagesPageResponseOptions,\n): Promise<Response> {\n const pageElement = withScriptNonce(\n React.createElement(React.Fragment, null, options.createPageElement(options.pageProps)),\n options.scriptNonce,\n );\n\n options.resetSSRHead?.();\n await options.flushPreloads?.();\n\n const fontHeadHTML = buildPagesFontHeadHtml(\n options.getFontLinks(),\n options.fontPreloads,\n options.getFontStyles(),\n options.scriptNonce,\n );\n const nextDataScript = buildPagesNextDataScript({\n buildId: options.buildId,\n i18n: options.i18n,\n isFallback: options.isFallback,\n pageProps: options.pageProps,\n params: options.params,\n routePattern: options.routePattern,\n safeJsonStringify: options.safeJsonStringify,\n scriptNonce: options.scriptNonce,\n });\n const bodyMarker = \"<!--VINEXT_STREAM_BODY-->\";\n // 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 shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML: options.getSSRHeadHTML?.() ?? \"\",\n });\n\n options.clearSsrContext();\n\n const markerIndex = shellHtml.indexOf(bodyMarker);\n const shellPrefix = shellHtml.slice(0, markerIndex);\n const shellSuffix = shellHtml.slice(markerIndex + bodyMarker.length);\n\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 stream: cacheBodyStream,\n });\n }\n\n const compositeStream = await buildPagesCompositeStream(\n responseBodyStream,\n shellPrefix,\n shellSuffix,\n );\n\n const responseHeaders = new Headers({ \"Content-Type\": \"text/html\" });\n const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes);\n\n if (options.scriptNonce) {\n responseHeaders.set(\"Cache-Control\", \"no-store, must-revalidate\");\n } else if (options.isrRevalidateSeconds) {\n responseHeaders.set(\n \"Cache-Control\",\n buildRevalidateCacheControl(options.isrRevalidateSeconds, options.expireSeconds),\n );\n setCacheStateHeaders(responseHeaders, \"MISS\");\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 { reportRequestError } from \"./instrumentation.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 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 let html = await options.renderDocumentToString(React.createElement(options.DocumentComponent));\n html = html.replace(\"__NEXT_MAIN__\", bodyMarker);\n if (options.ssrHeadHTML || options.assetTags || fontHeadHTML) {\n html = html.replace(\n \"</head>\",\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n ${options.assetTags}\\n</head>`,\n );\n }\n html = html.replace(\"<!-- __NEXT_SCRIPTS__ -->\", nextDataScript);\n if (!html.includes(\"__NEXT_DATA__\")) {\n html = html.replace(\"</body>\", ` ${nextDataScript}\\n</body>`);\n }\n return html;\n }\n\n return (\n \"<!DOCTYPE html>\\n<html>\\n<head>\\n\" +\n ' <meta charset=\"utf-8\" />\\n' +\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\\n' +\n ` ${fontHeadHTML}${options.ssrHeadHTML}\\n` +\n ` ${options.assetTags}\\n` +\n \"</head>\\n<body>\\n\" +\n ` <div id=\"__next\">${bodyMarker}</div>\\n` +\n ` ${nextDataScript}\\n` +\n \"</body>\\n</html>\"\n );\n}\n\nasync function buildPagesCompositeStream(\n bodyStream: ReadableStream<Uint8Array>,\n shellPrefix: string,\n shellSuffix: string,\n): Promise<ReadableStream<Uint8Array>> {\n const encoder = new TextEncoder();\n\n return new ReadableStream({\n async start(controller) {\n controller.enqueue(encoder.encode(shellPrefix));\n const reader = bodyStream.getReader();\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n break;\n }\n controller.enqueue(chunk.value);\n }\n } finally {\n reader.releaseLock();\n }\n controller.enqueue(encoder.encode(shellSuffix));\n controller.close();\n },\n });\n}\n\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 shellHtml = await buildPagesShellHtml(bodyMarker, fontHeadHTML, nextDataScript, {\n assetTags: options.assetTags,\n DocumentComponent: options.DocumentComponent,\n renderDocumentToString: options.renderDocumentToString,\n ssrHeadHTML: options.getSSRHeadHTML?.() ?? \"\",\n });\n\n options.clearSsrContext();\n\n const markerIndex = shellHtml.indexOf(bodyMarker);\n const shellPrefix = shellHtml.slice(0, markerIndex);\n const shellSuffix = shellHtml.slice(markerIndex + bodyMarker.length);\n const 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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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/server/prerender-route-params.d.ts
|
|
2
|
+
type PrerenderRouteParams = Record<string, string | string[]>;
|
|
3
|
+
type PrerenderRouteParamsPayload = {
|
|
4
|
+
params: PrerenderRouteParams;
|
|
5
|
+
routePattern: string;
|
|
6
|
+
};
|
|
7
|
+
declare function serializePrerenderRouteParamsHeader(payload: PrerenderRouteParamsPayload | null): string | null;
|
|
8
|
+
declare function readTrustedPrerenderRouteParamsFromHeaders(headers: Headers, expectedSecret?: string): PrerenderRouteParamsPayload | null;
|
|
9
|
+
declare function readTrustedPrerenderRouteParams(request: Request): PrerenderRouteParamsPayload | null;
|
|
10
|
+
declare function prerenderRouteParamsPayloadMatchesRoute(payload: PrerenderRouteParamsPayload | null, routePattern: string, params: PrerenderRouteParams): payload is PrerenderRouteParamsPayload;
|
|
11
|
+
declare function encodePrerenderRouteParams(pattern: string, params: PrerenderRouteParams): PrerenderRouteParamsPayload | null;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { PrerenderRouteParams, PrerenderRouteParamsPayload, encodePrerenderRouteParams, prerenderRouteParamsPayloadMatchesRoute, readTrustedPrerenderRouteParams, readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader };
|
|
14
|
+
//# sourceMappingURL=prerender-route-params.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "./headers.js";
|
|
2
|
+
import { isUnknownRecord } from "../utils/record.js";
|
|
3
|
+
//#region src/server/prerender-route-params.ts
|
|
4
|
+
function isPrerenderRouteParams(value) {
|
|
5
|
+
if (!isUnknownRecord(value)) return false;
|
|
6
|
+
for (const [, param] of Object.entries(value)) {
|
|
7
|
+
if (typeof param === "string") continue;
|
|
8
|
+
if (Array.isArray(param) && param.every((item) => typeof item === "string")) continue;
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
function isPrerenderRouteParamsPayload(value) {
|
|
14
|
+
if (!isUnknownRecord(value)) return false;
|
|
15
|
+
if (Object.keys(value).length !== 2) return false;
|
|
16
|
+
return typeof value.routePattern === "string" && value.routePattern.startsWith("/") && isPrerenderRouteParams(value.params);
|
|
17
|
+
}
|
|
18
|
+
function serializePrerenderRouteParamsHeader(payload) {
|
|
19
|
+
if (payload === null || Object.keys(payload.params).length === 0) return null;
|
|
20
|
+
return encodeURIComponent(JSON.stringify(payload));
|
|
21
|
+
}
|
|
22
|
+
function parsePrerenderRouteParamsHeader(value) {
|
|
23
|
+
if (value === null || value === "") return null;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(decodeURIComponent(value));
|
|
26
|
+
return isPrerenderRouteParamsPayload(parsed) ? parsed : null;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function readTrustedPrerenderRouteParamsFromHeaders(headers, expectedSecret) {
|
|
32
|
+
if (process.env.VINEXT_PRERENDER !== "1") return null;
|
|
33
|
+
const secret = headers.get(VINEXT_PRERENDER_SECRET_HEADER);
|
|
34
|
+
if (secret === null) return null;
|
|
35
|
+
if (expectedSecret !== void 0 && secret !== expectedSecret) return null;
|
|
36
|
+
const header = headers.get(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER);
|
|
37
|
+
if (header === null) return null;
|
|
38
|
+
const params = parsePrerenderRouteParamsHeader(header);
|
|
39
|
+
if (params === null) throw new Error("[vinext] Invalid internal prerender route params header.");
|
|
40
|
+
return params;
|
|
41
|
+
}
|
|
42
|
+
function readTrustedPrerenderRouteParams(request) {
|
|
43
|
+
return readTrustedPrerenderRouteParamsFromHeaders(request.headers);
|
|
44
|
+
}
|
|
45
|
+
function decodePrerenderRouteParam(value) {
|
|
46
|
+
try {
|
|
47
|
+
return decodeURIComponent(value);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function decodedPrerenderRouteParamEquals(prerenderValue, matchedValue) {
|
|
53
|
+
if (Array.isArray(prerenderValue) || Array.isArray(matchedValue)) {
|
|
54
|
+
if (!Array.isArray(prerenderValue) || !Array.isArray(matchedValue)) return false;
|
|
55
|
+
if (prerenderValue.length !== matchedValue.length) return false;
|
|
56
|
+
return prerenderValue.every((item, index) => {
|
|
57
|
+
const decoded = decodePrerenderRouteParam(item);
|
|
58
|
+
return decoded !== null && decoded === matchedValue[index];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const decoded = decodePrerenderRouteParam(prerenderValue);
|
|
62
|
+
return decoded !== null && decoded === matchedValue;
|
|
63
|
+
}
|
|
64
|
+
function prerenderRouteParamsPayloadMatchesRoute(payload, routePattern, params) {
|
|
65
|
+
if (payload === null) return false;
|
|
66
|
+
if (payload.routePattern !== routePattern) return false;
|
|
67
|
+
if (Object.keys(payload.params).length !== Object.keys(params).length) return false;
|
|
68
|
+
for (const [key, prerenderValue] of Object.entries(payload.params)) {
|
|
69
|
+
const matchedValue = params[key];
|
|
70
|
+
if (matchedValue === void 0) return false;
|
|
71
|
+
if (!decodedPrerenderRouteParamEquals(prerenderValue, matchedValue)) return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
function encodePrerenderRouteParams(pattern, params) {
|
|
76
|
+
const encoded = {};
|
|
77
|
+
for (const part of pattern.split("/").filter(Boolean)) {
|
|
78
|
+
let paramName = null;
|
|
79
|
+
if (part.startsWith(":") && (part.endsWith("+") || part.endsWith("*"))) paramName = part.slice(1, -1);
|
|
80
|
+
else if (part.startsWith(":")) paramName = part.slice(1);
|
|
81
|
+
if (paramName === null) continue;
|
|
82
|
+
const value = params[paramName];
|
|
83
|
+
if (Array.isArray(value)) encoded[paramName] = value.map((item) => encodeURIComponent(item));
|
|
84
|
+
else if (typeof value === "string") encoded[paramName] = encodeURIComponent(value);
|
|
85
|
+
}
|
|
86
|
+
return Object.keys(encoded).length > 0 ? {
|
|
87
|
+
routePattern: pattern,
|
|
88
|
+
params: encoded
|
|
89
|
+
} : null;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { encodePrerenderRouteParams, prerenderRouteParamsPayloadMatchesRoute, readTrustedPrerenderRouteParams, readTrustedPrerenderRouteParamsFromHeaders, serializePrerenderRouteParamsHeader };
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=prerender-route-params.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerender-route-params.js","names":[],"sources":["../../src/server/prerender-route-params.ts"],"sourcesContent":["import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from \"./headers.js\";\nimport { isUnknownRecord } from \"../utils/record.js\";\n\nexport type PrerenderRouteParams = Record<string, string | string[]>;\n\nexport type PrerenderRouteParamsPayload = {\n params: PrerenderRouteParams;\n routePattern: string;\n};\n\nfunction isPrerenderRouteParams(value: unknown): value is PrerenderRouteParams {\n if (!isUnknownRecord(value)) return false;\n\n for (const [, param] of Object.entries(value)) {\n if (typeof param === \"string\") continue;\n if (Array.isArray(param) && param.every((item) => typeof item === \"string\")) continue;\n return false;\n }\n\n return true;\n}\n\nfunction isPrerenderRouteParamsPayload(value: unknown): value is PrerenderRouteParamsPayload {\n if (!isUnknownRecord(value)) return false;\n if (Object.keys(value).length !== 2) return false;\n return (\n typeof value.routePattern === \"string\" &&\n value.routePattern.startsWith(\"/\") &&\n isPrerenderRouteParams(value.params)\n );\n}\n\n// A payload with no dynamic params serializes to `null`, which is\n// indistinguishable from an absent header on the read side. This is intentional:\n// the only producer, `encodePrerenderRouteParams`, already returns `null` for\n// param-less patterns, so an empty-params payload never carries information worth\n// propagating. Routes with no dynamic segments need no encoded-render override.\nexport function serializePrerenderRouteParamsHeader(\n payload: PrerenderRouteParamsPayload | null,\n): string | null {\n if (payload === null || Object.keys(payload.params).length === 0) return null;\n return encodeURIComponent(JSON.stringify(payload));\n}\n\nfunction parsePrerenderRouteParamsHeader(value: string | null): PrerenderRouteParamsPayload | null {\n if (value === null || value === \"\") return null;\n\n try {\n const parsed: unknown = JSON.parse(decodeURIComponent(value));\n return isPrerenderRouteParamsPayload(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nexport function readTrustedPrerenderRouteParamsFromHeaders(\n headers: Headers,\n expectedSecret?: string,\n): PrerenderRouteParamsPayload | null {\n if (process.env.VINEXT_PRERENDER !== \"1\") return null;\n const secret = headers.get(VINEXT_PRERENDER_SECRET_HEADER);\n if (secret === null) return null;\n if (expectedSecret !== undefined && secret !== expectedSecret) return null;\n const header = headers.get(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER);\n if (header === null) return null;\n const params = parsePrerenderRouteParamsHeader(header);\n if (params === null) {\n throw new Error(\"[vinext] Invalid internal prerender route params header.\");\n }\n return params;\n}\n\n// Convenience wrapper for reads that happen AFTER the prerender secret has\n// already been verified at the trust boundary. The only entry point that\n// receives raw external input, `prod-server`'s `nodeToWebRequest`, calls\n// `readTrustedPrerenderRouteParamsFromHeaders` WITH `expectedSecret` and\n// re-serializes the validated payload onto a clean header. Every downstream\n// reader (the App Router handler) therefore operates on an already-trusted\n// request and deliberately omits `expectedSecret`. The `VINEXT_PRERENDER=1`\n// gate still ensures this never activates outside the build-time prerender\n// phase. Do not call this on unverified external input.\nexport function readTrustedPrerenderRouteParams(\n request: Request,\n): PrerenderRouteParamsPayload | null {\n return readTrustedPrerenderRouteParamsFromHeaders(request.headers);\n}\n\nfunction decodePrerenderRouteParam(value: string): string | null {\n try {\n return decodeURIComponent(value);\n } catch {\n return null;\n }\n}\n\nfunction decodedPrerenderRouteParamEquals(\n prerenderValue: string | string[],\n matchedValue: string | string[],\n): boolean {\n if (Array.isArray(prerenderValue) || Array.isArray(matchedValue)) {\n if (!Array.isArray(prerenderValue) || !Array.isArray(matchedValue)) return false;\n if (prerenderValue.length !== matchedValue.length) return false;\n\n return prerenderValue.every((item, index) => {\n const decoded = decodePrerenderRouteParam(item);\n return decoded !== null && decoded === matchedValue[index];\n });\n }\n\n const decoded = decodePrerenderRouteParam(prerenderValue);\n return decoded !== null && decoded === matchedValue;\n}\n\nexport function prerenderRouteParamsPayloadMatchesRoute(\n payload: PrerenderRouteParamsPayload | null,\n routePattern: string,\n params: PrerenderRouteParams,\n): payload is PrerenderRouteParamsPayload {\n if (payload === null) return false;\n if (payload.routePattern !== routePattern) return false;\n\n const prerenderParamKeys = Object.keys(payload.params);\n if (prerenderParamKeys.length !== Object.keys(params).length) return false;\n\n for (const [key, prerenderValue] of Object.entries(payload.params)) {\n const matchedValue = params[key];\n if (matchedValue === undefined) return false;\n if (!decodedPrerenderRouteParamEquals(prerenderValue, matchedValue)) return false;\n }\n\n return true;\n}\n\nexport function encodePrerenderRouteParams(\n pattern: string,\n params: PrerenderRouteParams,\n): PrerenderRouteParamsPayload | null {\n const encoded: PrerenderRouteParams = {};\n\n for (const part of pattern.split(\"/\").filter(Boolean)) {\n let paramName: string | null = null;\n if (part.startsWith(\":\") && (part.endsWith(\"+\") || part.endsWith(\"*\"))) {\n paramName = part.slice(1, -1);\n } else if (part.startsWith(\":\")) {\n paramName = part.slice(1);\n }\n\n if (paramName === null) continue;\n const value = params[paramName];\n if (Array.isArray(value)) {\n encoded[paramName] = value.map((item) => encodeURIComponent(item));\n } else if (typeof value === \"string\") {\n encoded[paramName] = encodeURIComponent(value);\n }\n }\n\n return Object.keys(encoded).length > 0 ? { routePattern: pattern, params: encoded } : null;\n}\n"],"mappings":"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"}
|