vinext 1.0.0-beta.2 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +6 -9
  2. package/dist/build/client-build-config.js +7 -1
  3. package/dist/check.js +4 -0
  4. package/dist/config/config-matchers.js +4 -1
  5. package/dist/config/next-config.d.ts +12 -3
  6. package/dist/config/next-config.js +4 -0
  7. package/dist/entries/app-browser-entry.d.ts +4 -10
  8. package/dist/entries/app-browser-entry.js +20 -4
  9. package/dist/entries/app-rsc-entry.js +3 -0
  10. package/dist/entries/app-rsc-manifest.js +19 -0
  11. package/dist/entries/pages-client-entry.js +0 -1
  12. package/dist/entries/pages-server-entry.js +10 -3
  13. package/dist/index.js +112 -43
  14. package/dist/init-cloudflare.d.ts +3 -1
  15. package/dist/init-cloudflare.js +59 -5
  16. package/dist/plugins/extensionless-dynamic-import.js +23 -18
  17. package/dist/plugins/ignore-dynamic-requests.js +3 -1
  18. package/dist/plugins/import-meta-url.js +17 -6
  19. package/dist/plugins/require-context.js +20 -15
  20. package/dist/plugins/transform-cache.d.ts +19 -0
  21. package/dist/plugins/transform-cache.js +36 -0
  22. package/dist/routing/app-route-graph.d.ts +14 -6
  23. package/dist/routing/app-route-graph.js +106 -16
  24. package/dist/server/api-handler.d.ts +2 -0
  25. package/dist/server/api-handler.js +3 -3
  26. package/dist/server/app-browser-entry.js +103 -31
  27. package/dist/server/app-browser-navigation-controller.d.ts +23 -21
  28. package/dist/server/app-browser-navigation-controller.js +1 -1
  29. package/dist/server/app-page-cache-finalizer.js +1 -1
  30. package/dist/server/app-page-cache.d.ts +1 -1
  31. package/dist/server/app-page-cache.js +16 -2
  32. package/dist/server/app-page-dispatch.d.ts +8 -0
  33. package/dist/server/app-page-dispatch.js +31 -6
  34. package/dist/server/app-page-element-builder.d.ts +2 -0
  35. package/dist/server/app-page-element-builder.js +33 -7
  36. package/dist/server/app-page-head.d.ts +3 -3
  37. package/dist/server/app-page-head.js +58 -13
  38. package/dist/server/app-page-http-access-fallback-metadata.d.ts +2 -2
  39. package/dist/server/app-page-http-access-fallback-metadata.js +9 -1
  40. package/dist/server/app-page-probe.d.ts +4 -0
  41. package/dist/server/app-page-probe.js +6 -2
  42. package/dist/server/app-page-request.d.ts +3 -0
  43. package/dist/server/app-page-request.js +1 -1
  44. package/dist/server/app-page-response.js +1 -1
  45. package/dist/server/app-page-route-wiring.d.ts +7 -0
  46. package/dist/server/app-page-route-wiring.js +211 -49
  47. package/dist/server/app-pages-bridge.d.ts +1 -1
  48. package/dist/server/app-pages-bridge.js +2 -1
  49. package/dist/server/app-route-handler-cache.js +4 -0
  50. package/dist/server/app-route-handler-dispatch.js +9 -3
  51. package/dist/server/app-route-handler-response.js +1 -1
  52. package/dist/server/app-route-module-loader.d.ts +6 -0
  53. package/dist/server/app-route-module-loader.js +3 -0
  54. package/dist/server/app-router-entry.js +6 -3
  55. package/dist/server/app-rsc-errors.js +10 -0
  56. package/dist/server/app-rsc-handler.js +21 -9
  57. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  58. package/dist/server/app-rsc-route-matching.js +6 -0
  59. package/dist/server/app-ssr-entry.js +4 -4
  60. package/dist/server/cache-control.d.ts +2 -2
  61. package/dist/server/cache-control.js +2 -1
  62. package/dist/server/dev-server.d.ts +3 -1
  63. package/dist/server/dev-server.js +145 -312
  64. package/dist/server/headers.d.ts +2 -2
  65. package/dist/server/headers.js +4 -3
  66. package/dist/server/isr-cache.d.ts +10 -33
  67. package/dist/server/isr-cache.js +33 -43
  68. package/dist/server/isr-decision.d.ts +2 -2
  69. package/dist/server/pages-api-route.d.ts +2 -0
  70. package/dist/server/pages-api-route.js +4 -1
  71. package/dist/server/pages-dev-hydration.js +0 -1
  72. package/dist/server/pages-get-initial-props.d.ts +1 -1
  73. package/dist/server/pages-get-initial-props.js +1 -4
  74. package/dist/server/pages-i18n.js +64 -16
  75. package/dist/server/pages-node-compat.d.ts +2 -0
  76. package/dist/server/pages-node-compat.js +7 -3
  77. package/dist/server/pages-page-data.d.ts +61 -9
  78. package/dist/server/pages-page-data.js +334 -74
  79. package/dist/server/pages-page-handler.d.ts +5 -1
  80. package/dist/server/pages-page-handler.js +115 -19
  81. package/dist/server/pages-page-response.d.ts +5 -3
  82. package/dist/server/pages-page-response.js +23 -26
  83. package/dist/server/pages-request-pipeline.d.ts +7 -0
  84. package/dist/server/pages-request-pipeline.js +5 -1
  85. package/dist/server/pages-revalidate.d.ts +1 -1
  86. package/dist/server/pages-revalidate.js +54 -8
  87. package/dist/server/pages-router-entry.d.ts +1 -0
  88. package/dist/server/pages-router-entry.js +10 -4
  89. package/dist/server/prod-server.d.ts +2 -1
  90. package/dist/server/prod-server.js +37 -13
  91. package/dist/server/revalidation-host.d.ts +11 -0
  92. package/dist/server/revalidation-host.js +19 -0
  93. package/dist/server/worker-revalidation-context.d.ts +13 -0
  94. package/dist/server/worker-revalidation-context.js +29 -0
  95. package/dist/shims/cache-handler.d.ts +1 -1
  96. package/dist/shims/cache-handler.js +10 -7
  97. package/dist/shims/cache-runtime.d.ts +6 -0
  98. package/dist/shims/cache-runtime.js +4 -1
  99. package/dist/shims/cache.js +1 -1
  100. package/dist/shims/error.js +1 -1
  101. package/dist/shims/fetch-cache.js +1 -1
  102. package/dist/shims/font-google-base.js +17 -8
  103. package/dist/shims/font-local.js +41 -10
  104. package/dist/shims/headers.d.ts +10 -13
  105. package/dist/shims/headers.js +85 -36
  106. package/dist/shims/internal/cookie-serialize.d.ts +10 -9
  107. package/dist/shims/internal/cookie-serialize.js +9 -8
  108. package/dist/shims/internal/interpolate-as.d.ts +15 -1
  109. package/dist/shims/internal/interpolate-as.js +27 -2
  110. package/dist/shims/link.js +18 -6
  111. package/dist/shims/metadata.d.ts +7 -17
  112. package/dist/shims/metadata.js +74 -24
  113. package/dist/shims/navigation.js +1 -1
  114. package/dist/shims/request-context.d.ts +4 -1
  115. package/dist/shims/router.js +18 -7
  116. package/dist/shims/script.js +1 -1
  117. package/dist/shims/server.d.ts +7 -6
  118. package/dist/shims/server.js +156 -57
  119. package/dist/shims/unified-request-context.d.ts +21 -2
  120. package/dist/shims/unified-request-context.js +82 -1
  121. package/dist/utils/cache-control-metadata.d.ts +2 -1
  122. package/dist/utils/cache-control-metadata.js +5 -1
  123. package/dist/utils/html-limited-bots.js +1 -1
  124. package/dist/utils/protocol-headers.d.ts +7 -1
  125. package/dist/utils/protocol-headers.js +7 -1
  126. package/package.json +1 -1
@@ -1,37 +1,157 @@
1
1
  import { normalizePathnameForRouteMatch } from "../routing/utils.js";
2
- import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
2
+ import { NEXTJS_CACHE_HEADER, NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
3
3
  import { isDangerousScheme } from "../shims/url-safety.js";
4
- import { buildCacheStateHeaders } from "./cache-headers.js";
5
4
  import { isUnknownRecord } from "../utils/record.js";
6
5
  import { applyCdnResponseHeaders } from "./cache-control.js";
7
- import { decideIsr } from "./isr-decision.js";
6
+ import { buildMissIsrCacheControl, decideIsr } from "./isr-decision.js";
8
7
  import { buildPagesCacheValue } from "./isr-cache.js";
8
+ import { encodeCacheTag } from "../utils/encode-cache-tag.js";
9
9
  import { normalizeStaticPathname } from "../routing/route-pattern.js";
10
+ import { buildCacheStateHeaders } from "./cache-headers.js";
11
+ import { isBotUserAgent } from "../utils/html-limited-bots.js";
10
12
  import { buildPagesNextDataScript, etagMatches, generatePagesETag, isPagesStreamingBot, requestsNoCache } from "./pages-page-response.js";
11
13
  import { createPagesGetInitialPropsRouter, hasPagesGetInitialProps, isResponseSent, loadPagesGetInitialProps } from "./pages-get-initial-props.js";
12
14
  import { buildNextDataPropsJsonResponse } from "./pages-data-route.js";
13
15
  import { isSerializableProps } from "./pages-serializable-props.js";
14
- import { isBotUserAgent } from "../utils/html-limited-bots.js";
15
16
  //#region src/server/pages-page-data.ts
17
+ const ALLOWED_PAGES_REDIRECT_STATUS_CODES = /* @__PURE__ */ new Set([
18
+ 301,
19
+ 302,
20
+ 303,
21
+ 307,
22
+ 308
23
+ ]);
24
+ /** Headers that are part of a cached Pages representation, never request state. */
25
+ function isCachedPagesRepresentationHeader(name) {
26
+ const lowerName = name.toLowerCase();
27
+ return lowerName === "location" || lowerName === "content-type";
28
+ }
29
+ function assertPages404DoesNotReturnNotFound(routePattern, result) {
30
+ if (routePattern === "/404" && result?.notFound) throw new Error("The /404 page can not return notFound in \"getStaticProps\", please remove it to continue!");
31
+ }
32
+ /**
33
+ * Next.js preserves an omitted/false Pages `revalidate` result as an indefinite
34
+ * cache lifetime. The one-year sentinel is only an HTTP Cache-Control detail;
35
+ * storing it as a revalidation deadline would incorrectly regenerate static
36
+ * pages after one year.
37
+ *
38
+ * Next.js source:
39
+ * - packages/next/src/server/render.tsx (`metadata.cacheControl`)
40
+ * - packages/next/src/server/route-modules/pages/pages-handler.ts
41
+ */
42
+ function resolvePagesRevalidateSeconds(result, routeUrl = "") {
43
+ const revalidate = result.revalidate;
44
+ if (revalidate === true) return 1;
45
+ if (typeof revalidate === "number") {
46
+ if (!Number.isInteger(revalidate)) throw new Error(`A page's revalidate option must be seconds expressed as a natural number for ${routeUrl}. Mixed numbers, such as '${revalidate}', cannot be used.`);
47
+ if (revalidate <= 0) throw new Error(`A page's revalidate option can not be less than or equal to zero for ${routeUrl}.`);
48
+ return revalidate;
49
+ }
50
+ if (revalidate === false || revalidate === void 0) return false;
51
+ throw new Error(`A page's revalidate option must be seconds expressed as a natural number. Mixed numbers and strings cannot be used. Received '${JSON.stringify(revalidate)}' for ${routeUrl}`);
52
+ }
53
+ function resolvePagesExpireSeconds(result, configuredExpireSeconds) {
54
+ return result.revalidate === false || result.revalidate === void 0 ? void 0 : configuredExpireSeconds;
55
+ }
56
+ /**
57
+ * Merge gSP/gSSP data into the custom App's raw pageProps value.
58
+ *
59
+ * Next.js deliberately uses Object.assign rather than object spread, so
60
+ * enumerable keys from arrays and primitive wrapper objects are retained.
61
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx
62
+ */
63
+ function mergePagesDataProps(appPageProps, dataProps) {
64
+ return Object.assign({}, appPageProps, dataProps);
65
+ }
16
66
  function buildPagesDataNotFoundResponse(deploymentId) {
17
67
  const headers = { "Content-Type": "application/json" };
18
68
  if (deploymentId) headers[NEXTJS_DEPLOYMENT_ID_HEADER] = deploymentId;
19
- return new Response("{}", {
69
+ return new Response("{\"notFound\":true}", {
20
70
  status: 404,
21
71
  headers
22
72
  });
23
73
  }
24
- function buildPagesNotFoundResult(options) {
74
+ function buildPagesNotFoundResult(options, revalidateSeconds, cacheState, expireSeconds) {
25
75
  if (options.isDataReq) return {
26
76
  kind: "response",
27
77
  response: buildPagesDataNotFoundResponse(options.deploymentId)
28
78
  };
29
- return { kind: "notFound" };
79
+ return {
80
+ kind: "notFound",
81
+ revalidateSeconds,
82
+ expireSeconds,
83
+ cacheState
84
+ };
85
+ }
86
+ function applyPagesTerminalMissHeaders(response, revalidateSeconds, isrCachePathname, expireSeconds) {
87
+ const stem = isrCachePathname.endsWith("/") ? isrCachePathname.slice(0, -1) : isrCachePathname;
88
+ applyCdnResponseHeaders(response.headers, {
89
+ cacheControl: buildMissIsrCacheControl(revalidateSeconds, expireSeconds),
90
+ tags: [encodeCacheTag(`_N_T_${stem || "/"}`)]
91
+ });
92
+ for (const [name, value] of Object.entries(buildCacheStateHeaders("MISS"))) response.headers.set(name, value);
93
+ return response;
94
+ }
95
+ function applyCachedPagesRepresentationHeaders(response, cacheState, entry, options) {
96
+ const { cacheControl } = decideIsr({
97
+ cacheState,
98
+ kind: "pages",
99
+ revalidateSeconds: entry.cacheControl?.revalidate ?? 60,
100
+ expireSeconds: entry.cacheControl?.expire === void 0 ? void 0 : options.expireSeconds,
101
+ cacheControlMeta: entry.cacheControl
102
+ });
103
+ applyCdnResponseHeaders(response.headers, { cacheControl });
104
+ for (const [name, value] of Object.entries(buildCacheStateHeaders(cacheState))) response.headers.set(name, value);
105
+ return response;
106
+ }
107
+ function buildCachedPagesNotFoundResult(options, entry, cacheState) {
108
+ const result = buildPagesNotFoundResult(options, entry.cacheControl?.revalidate ?? 60, cacheState, entry.cacheControl?.expire);
109
+ if (result.kind === "response") return {
110
+ kind: "response",
111
+ response: applyCachedPagesRepresentationHeaders(result.response, cacheState, entry, options)
112
+ };
113
+ return result;
30
114
  }
31
115
  function resolvePagesRedirectStatus(redirect) {
32
116
  return redirect.statusCode != null ? redirect.statusCode : redirect.permanent ? 308 : 307;
33
117
  }
118
+ /** Validate and normalize the redirect metadata returned by gSP/gSSP. */
119
+ function resolvePagesRedirect(redirect, options) {
120
+ const errors = [];
121
+ const hasPermanent = redirect.permanent !== void 0;
122
+ const hasStatusCode = redirect.statusCode !== void 0;
123
+ if (hasPermanent && hasStatusCode) errors.push("`permanent` and `statusCode` can not both be provided");
124
+ else if (hasPermanent && typeof redirect.permanent !== "boolean") errors.push("`permanent` must be `true` or `false`");
125
+ else if (hasStatusCode && !ALLOWED_PAGES_REDIRECT_STATUS_CODES.has(redirect.statusCode)) errors.push(`\`statusCode\` must undefined or one of ${[...ALLOWED_PAGES_REDIRECT_STATUS_CODES].join(", ")}`);
126
+ if (typeof redirect.destination !== "string") errors.push(`\`destination\` should be string but received ${typeof redirect.destination}`);
127
+ if (redirect.basePath !== void 0 && typeof redirect.basePath !== "boolean") errors.push(`\`basePath\` should be undefined or a false, received ${typeof redirect.basePath}`);
128
+ if (errors.length > 0) throw new Error(`Invalid redirect object returned from ${options.method} for ${options.routeUrl}\n${errors.join(" and ")}\nSee more info here: https://nextjs.org/docs/messages/invalid-redirect-gssp`);
129
+ return {
130
+ destination: options.sanitizeDestination(redirect.destination),
131
+ statusCode: resolvePagesRedirectStatus(redirect),
132
+ ...redirect.basePath === void 0 ? {} : { basePath: redirect.basePath }
133
+ };
134
+ }
135
+ function resolvePagesRedirectLocation(redirect, configuredBasePath = "") {
136
+ let destination = redirect.destination;
137
+ if (configuredBasePath && redirect.basePath !== false && redirect.destination.startsWith("/")) destination = `${configuredBasePath}${redirect.destination}`;
138
+ if (!destination.startsWith("/")) return destination;
139
+ const urlParts = destination.split("?");
140
+ return urlParts[0].replace(/\\/g, "/").replace(/\/\/+/g, "/") + (urlParts[1] ? `?${urlParts.slice(1).join("?")}` : "");
141
+ }
142
+ function buildPagesRedirectProps(redirect, props) {
143
+ return {
144
+ ...props,
145
+ pageProps: {
146
+ ...isUnknownRecord(props.pageProps) ? props.pageProps : {},
147
+ __N_REDIRECT: redirect.destination,
148
+ __N_REDIRECT_STATUS: redirect.statusCode,
149
+ ...redirect.basePath === void 0 ? {} : { __N_REDIRECT_BASE_PATH: redirect.basePath }
150
+ }
151
+ };
152
+ }
34
153
  function normalizePagesRenderProps(props) {
154
+ if (!("pageProps" in props)) return { pageProps: props };
35
155
  return {
36
156
  ...props,
37
157
  pageProps: props.pageProps
@@ -77,8 +197,8 @@ async function loadPagesAppInitialRenderProps(options, getSharedReqRes) {
77
197
  response: responsePromise
78
198
  };
79
199
  if (initialProps) {
80
- renderProps = normalizePagesRenderProps(initialProps);
81
- pageProps = isUnknownRecord(renderProps.pageProps) ? renderProps.pageProps : {};
200
+ renderProps = initialProps;
201
+ pageProps = isUnknownRecord(initialProps.pageProps) ? initialProps.pageProps : {};
82
202
  }
83
203
  return {
84
204
  kind: "props",
@@ -107,9 +227,14 @@ async function loadPagesAppInitialRenderProps(options, getSharedReqRes) {
107
227
  * redirects (search `__N_REDIRECT`), consumed in
108
228
  * `packages/next/src/shared/lib/router/router.ts` (`pageProps.__N_REDIRECT`).
109
229
  */
110
- function buildPagesRedirectResponse(redirect, options, props = { pageProps: {} }) {
111
- const destination = options.sanitizeDestination(redirect.destination);
112
- if (isDangerousScheme(destination)) {
230
+ function buildPagesRedirectResponse(redirect, options, props = { pageProps: {} }, method = "getStaticProps") {
231
+ const resolved = resolvePagesRedirect(redirect, {
232
+ method,
233
+ routeUrl: options.routeUrl,
234
+ sanitizeDestination: options.sanitizeDestination
235
+ });
236
+ const redirectProps = buildPagesRedirectProps(resolved, props);
237
+ if (isDangerousScheme(resolved.destination)) {
113
238
  const headers = new Headers({
114
239
  "Cache-Control": "private, no-cache, no-store, max-age=0, must-revalidate",
115
240
  "Content-Type": "text/plain; charset=utf-8"
@@ -123,20 +248,46 @@ function buildPagesRedirectResponse(redirect, options, props = { pageProps: {} }
123
248
  if (options.isDataReq) {
124
249
  const init = { headers: {} };
125
250
  if (options.deploymentId) init.headers[NEXTJS_DEPLOYMENT_ID_HEADER] = options.deploymentId;
126
- return buildNextDataPropsJsonResponse({
127
- ...props,
128
- pageProps: {
129
- ...isUnknownRecord(props.pageProps) ? props.pageProps : {},
130
- __N_REDIRECT: destination,
131
- __N_REDIRECT_STATUS: resolvePagesRedirectStatus(redirect)
132
- }
133
- }, options.safeJsonStringify, init);
251
+ return buildNextDataPropsJsonResponse(redirectProps, options.safeJsonStringify, init);
134
252
  }
135
- return new Response(null, {
136
- status: resolvePagesRedirectStatus(redirect),
137
- headers: { Location: destination }
253
+ const location = resolvePagesRedirectLocation(resolved, options.basePath);
254
+ return new Response(location, {
255
+ status: resolved.statusCode,
256
+ headers: {
257
+ Location: location,
258
+ ...resolved.statusCode === 308 ? { Refresh: `0;url=${location}` } : {}
259
+ }
138
260
  });
139
261
  }
262
+ function buildCachedPagesRedirectResponse(cached, options) {
263
+ const props = normalizePagesRenderProps(cached.props);
264
+ const pageProps = isUnknownRecord(props.pageProps) ? props.pageProps : {};
265
+ const destination = pageProps.__N_REDIRECT;
266
+ const statusCode = pageProps.__N_REDIRECT_STATUS;
267
+ const redirectBasePath = pageProps.__N_REDIRECT_BASE_PATH;
268
+ if (typeof destination !== "string") throw new Error("Invalid cached Pages redirect: missing __N_REDIRECT");
269
+ return buildPagesRedirectResponse({
270
+ destination,
271
+ ...typeof statusCode === "number" ? { statusCode } : {},
272
+ ...redirectBasePath === void 0 ? {} : { basePath: redirectBasePath }
273
+ }, options, props);
274
+ }
275
+ function getCachedPagesRedirect(cached) {
276
+ if (cached?.kind === "REDIRECT") return cached;
277
+ if (cached?.kind !== "PAGES") return null;
278
+ const locationHeader = cached.headers ? Object.entries(cached.headers).find(([name]) => name.toLowerCase() === "location")?.[1] : void 0;
279
+ const destination = Array.isArray(locationHeader) ? locationHeader[0] : locationHeader;
280
+ if (typeof destination !== "string" || typeof cached.status !== "number" || !ALLOWED_PAGES_REDIRECT_STATUS_CODES.has(cached.status)) return null;
281
+ const props = normalizePagesRenderProps(cached.pageData);
282
+ return {
283
+ kind: "REDIRECT",
284
+ props: buildPagesRedirectProps({
285
+ destination,
286
+ statusCode: cached.status,
287
+ basePath: false
288
+ }, props)
289
+ };
290
+ }
140
291
  function getPagesRouteParams(routePattern) {
141
292
  return routePattern.split("/").map((segment) => {
142
293
  const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
@@ -179,12 +330,12 @@ function matchesPagesStaticPath(pathEntry, params, routeParams, routeUrl) {
179
330
  return String(value) === String(actual);
180
331
  });
181
332
  }
182
- function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSeconds, expireSeconds, cacheControl, status) {
333
+ function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSeconds, expireSeconds, cacheControl, status, cachedHeaders) {
183
334
  const { cacheControl: cacheControlHeader } = decideIsr({
184
335
  cacheState,
185
336
  kind: "pages",
186
337
  revalidateSeconds: revalidateSeconds ?? 60,
187
- expireSeconds,
338
+ expireSeconds: cacheControl?.expire === void 0 ? void 0 : expireSeconds,
188
339
  cacheControlMeta: cacheControl
189
340
  });
190
341
  const headers = new Headers({
@@ -193,6 +344,13 @@ function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSec
193
344
  });
194
345
  applyCdnResponseHeaders(headers, { cacheControl: cacheControlHeader });
195
346
  if (fontLinkHeader) headers.set("Link", fontLinkHeader);
347
+ if (cachedHeaders) for (const [name, value] of Object.entries(cachedHeaders)) {
348
+ if (!isCachedPagesRepresentationHeader(name.toLowerCase())) continue;
349
+ if (Array.isArray(value)) {
350
+ headers.delete(name);
351
+ for (const item of value) headers.append(name, item);
352
+ } else headers.set(name, value);
353
+ }
196
354
  return new Response(html, {
197
355
  status: status ?? 200,
198
356
  headers
@@ -257,6 +415,7 @@ async function resolvePagesPageData(options) {
257
415
  const userFacingParams = options.route.isDynamic ? options.params : null;
258
416
  let isFallback = false;
259
417
  let shouldPersistFallbackData = false;
418
+ let onDemandPreviousCacheEntry;
260
419
  const previewData = options.isOnDemandRevalidate ? false : options.previewData ?? false;
261
420
  if (typeof options.pageModule.getStaticPaths === "function" && options.route.isDynamic) {
262
421
  const pathsResult = await options.pageModule.getStaticPaths({
@@ -267,11 +426,29 @@ async function resolvePagesPageData(options) {
267
426
  const paths = pathsResult?.paths ?? [];
268
427
  const routeParams = getPagesRouteParams(options.routePattern);
269
428
  const isValidPath = paths.some((pathEntry) => matchesPagesStaticPath(pathEntry, options.params, routeParams, options.routeUrl));
270
- if (fallback === false && !isValidPath && previewData === false) return buildPagesNotFoundResult(options);
429
+ if (fallback === false && !isValidPath && previewData === false) {
430
+ if (options.isOnDemandRevalidate && !options.revalidateOnlyGenerated) return {
431
+ kind: "response",
432
+ response: new Response("This page could not be found", { status: 404 }),
433
+ onDemandRevalidateSuccess: false
434
+ };
435
+ return buildPagesNotFoundResult(options);
436
+ }
271
437
  const isBotRequest = !!options.userAgent && isBotUserAgent(options.userAgent, options.htmlLimitedBots);
272
438
  if (fallback === true && !isValidPath && !options.isDataReq && !isBotRequest && previewData === false) isFallback = true;
273
439
  shouldPersistFallbackData = fallback === true && !isValidPath && options.isDataReq === true;
274
440
  }
441
+ if (typeof options.pageModule.getStaticProps === "function" && options.isOnDemandRevalidate && options.revalidateOnlyGenerated) {
442
+ const pathname = options.isrCachePathname ?? options.routeUrl.split("?")[0];
443
+ onDemandPreviousCacheEntry = await options.isrGet(options.isrCacheKey("pages", pathname));
444
+ if (!onDemandPreviousCacheEntry) return {
445
+ kind: "response",
446
+ response: new Response("This page could not be found", {
447
+ status: 404,
448
+ headers: { [NEXTJS_CACHE_HEADER]: "REVALIDATED" }
449
+ })
450
+ };
451
+ }
275
452
  let pageProps = {};
276
453
  let gsspRes = null;
277
454
  const previewContext = previewData === false ? {} : {
@@ -297,7 +474,7 @@ async function resolvePagesPageData(options) {
297
474
  return null;
298
475
  }
299
476
  if (isFallback) {
300
- const pathname = options.routeUrl.split("?")[0];
477
+ const pathname = options.isrCachePathname ?? options.routeUrl.split("?")[0];
301
478
  if ((await options.isrGet(options.isrCacheKey("pages", pathname)))?.value.value?.kind !== "PAGES") {
302
479
  const appShortCircuit = await loadForegroundAppInitialRenderProps();
303
480
  if (appShortCircuit) return appShortCircuit;
@@ -341,10 +518,7 @@ async function resolvePagesPageData(options) {
341
518
  response: await responsePromise
342
519
  };
343
520
  if (result?.props) {
344
- pageProps = {
345
- ...pageProps,
346
- ...await Promise.resolve(result.props)
347
- };
521
+ pageProps = mergePagesDataProps(renderProps.pageProps, await Promise.resolve(result.props));
348
522
  renderProps = {
349
523
  ...renderProps,
350
524
  pageProps
@@ -352,28 +526,22 @@ async function resolvePagesPageData(options) {
352
526
  }
353
527
  if (result?.redirect) return {
354
528
  kind: "response",
355
- response: buildPagesRedirectResponse(result.redirect, options, renderProps)
529
+ response: buildPagesRedirectResponse(result.redirect, options, renderProps, "getServerSideProps")
356
530
  };
357
531
  if (result?.notFound) return buildPagesNotFoundResult(options);
358
532
  if (result?.props !== void 0 && options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getServerSideProps", pageProps);
359
533
  gsspRes = res;
360
534
  }
361
535
  let isrRevalidateSeconds = null;
536
+ let isrExpireSeconds;
362
537
  if (typeof options.pageModule.getStaticProps === "function") {
363
- const pathname = options.routeUrl.split("?")[0];
538
+ const pathname = options.isrCachePathname ?? options.routeUrl.split("?")[0];
364
539
  const cacheKey = options.isrCacheKey("pages", pathname);
365
- const cached = await options.isrGet(cacheKey);
540
+ const cached = onDemandPreviousCacheEntry !== void 0 ? onDemandPreviousCacheEntry : await options.isrGet(cacheKey);
366
541
  const cachedValue = cached?.value.value;
367
- if (!options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
368
- const hitResponse = buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status);
369
- const hitBotResult = applyBotETagAndCheck(hitResponse, cachedValue.html, options);
370
- if (hitBotResult) return hitBotResult;
371
- return {
372
- kind: "response",
373
- response: hitResponse
374
- };
375
- }
376
- if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
542
+ const isLegacyCachedNotFound = cachedValue?.kind === "PAGES" && cachedValue.status === 404 && options.routePattern !== "/404" && options.routePattern !== "/_error";
543
+ const cachedRedirect = getCachedPagesRedirect(cachedValue);
544
+ const scheduleStaleRegeneration = () => {
377
545
  options.triggerBackgroundRegeneration(cacheKey, async function() {
378
546
  return options.runInFreshUnifiedContext(async () => {
379
547
  options.applyRequestContexts();
@@ -388,18 +556,35 @@ async function resolvePagesPageData(options) {
388
556
  defaultLocale: options.i18n.defaultLocale,
389
557
  revalidateReason: "stale"
390
558
  });
391
- if (freshResult?.props) {
392
- freshPageProps = {
393
- ...freshPageProps,
394
- ...freshResult.props
395
- };
396
- freshRenderProps = {
397
- ...freshRenderProps,
398
- pageProps: freshPageProps
399
- };
559
+ if (!freshResult) return;
560
+ assertPages404DoesNotReturnNotFound(options.routePattern, freshResult);
561
+ const revalidateSeconds = resolvePagesRevalidateSeconds(freshResult, options.routeUrl);
562
+ const expireSeconds = resolvePagesExpireSeconds(freshResult, options.expireSeconds);
563
+ if (freshResult.redirect) {
564
+ const redirect = resolvePagesRedirect(freshResult.redirect, {
565
+ method: "getStaticProps",
566
+ routeUrl: options.routeUrl,
567
+ sanitizeDestination: options.sanitizeDestination
568
+ });
569
+ await options.isrSet(cacheKey, {
570
+ kind: "REDIRECT",
571
+ props: buildPagesRedirectProps(redirect, freshRenderProps)
572
+ }, revalidateSeconds, void 0, expireSeconds);
573
+ return;
574
+ }
575
+ if (freshResult.notFound) {
576
+ await options.isrSet(cacheKey, null, revalidateSeconds, void 0, expireSeconds);
577
+ return;
400
578
  }
401
- const freshRevalidateSeconds = typeof freshResult?.revalidate === "number" && freshResult.revalidate > 0 ? freshResult.revalidate : cached.value.cacheControl?.revalidate;
402
- if (freshResult?.props && freshRevalidateSeconds && freshRevalidateSeconds > 0) {
579
+ if (freshResult.props === void 0) return;
580
+ const resolvedFreshProps = await Promise.resolve(freshResult.props);
581
+ freshPageProps = mergePagesDataProps(freshRenderProps.pageProps, resolvedFreshProps);
582
+ freshRenderProps = {
583
+ ...freshRenderProps,
584
+ pageProps: freshPageProps
585
+ };
586
+ if (options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getStaticProps", freshPageProps);
587
+ if (cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest) {
403
588
  const freshHtml = await renderPagesIsrHtml({
404
589
  buildId: options.buildId,
405
590
  cachedHtml: cachedValue.html,
@@ -414,15 +599,61 @@ async function resolvePagesPageData(options) {
414
599
  nextData: options.nextData,
415
600
  vinext: options.vinext
416
601
  });
417
- await options.isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshRenderProps, options.statusCode), freshRevalidateSeconds, void 0, options.expireSeconds);
602
+ await options.isrSet(cacheKey, buildPagesCacheValue(freshHtml, freshRenderProps, options.statusCode), revalidateSeconds, void 0, expireSeconds);
603
+ return;
418
604
  }
605
+ await options.isrSet(cacheKey, {
606
+ kind: "PAGES",
607
+ html: "",
608
+ pageData: freshRenderProps,
609
+ generatedFromDataRequest: true,
610
+ headers: void 0,
611
+ status: void 0
612
+ }, revalidateSeconds, void 0, expireSeconds);
419
613
  });
420
614
  }, {
421
615
  routerKind: "Pages Router",
422
616
  routePath: options.routePattern,
423
617
  routeType: "render"
424
618
  });
425
- const staleResponse = buildPagesCacheResponse(cachedValue.html, "STALE", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status);
619
+ };
620
+ if (!options.isOnDemandRevalidate && cached && !cached.isStale && !cached.isExpired && previewData === false) {
621
+ if (cachedValue === null) return buildCachedPagesNotFoundResult(options, cached.value, "HIT");
622
+ if (isLegacyCachedNotFound) return buildCachedPagesNotFoundResult(options, cached.value, "HIT");
623
+ if (cachedRedirect) return {
624
+ kind: "response",
625
+ response: applyCachedPagesRepresentationHeaders(buildCachedPagesRedirectResponse(cachedRedirect, options), "HIT", cached.value, options)
626
+ };
627
+ if (options.isDataReq && cachedValue?.kind === "PAGES") return {
628
+ kind: "response",
629
+ response: applyCachedPagesRepresentationHeaders(buildNextDataPropsJsonResponse(normalizePagesRenderProps(cachedValue.pageData), options.safeJsonStringify, options.deploymentId ? { headers: { [NEXTJS_DEPLOYMENT_ID_HEADER]: options.deploymentId } } : void 0), "HIT", cached.value, options)
630
+ };
631
+ }
632
+ if (!options.isOnDemandRevalidate && cached?.isStale === false && !cached.isExpired && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
633
+ const hitResponse = buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status, cachedValue.headers);
634
+ const hitBotResult = applyBotETagAndCheck(hitResponse, cachedValue.html, options);
635
+ if (hitBotResult) return hitBotResult;
636
+ return {
637
+ kind: "response",
638
+ response: hitResponse
639
+ };
640
+ }
641
+ if (!options.isOnDemandRevalidate && cached && cached.isStale && !cached.isExpired && !options.scriptNonce && previewData === false && (cachedValue === null || cachedRedirect !== null || isLegacyCachedNotFound || options.isDataReq)) {
642
+ scheduleStaleRegeneration();
643
+ if (cachedValue === null) return buildCachedPagesNotFoundResult(options, cached.value, "STALE");
644
+ if (isLegacyCachedNotFound) return buildCachedPagesNotFoundResult(options, cached.value, "STALE");
645
+ if (cachedRedirect) return {
646
+ kind: "response",
647
+ response: applyCachedPagesRepresentationHeaders(buildCachedPagesRedirectResponse(cachedRedirect, options), "STALE", cached.value, options)
648
+ };
649
+ if (cachedValue?.kind === "PAGES") return {
650
+ kind: "response",
651
+ response: applyCachedPagesRepresentationHeaders(buildNextDataPropsJsonResponse(normalizePagesRenderProps(cachedValue.pageData), options.safeJsonStringify, options.deploymentId ? { headers: { [NEXTJS_DEPLOYMENT_ID_HEADER]: options.deploymentId } } : void 0), "STALE", cached.value, options)
652
+ };
653
+ }
654
+ if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && cached.isStale && !cached.isExpired && !options.scriptNonce && !options.isDataReq && previewData === false) {
655
+ scheduleStaleRegeneration();
656
+ const staleResponse = buildPagesCacheResponse(cachedValue.html, "STALE", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status, cachedValue.headers);
426
657
  const staleBotResult = applyBotETagAndCheck(staleResponse, cachedValue.html, options);
427
658
  if (staleBotResult) return staleBotResult;
428
659
  return {
@@ -430,7 +661,7 @@ async function resolvePagesPageData(options) {
430
661
  response: staleResponse
431
662
  };
432
663
  }
433
- const generatedPageData = !options.isOnDemandRevalidate && previewData === false && cached?.isStale === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest && isUnknownRecord(cachedValue.pageData) ? cachedValue.pageData : null;
664
+ const generatedPageData = !options.isOnDemandRevalidate && previewData === false && cached?.isStale === false && !cached.isExpired && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest && isUnknownRecord(cachedValue.pageData) ? cachedValue.pageData : null;
434
665
  if (!generatedPageData) {
435
666
  const shortCircuit = await loadForegroundAppInitialRenderProps();
436
667
  if (shortCircuit) return shortCircuit;
@@ -443,30 +674,58 @@ async function resolvePagesPageData(options) {
443
674
  ...previewContext,
444
675
  revalidateReason: options.isOnDemandRevalidate ? "on-demand" : options.isBuildTimePrerendering ? "build" : "stale"
445
676
  });
677
+ assertPages404DoesNotReturnNotFound(options.routePattern, result);
446
678
  if (generatedPageData) {
447
- renderProps = generatedPageData;
679
+ renderProps = normalizePagesRenderProps(generatedPageData);
448
680
  pageProps = isUnknownRecord(renderProps.pageProps) ? renderProps.pageProps : {};
449
681
  }
450
- if (result?.props) {
451
- pageProps = {
452
- ...pageProps,
453
- ...result.props
454
- };
682
+ if (result?.props !== void 0) {
683
+ pageProps = mergePagesDataProps(renderProps.pageProps, await Promise.resolve(result.props));
455
684
  renderProps = {
456
685
  ...renderProps,
457
686
  pageProps
458
687
  };
459
688
  }
460
- if (result?.redirect) return {
461
- kind: "response",
462
- response: buildPagesRedirectResponse(result.redirect, options, renderProps)
463
- };
464
- if (result?.notFound) return buildPagesNotFoundResult(options);
689
+ if (result?.redirect) {
690
+ const response = buildPagesRedirectResponse(result.redirect, options, renderProps);
691
+ if (previewData === false) {
692
+ const revalidateSeconds = resolvePagesRevalidateSeconds(result, options.routeUrl);
693
+ const expireSeconds = resolvePagesExpireSeconds(result, options.expireSeconds);
694
+ const redirect = resolvePagesRedirect(result.redirect, {
695
+ method: "getStaticProps",
696
+ routeUrl: options.routeUrl,
697
+ sanitizeDestination: options.sanitizeDestination
698
+ });
699
+ await options.isrSet(cacheKey, {
700
+ kind: "REDIRECT",
701
+ props: buildPagesRedirectProps(redirect, renderProps)
702
+ }, revalidateSeconds, void 0, expireSeconds);
703
+ applyPagesTerminalMissHeaders(response, revalidateSeconds, pathname, expireSeconds);
704
+ }
705
+ return {
706
+ kind: "response",
707
+ response
708
+ };
709
+ }
710
+ if (result?.notFound) {
711
+ const revalidateSeconds = resolvePagesRevalidateSeconds(result, options.routeUrl);
712
+ const expireSeconds = resolvePagesExpireSeconds(result, options.expireSeconds);
713
+ if (previewData === false) await options.isrSet(cacheKey, null, revalidateSeconds, void 0, expireSeconds);
714
+ const notFoundResult = buildPagesNotFoundResult(options, revalidateSeconds, previewData === false ? "MISS" : void 0, expireSeconds);
715
+ if (notFoundResult.kind === "response" && previewData === false) applyPagesTerminalMissHeaders(notFoundResult.response, revalidateSeconds, pathname, expireSeconds);
716
+ return notFoundResult;
717
+ }
465
718
  if (result?.props !== void 0 && options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getStaticProps", pageProps);
466
- if (previewData === false && typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
467
- else if (previewData === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest) isrRevalidateSeconds = cached?.value.cacheControl?.revalidate ?? 31536e3;
719
+ if (previewData === false && result) {
720
+ isrRevalidateSeconds = resolvePagesRevalidateSeconds(result, options.routeUrl);
721
+ isrExpireSeconds = resolvePagesExpireSeconds(result, options.expireSeconds);
722
+ } else if (previewData === false && options.isOnDemandRevalidate) isrRevalidateSeconds = false;
723
+ else if (previewData === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest) {
724
+ isrRevalidateSeconds = cached?.value.cacheControl?.revalidate ?? false;
725
+ isrExpireSeconds = cached?.value.cacheControl?.expire;
726
+ }
468
727
  if (shouldPersistFallbackData && previewData === false) {
469
- const revalidateSeconds = isrRevalidateSeconds ?? 31536e3;
728
+ const revalidateSeconds = isrRevalidateSeconds ?? false;
470
729
  await options.isrSet(cacheKey, {
471
730
  kind: "PAGES",
472
731
  html: "",
@@ -474,7 +733,7 @@ async function resolvePagesPageData(options) {
474
733
  generatedFromDataRequest: true,
475
734
  headers: void 0,
476
735
  status: void 0
477
- }, revalidateSeconds, void 0, options.expireSeconds);
736
+ }, revalidateSeconds, void 0, isrExpireSeconds);
478
737
  }
479
738
  }
480
739
  if (typeof options.pageModule.getServerSideProps !== "function" && typeof options.pageModule.getStaticProps !== "function" && hasPagesGetInitialProps(options.AppComponent)) {
@@ -514,10 +773,11 @@ async function resolvePagesPageData(options) {
514
773
  documentReqRes: sharedReqRes,
515
774
  gsspRes,
516
775
  isrRevalidateSeconds,
776
+ isrExpireSeconds,
517
777
  pageProps,
518
778
  props: renderProps,
519
779
  isFallback: false
520
780
  };
521
781
  }
522
782
  //#endregion
523
- export { getPagesRouteParams, matchesPagesStaticPath, renderPagesIsrHtml, resolvePagesPageData };
783
+ export { assertPages404DoesNotReturnNotFound, buildPagesRedirectProps, getPagesRouteParams, matchesPagesStaticPath, mergePagesDataProps, renderPagesIsrHtml, resolvePagesPageData, resolvePagesRedirect, resolvePagesRedirectLocation, resolvePagesRevalidateSeconds };
@@ -89,7 +89,11 @@ type RenderPageOptions = {
89
89
  originalUrl?: string;
90
90
  renderErrorPageOnMiss?: boolean;
91
91
  __isInternalErrorRender?: boolean;
92
- __forcedRoute?: PageRoute;
92
+ __forcedRoute?: PageRoute; /** Source-page cache lifetime forwarded while rendering a notFound error page. */
93
+ __notFoundRevalidateSeconds?: number | false; /** Source-page expire ceiling forwarded for the outgoing notFound response. */
94
+ __notFoundExpireSeconds?: number; /** Source-page identity used for the outgoing notFound cache tag. */
95
+ __notFoundCachePathname?: string; /** Internal recursion guard while a top-level on-demand request owns the batch. */
96
+ __skipOnDemandCoalesce?: boolean;
93
97
  err?: unknown;
94
98
  };
95
99
  /**