vinext 0.2.0 → 1.0.0-beta.0

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 (168) hide show
  1. package/README.md +50 -29
  2. package/dist/build/client-build-config.d.ts +13 -92
  3. package/dist/build/client-build-config.js +17 -95
  4. package/dist/build/css-url-assets.d.ts +3 -1
  5. package/dist/build/css-url-assets.js +18 -1
  6. package/dist/build/inject-pregenerated-paths.d.ts +3 -0
  7. package/dist/build/inject-pregenerated-paths.js +4 -1
  8. package/dist/build/prerender-paths.d.ts +23 -0
  9. package/dist/build/prerender-paths.js +296 -0
  10. package/dist/build/prerender-server-entry.d.ts +1 -0
  11. package/dist/build/prerender-server-entry.js +49 -0
  12. package/dist/build/prerender-server-pool.d.ts +44 -0
  13. package/dist/build/prerender-server-pool.js +194 -0
  14. package/dist/build/prerender.d.ts +12 -1
  15. package/dist/build/prerender.js +87 -23
  16. package/dist/build/run-prerender.js +2 -1
  17. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  18. package/dist/cache/cache-adapters-virtual.js +26 -2
  19. package/dist/check.d.ts +4 -6
  20. package/dist/check.js +13 -9
  21. package/dist/cli.js +34 -54
  22. package/dist/client/vinext-next-data.d.ts +4 -1
  23. package/dist/config/config-matchers.js +33 -11
  24. package/dist/config/next-config.d.ts +36 -6
  25. package/dist/config/next-config.js +17 -4
  26. package/dist/config/prerender.d.ts +11 -1
  27. package/dist/config/prerender.js +19 -1
  28. package/dist/config/tsconfig-paths.js +5 -1
  29. package/dist/entries/app-browser-entry.js +5 -1
  30. package/dist/entries/app-rsc-entry.d.ts +3 -2
  31. package/dist/entries/app-rsc-entry.js +4 -0
  32. package/dist/entries/pages-client-entry.js +14 -3
  33. package/dist/entries/pages-server-entry.js +18 -4
  34. package/dist/image/image-adapters-virtual.js +1 -0
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +549 -152
  37. package/dist/init-cloudflare.js +12 -3
  38. package/dist/init-platform.d.ts +10 -1
  39. package/dist/init-platform.js +78 -12
  40. package/dist/init.d.ts +11 -3
  41. package/dist/init.js +129 -31
  42. package/dist/plugins/fonts.js +1 -1
  43. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  44. package/dist/plugins/og-assets.js +2 -1
  45. package/dist/plugins/optimize-imports.js +1 -1
  46. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  47. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  48. package/dist/plugins/sass.d.ts +17 -1
  49. package/dist/plugins/sass.js +74 -1
  50. package/dist/plugins/styled-jsx.d.ts +16 -0
  51. package/dist/plugins/styled-jsx.js +149 -0
  52. package/dist/routing/app-route-graph.js +22 -16
  53. package/dist/routing/file-matcher.d.ts +8 -1
  54. package/dist/routing/file-matcher.js +15 -3
  55. package/dist/server/app-browser-entry.js +44 -14
  56. package/dist/server/app-browser-navigation-controller.js +5 -2
  57. package/dist/server/app-browser-server-action-client.js +5 -3
  58. package/dist/server/app-browser-state.d.ts +1 -0
  59. package/dist/server/app-browser-state.js +1 -1
  60. package/dist/server/app-layout-param-observation.d.ts +1 -1
  61. package/dist/server/app-page-boundary.js +2 -1
  62. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  63. package/dist/server/app-page-cache-finalizer.js +3 -3
  64. package/dist/server/app-page-cache-render.d.ts +1 -1
  65. package/dist/server/app-page-cache.js +23 -6
  66. package/dist/server/app-page-dispatch.d.ts +2 -1
  67. package/dist/server/app-page-dispatch.js +27 -10
  68. package/dist/server/app-page-element-builder.js +1 -0
  69. package/dist/server/app-page-execution.js +2 -1
  70. package/dist/server/app-page-render-identity.d.ts +1 -0
  71. package/dist/server/app-page-render-identity.js +2 -1
  72. package/dist/server/app-page-render.d.ts +3 -1
  73. package/dist/server/app-page-render.js +67 -16
  74. package/dist/server/app-page-response.d.ts +7 -0
  75. package/dist/server/app-page-response.js +16 -4
  76. package/dist/server/app-page-route-wiring.js +3 -3
  77. package/dist/server/app-page-stream.d.ts +2 -1
  78. package/dist/server/app-page-stream.js +1 -1
  79. package/dist/server/app-route-handler-execution.d.ts +1 -1
  80. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  81. package/dist/server/app-route-tree-prefetch.js +187 -0
  82. package/dist/server/app-router-entry.js +1 -1
  83. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  84. package/dist/server/app-rsc-cache-busting.js +9 -5
  85. package/dist/server/app-rsc-handler.d.ts +6 -0
  86. package/dist/server/app-rsc-handler.js +88 -10
  87. package/dist/server/app-rsc-render-mode.d.ts +3 -5
  88. package/dist/server/app-rsc-render-mode.js +5 -12
  89. package/dist/server/app-rsc-request-normalization.d.ts +3 -4
  90. package/dist/server/app-rsc-request-normalization.js +4 -5
  91. package/dist/server/app-server-action-execution.js +6 -5
  92. package/dist/server/app-ssr-entry.js +1 -1
  93. package/dist/server/cache-control.d.ts +3 -1
  94. package/dist/server/cache-control.js +13 -1
  95. package/dist/server/dev-module-runner.js +1 -1
  96. package/dist/server/dev-origin-check.d.ts +2 -2
  97. package/dist/server/dev-origin-check.js +2 -2
  98. package/dist/server/dev-server.d.ts +11 -1
  99. package/dist/server/dev-server.js +82 -24
  100. package/dist/server/headers.d.ts +6 -2
  101. package/dist/server/headers.js +11 -5
  102. package/dist/server/image-optimization.d.ts +12 -1
  103. package/dist/server/image-optimization.js +13 -1
  104. package/dist/server/isr-cache.d.ts +13 -4
  105. package/dist/server/isr-cache.js +8 -4
  106. package/dist/server/pages-data-route.d.ts +4 -2
  107. package/dist/server/pages-data-route.js +18 -4
  108. package/dist/server/pages-dev-module-url.d.ts +2 -1
  109. package/dist/server/pages-dev-module-url.js +6 -3
  110. package/dist/server/pages-node-compat.d.ts +12 -1
  111. package/dist/server/pages-node-compat.js +50 -1
  112. package/dist/server/pages-page-data.d.ts +9 -0
  113. package/dist/server/pages-page-data.js +18 -10
  114. package/dist/server/pages-page-handler.js +19 -7
  115. package/dist/server/pages-page-response.d.ts +1 -0
  116. package/dist/server/pages-page-response.js +5 -4
  117. package/dist/server/pages-request-pipeline.d.ts +8 -6
  118. package/dist/server/pages-request-pipeline.js +31 -7
  119. package/dist/server/pages-router-entry.js +1 -1
  120. package/dist/server/prerender-manifest.d.ts +15 -1
  121. package/dist/server/prerender-manifest.js +29 -1
  122. package/dist/server/prod-server.d.ts +10 -7
  123. package/dist/server/prod-server.js +43 -28
  124. package/dist/server/request-pipeline.js +1 -1
  125. package/dist/server/static-file-cache.js +1 -1
  126. package/dist/shims/cache-handler.js +8 -1
  127. package/dist/shims/cache.d.ts +1 -1
  128. package/dist/shims/cache.js +3 -0
  129. package/dist/shims/cdn-cache.d.ts +2 -7
  130. package/dist/shims/cdn-cache.js +2 -14
  131. package/dist/shims/fetch-cache.d.ts +3 -1
  132. package/dist/shims/fetch-cache.js +77 -52
  133. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  134. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  135. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
  136. package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
  137. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
  138. package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
  139. package/dist/shims/internal/pages-data-target.d.ts +8 -3
  140. package/dist/shims/internal/pages-data-target.js +9 -4
  141. package/dist/shims/link.d.ts +1 -1
  142. package/dist/shims/link.js +179 -42
  143. package/dist/shims/navigation.d.ts +15 -3
  144. package/dist/shims/navigation.js +202 -39
  145. package/dist/shims/request-context.js +18 -0
  146. package/dist/shims/request-state-types.d.ts +2 -2
  147. package/dist/shims/router.js +31 -15
  148. package/dist/shims/unified-request-context.d.ts +1 -1
  149. package/dist/shims/unified-request-context.js +1 -0
  150. package/dist/typegen.js +1 -1
  151. package/dist/utils/middleware-request-headers.js +1 -1
  152. package/dist/utils/project.d.ts +1 -1
  153. package/dist/utils/protocol-headers.d.ts +7 -1
  154. package/dist/utils/protocol-headers.js +7 -1
  155. package/dist/utils/vite-version.d.ts +4 -12
  156. package/dist/utils/vite-version.js +39 -21
  157. package/package.json +31 -13
  158. package/dist/cloudflare/index.d.ts +0 -3
  159. package/dist/cloudflare/index.js +0 -3
  160. package/dist/packages/cloudflare/src/cache/cdn-adapter.runtime.js +0 -102
  161. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +0 -126
  162. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +0 -435
  163. package/dist/packages/cloudflare/src/deploy-config.js +0 -150
  164. package/dist/packages/cloudflare/src/deploy-help.js +0 -55
  165. package/dist/packages/cloudflare/src/deploy.js +0 -276
  166. package/dist/packages/cloudflare/src/tpr.d.ts +0 -45
  167. package/dist/packages/cloudflare/src/tpr.js +0 -561
  168. package/dist/packages/cloudflare/src/utils/cache-control-metadata.js +0 -20
@@ -27,8 +27,20 @@ const IMAGE_OPTIMIZATION_PATH = "/_next/image";
27
27
  * for any newly generated URLs.
28
28
  */
29
29
  const VINEXT_IMAGE_OPTIMIZATION_PATH = "/_vinext/image";
30
- /** Returns true when `pathname` is either supported image optimization endpoint. */
30
+ /**
31
+ * Returns true when `pathname` is either supported image optimization
32
+ * endpoint.
33
+ *
34
+ * A single trailing slash is accepted (`/_next/image/`): with
35
+ * `trailingSlash: true`, Next.js 308-redirects `/_next/image?url=...` to
36
+ * `/_next/image/?url=...` and then serves the slashed form — its route
37
+ * matching strips a trailing slash before matching internal paths (see
38
+ * getItem in packages/next/src/server/lib/router-utils/filesystem.ts).
39
+ * Rejecting the slashed form 404'd every dev-mode next/image request under
40
+ * `trailingSlash: true`.
41
+ */
31
42
  function isImageOptimizationPath(pathname) {
43
+ if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
32
44
  return pathname === "/_next/image" || pathname === "/_vinext/image";
33
45
  }
34
46
  /**
@@ -1,7 +1,7 @@
1
- import { RenderObservation } from "./cache-proof.js";
2
- import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache-handler.js";
3
1
  import { OnRequestErrorContext } from "./instrumentation.js";
2
+ import { RenderObservation } from "./cache-proof.js";
4
3
  import { normalizeMountedSlotsHeader } from "./app-mounted-slots-header.js";
4
+ import { CacheHandlerValue, CachedAppPageValue, CachedPagesValue, IncrementalCacheValue } from "../shims/cache-handler.js";
5
5
  import { AppRscRenderMode } from "./app-rsc-render-mode.js";
6
6
 
7
7
  //#region src/server/isr-cache.d.ts
@@ -34,6 +34,7 @@ declare const PRERENDER_REVALIDATE_HEADER = "x-prerender-revalidate";
34
34
  */
35
35
  declare const PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER = "x-prerender-revalidate-if-generated";
36
36
  declare function getRevalidateSecret(): string;
37
+ declare function isRevalidateSecret(value: string | null | undefined): boolean;
37
38
  /**
38
39
  * Authorize an incoming request as an on-demand revalidation trigger. Mirrors
39
40
  * Next.js's `checkIsOnDemandRevalidate`: the {@link PRERENDER_REVALIDATE_HEADER}
@@ -101,13 +102,21 @@ declare function buildAppPageCacheValue(html: string, rscData?: ArrayBuffer, sta
101
102
  * Long pathnames are hashed to stay within KV key-length limits (512 bytes).
102
103
  */
103
104
  declare function isrCacheKey(router: string, pathname: string, buildId?: string): string;
105
+ /**
106
+ * Compute an App Router ISR key for one cache artifact.
107
+ *
108
+ * App pages store HTML, RSC payloads, and route-handler responses separately.
109
+ * The suffix mirrors Next.js's separate on-disk app artifacts while keeping the
110
+ * Cloudflare KV key under its 512-byte limit for long pathnames.
111
+ */
112
+ declare function appIsrCacheKey(pathname: string, suffix: string, buildId?: string | undefined): string;
104
113
  declare function appIsrHtmlKey(pathname: string): string;
105
114
  /**
106
115
  * Build the ISR cache key for an RSC payload.
107
116
  *
108
117
  * Variants are sequenced in order: `source:<hash>` (intercepted source context,
109
118
  * only when an interception context is present), `slots:<hash>` (mounted parallel
110
- * route slots), and optionally `<render-mode-variant>` (e.g. `preserve-ui` or
119
+ * route slots), and optionally `<render-mode-variant>` (for example,
111
120
  * `prefetch-loading-shell`). Existing cached entries under the old format will
112
121
  * become unreachable after deployment. This is acceptable because ISR entries
113
122
  * have TTLs and will be regenerated on the next request.
@@ -124,4 +133,4 @@ declare function setRevalidateDuration(key: string, seconds: number): void;
124
133
  */
125
134
  declare function getRevalidateDuration(key: string): number | undefined;
126
135
  //#endregion
127
- export { ISRCacheEntry, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
136
+ export { ISRCacheEntry, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
@@ -80,6 +80,10 @@ function safeEqual(a, b) {
80
80
  for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
81
81
  return mismatch === 0;
82
82
  }
83
+ function isRevalidateSecret(value) {
84
+ if (typeof value !== "string" || value.length === 0) return false;
85
+ return safeEqual(value, getRevalidateSecret());
86
+ }
83
87
  /**
84
88
  * Authorize an incoming request as an on-demand revalidation trigger. Mirrors
85
89
  * Next.js's `checkIsOnDemandRevalidate`: the {@link PRERENDER_REVALIDATE_HEADER}
@@ -87,8 +91,8 @@ function safeEqual(a, b) {
87
91
  * NOT sufficient — see the security note on {@link PRERENDER_REVALIDATE_HEADER}.
88
92
  */
89
93
  function isOnDemandRevalidateRequest(headerValue) {
90
- if (typeof headerValue !== "string" || headerValue.length === 0) return false;
91
- return safeEqual(headerValue, getRevalidateSecret());
94
+ if (typeof headerValue !== "string") return false;
95
+ return isRevalidateSecret(headerValue);
92
96
  }
93
97
  /**
94
98
  * Get a cache entry with staleness information.
@@ -238,7 +242,7 @@ function normalizeInterceptionContextForCacheKey(interceptionContext) {
238
242
  *
239
243
  * Variants are sequenced in order: `source:<hash>` (intercepted source context,
240
244
  * only when an interception context is present), `slots:<hash>` (mounted parallel
241
- * route slots), and optionally `<render-mode-variant>` (e.g. `preserve-ui` or
245
+ * route slots), and optionally `<render-mode-variant>` (for example,
242
246
  * `prefetch-loading-shell`). Existing cached entries under the old format will
243
247
  * become unreachable after deployment. This is acceptable because ISR entries
244
248
  * have TTLs and will be regenerated on the next request.
@@ -279,4 +283,4 @@ function getRevalidateDuration(key) {
279
283
  return revalidateDurations.get(key);
280
284
  }
281
285
  //#endregion
282
- export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
286
+ export { PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, appIsrCacheKey, appIsrHtmlKey, appIsrRouteKey, appIsrRscKey, buildAppPageCacheValue, buildPagesCacheValue, getRevalidateDuration, getRevalidateSecret, isOnDemandRevalidateRequest, isRevalidateSecret, isrCacheKey, isrGet, isrSet, isrSetPrerenderedAppPage, normalizeMountedSlotsHeader, setRevalidateDuration, triggerBackgroundRegeneration };
@@ -51,6 +51,7 @@ declare function isNextDataPathname(pathname: string): boolean;
51
51
  * `/_next/data/<buildId>/about` → null (missing .json suffix)
52
52
  */
53
53
  declare function parseNextDataPathname(pathname: string, buildId: string): NextDataMatch | null;
54
+ declare function normalizeNextDataPagePathname(pagePathname: string, trailingSlash?: boolean): string;
54
55
  /**
55
56
  * Build the JSON envelope returned by `/_next/data/<buildId>/<page>.json`.
56
57
  * Mirrors Next.js' `RenderResult(JSON.stringify(props))` path in
@@ -79,6 +80,7 @@ declare function buildNextDataPropsJsonResponse(props: Record<string, unknown>,
79
80
  * before checking the status code.
80
81
  */
81
82
  declare function buildNextDataNotFoundResponse(): Response;
83
+ declare function buildMiddlewarePrefetchSkipResponse(matchedPathname: string): Response;
82
84
  type NormalizePagesDataRequestResult = {
83
85
  isDataReq: false;
84
86
  request: Request;
@@ -116,6 +118,6 @@ type NormalizePagesDataRequestResult = {
116
118
  * Extracted from `entries/pages-server-entry.ts` so both `renderPage` and
117
119
  * `runMiddleware` share a single implementation.
118
120
  */
119
- declare function normalizePagesDataRequest(request: Request, buildId: string | null, basePath?: string): NormalizePagesDataRequestResult;
121
+ declare function normalizePagesDataRequest(request: Request, buildId: string | null, basePath?: string, trailingSlash?: boolean): NormalizePagesDataRequestResult;
120
122
  //#endregion
121
- export { buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizePagesDataRequest, parseNextDataPathname };
123
+ export { buildMiddlewarePrefetchSkipResponse, buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname };
@@ -1,4 +1,5 @@
1
1
  import { addBasePathToPathname, hasBasePath, stripBasePath } from "../utils/base-path.js";
2
+ import { MIDDLEWARE_SKIP_HEADER } from "../utils/protocol-headers.js";
2
3
  import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
3
4
  //#region src/server/pages-data-route.ts
4
5
  /**
@@ -58,6 +59,10 @@ function parseNextDataPathname(pathname, buildId) {
58
59
  if (rest.startsWith("index/")) return { pagePathname: `/${rest.slice(6)}` };
59
60
  return { pagePathname: `/${rest}` };
60
61
  }
62
+ function normalizeNextDataPagePathname(pagePathname, trailingSlash = false) {
63
+ if (!trailingSlash || pagePathname === "/" || pagePathname.endsWith("/")) return pagePathname;
64
+ return `${pagePathname}/`;
65
+ }
61
66
  /**
62
67
  * Build the JSON envelope returned by `/_next/data/<buildId>/<page>.json`.
63
68
  * Mirrors Next.js' `RenderResult(JSON.stringify(props))` path in
@@ -106,6 +111,14 @@ function buildNextDataNotFoundResponse() {
106
111
  headers
107
112
  });
108
113
  }
114
+ function buildMiddlewarePrefetchSkipResponse(matchedPathname) {
115
+ return new Response("{}", { headers: {
116
+ "Content-Type": "application/json",
117
+ "x-matched-path": matchedPathname,
118
+ [MIDDLEWARE_SKIP_HEADER]: "1",
119
+ "Cache-Control": "private, no-cache, no-store, max-age=0, must-revalidate"
120
+ } });
121
+ }
109
122
  /**
110
123
  * Detect and normalize `/_next/data/<buildId>/<page>.json` requests in one
111
124
  * place so the Pages Router pipeline and middleware shim do not need to know
@@ -124,7 +137,7 @@ function buildNextDataNotFoundResponse() {
124
137
  * Extracted from `entries/pages-server-entry.ts` so both `renderPage` and
125
138
  * `runMiddleware` share a single implementation.
126
139
  */
127
- function normalizePagesDataRequest(request, buildId, basePath = "") {
140
+ function normalizePagesDataRequest(request, buildId, basePath = "", trailingSlash = false) {
128
141
  const reqUrl = new URL(request.url);
129
142
  const hadBasePath = !!basePath && hasBasePath(reqUrl.pathname, basePath);
130
143
  const dataPathname = basePath ? stripBasePath(reqUrl.pathname, basePath) : reqUrl.pathname;
@@ -143,15 +156,16 @@ function normalizePagesDataRequest(request, buildId, basePath = "") {
143
156
  search: "",
144
157
  notFoundResponse: buildNextDataNotFoundResponse()
145
158
  };
159
+ const pagePathname = normalizeNextDataPagePathname(dataMatch.pagePathname, trailingSlash);
146
160
  const normalizedUrl = new URL(reqUrl);
147
- normalizedUrl.pathname = hadBasePath ? addBasePathToPathname(dataMatch.pagePathname, basePath) : dataMatch.pagePathname;
161
+ normalizedUrl.pathname = hadBasePath ? addBasePathToPathname(pagePathname, basePath) : pagePathname;
148
162
  return {
149
163
  isDataReq: true,
150
164
  request: new Request(normalizedUrl, request),
151
- normalizedPathname: dataMatch.pagePathname,
165
+ normalizedPathname: pagePathname,
152
166
  search: reqUrl.search,
153
167
  notFoundResponse: null
154
168
  };
155
169
  }
156
170
  //#endregion
157
- export { buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizePagesDataRequest, parseNextDataPathname };
171
+ export { buildMiddlewarePrefetchSkipResponse, buildNextDataJsonResponse, buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, isNextDataPathname, normalizeNextDataPagePathname, normalizePagesDataRequest, parseNextDataPathname };
@@ -1,4 +1,5 @@
1
1
  //#region src/server/pages-dev-module-url.d.ts
2
+ declare function createPagesDevAssetUrl(assetPath: string): string;
2
3
  declare function createPagesDevModuleUrl(viteRoot: string, moduleFilePath: string, viteBase: string): string;
3
4
  //#endregion
4
- export { createPagesDevModuleUrl };
5
+ export { createPagesDevAssetUrl, createPagesDevModuleUrl };
@@ -4,12 +4,15 @@ function normalizeBase(base) {
4
4
  if (!base || base === "/") return "/";
5
5
  return `/${base.replace(/^\/+|\/+$/g, "")}/`;
6
6
  }
7
- function encodeModulePath(modulePath) {
7
+ function encodePagesDevModulePath(modulePath) {
8
8
  return encodeURI(modulePath).replace(/%5B/gi, "[").replace(/%5D/gi, "]").replace(/\?/g, "%3F").replace(/#/g, "%23");
9
9
  }
10
+ function createPagesDevAssetUrl(assetPath) {
11
+ return "/" + encodePagesDevModulePath(assetPath.replace(/^\/+/, ""));
12
+ }
10
13
  function createPagesDevModuleUrl(viteRoot, moduleFilePath, viteBase) {
11
14
  const relativePath = (/^[A-Za-z]:[\\/]/.test(viteRoot) ? path.win32 : path).relative(viteRoot, moduleFilePath).replace(/\\/g, "/");
12
- return normalizeBase(viteBase) + encodeModulePath(relativePath);
15
+ return normalizeBase(viteBase) + encodePagesDevModulePath(relativePath);
13
16
  }
14
17
  //#endregion
15
- export { createPagesDevModuleUrl };
18
+ export { createPagesDevAssetUrl, createPagesDevModuleUrl };
@@ -27,6 +27,10 @@ type PagesReqResResponse = Writable & {
27
27
  redirect: (statusOrUrl: number | string, url?: string) => void;
28
28
  getHeaders: () => PagesReqResHeaders;
29
29
  revalidate: (urlPath: string, opts?: RevalidateOptions) => Promise<void>;
30
+ setPreviewData: (data: object | string, options?: {
31
+ maxAge?: number;
32
+ path?: string;
33
+ }) => PagesReqResResponse;
30
34
  };
31
35
  type PagesRequestCookiesCarrier = {
32
36
  headers: {
@@ -45,6 +49,7 @@ type CreatePagesReqResResult = {
45
49
  res: PagesReqResResponse;
46
50
  responsePromise: Promise<Response>;
47
51
  };
52
+ type PagesPreviewData = object | string;
48
53
  /**
49
54
  * Read and parse a Pages Router API request body for the Workers/prod path.
50
55
  *
@@ -56,7 +61,13 @@ type CreatePagesReqResResult = {
56
61
  * @see https://nextjs.org/docs/pages/building-your-application/routing/api-routes#custom-config
57
62
  */
58
63
  declare function parsePagesApiBody(request: Request, maxBytes?: number): Promise<unknown>;
64
+ declare function getPagesPreviewDataFromCookieHeader(cookieHeader: string | string[] | null | undefined, options?: {
65
+ isOnDemandRevalidate?: boolean;
66
+ }): PagesPreviewData | false;
67
+ declare function getPagesPreviewData(request: Request, options?: {
68
+ isOnDemandRevalidate?: boolean;
69
+ }): PagesPreviewData | false;
59
70
  declare function attachPagesRequestCookies(req: PagesRequestCookiesCarrier): void;
60
71
  declare function createPagesReqRes(options: CreatePagesReqResOptions): CreatePagesReqResResult;
61
72
  //#endregion
62
- export { PagesBodyParseError as PagesApiBodyParseError, PagesReqResRequest, PagesReqResResponse, PagesRequestQuery, attachPagesRequestCookies, createPagesReqRes, parsePagesApiBody };
73
+ export { PagesBodyParseError as PagesApiBodyParseError, PagesPreviewData, PagesReqResRequest, PagesReqResResponse, PagesRequestQuery, attachPagesRequestCookies, createPagesReqRes, getPagesPreviewData, getPagesPreviewDataFromCookieHeader, parsePagesApiBody };
@@ -1,4 +1,5 @@
1
1
  import { parseCookieHeader } from "../utils/parse-cookie.js";
2
+ import { getRevalidateSecret, isRevalidateSecret } from "./isr-cache.js";
2
3
  import { readStreamAsTextWithLimit } from "../utils/text-stream.js";
3
4
  import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT } from "./pages-body-parser-config.js";
4
5
  import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
@@ -64,6 +65,44 @@ function createRequestReadable(request) {
64
65
  function parsePagesRequestCookies(cookieHeader) {
65
66
  return parseCookieHeader(Array.isArray(cookieHeader) ? cookieHeader.join("; ") : cookieHeader);
66
67
  }
68
+ function serializePreviewCookie(name, value, options = {}) {
69
+ const parts = [
70
+ `${name}=${encodeURIComponent(value)}`,
71
+ "HttpOnly",
72
+ `Path=${options.path ?? "/"}`,
73
+ process.env.NODE_ENV !== "development" ? "SameSite=None" : "SameSite=Lax"
74
+ ];
75
+ if (process.env.NODE_ENV !== "development") parts.push("Secure");
76
+ if (options.maxAge !== void 0) parts.push(`Max-Age=${Math.trunc(options.maxAge)}`);
77
+ return parts.join("; ");
78
+ }
79
+ function decodePagesPreviewPayload(payload) {
80
+ try {
81
+ const decoded = Buffer.from(payload, "base64url").toString("utf8");
82
+ const value = JSON.parse(decoded);
83
+ return typeof value === "object" && value !== null ? value : String(value);
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+ function getPagesPreviewDataFromCookieHeader(cookieHeader, options = {}) {
89
+ if (options.isOnDemandRevalidate) return false;
90
+ const cookies = parsePagesRequestCookies(cookieHeader);
91
+ const bypass = cookies.__prerender_bypass;
92
+ const payload = cookies.__next_preview_data;
93
+ if (!bypass && !payload) return false;
94
+ if (!isRevalidateSecret(bypass)) return false;
95
+ if (!payload) return {};
96
+ return decodePagesPreviewPayload(payload);
97
+ }
98
+ function getPagesPreviewData(request, options = {}) {
99
+ return getPagesPreviewDataFromCookieHeader(request.headers.get("cookie"), options);
100
+ }
101
+ function normalizeSetCookieHeader(value) {
102
+ if (value === void 0) return [];
103
+ if (Array.isArray(value)) return value.map(String);
104
+ return [String(value)];
105
+ }
67
106
  function attachPagesRequestCookies(req) {
68
107
  if (Object.hasOwn(req, "cookies")) return;
69
108
  Object.defineProperty(req, "cookies", {
@@ -170,6 +209,16 @@ var PagesResponseStream = class extends Writable {
170
209
  async revalidate(urlPath, opts) {
171
210
  await performOnDemandRevalidate(this.requestHeaders, urlPath, opts);
172
211
  }
212
+ setPreviewData(data, options = {}) {
213
+ const payload = Buffer.from(JSON.stringify(data)).toString("base64url");
214
+ if (payload.length > 2048) throw new Error("Preview data is limited to 2KB currently, reduce how much data you are storing as preview data to continue");
215
+ this.setHeader("Set-Cookie", [
216
+ ...normalizeSetCookieHeader(this.getHeader("Set-Cookie")),
217
+ serializePreviewCookie("__prerender_bypass", getRevalidateSecret(), options),
218
+ serializePreviewCookie("__next_preview_data", payload, options)
219
+ ]);
220
+ return this;
221
+ }
173
222
  _write(chunk, encoding, callback) {
174
223
  const buffer = typeof chunk === "string" ? Buffer.from(chunk, encoding) : Buffer.from(chunk);
175
224
  if (this.controller && !this.streamEnded) try {
@@ -260,4 +309,4 @@ function createPagesReqRes(options) {
260
309
  };
261
310
  }
262
311
  //#endregion
263
- export { PagesBodyParseError as PagesApiBodyParseError, attachPagesRequestCookies, createPagesReqRes, parsePagesApiBody };
312
+ export { PagesBodyParseError as PagesApiBodyParseError, attachPagesRequestCookies, createPagesReqRes, getPagesPreviewData, getPagesPreviewDataFromCookieHeader, parsePagesApiBody };
@@ -2,6 +2,7 @@ import { Route } from "../routing/pages-router.js";
2
2
  import { VinextNextData } from "../client/vinext-next-data.js";
3
3
  import { CachedPagesValue } from "../shims/cache-handler.js";
4
4
  import { ISRCacheEntry } from "./isr-cache.js";
5
+ import { PagesPreviewData } from "./pages-node-compat.js";
5
6
  import { PagesGsspResponse, PagesI18nRenderContext, PagesNextDataExtras } from "./pages-page-response.js";
6
7
  import { ReactNode } from "react";
7
8
 
@@ -62,12 +63,18 @@ type PagesPageModule = {
62
63
  locale?: string;
63
64
  locales?: string[];
64
65
  defaultLocale?: string;
66
+ draftMode?: true;
67
+ preview?: true;
68
+ previewData?: PagesPreviewData;
65
69
  }) => Promise<PagesPagePropsResult> | PagesPagePropsResult;
66
70
  getStaticProps?: (context: {
67
71
  params: Record<string, unknown> | null;
68
72
  locale?: string;
69
73
  locales?: string[];
70
74
  defaultLocale?: string;
75
+ draftMode?: true;
76
+ preview?: true;
77
+ previewData?: PagesPreviewData;
71
78
  /**
72
79
  * Indicates why `getStaticProps` was invoked.
73
80
  *
@@ -127,6 +134,7 @@ type ResolvePagesPageDataOptions = {
127
134
  * `.nextjs-ref/packages/next/src/server/render.tsx`.
128
135
  */
129
136
  isBuildTimePrerendering?: boolean;
137
+ validatePropsSerialization?: boolean;
130
138
  /**
131
139
  * When true, this dispatch was triggered by an on-demand revalidation
132
140
  * request (e.g. `res.revalidate()` in a Pages Router API route, or an
@@ -143,6 +151,7 @@ type ResolvePagesPageDataOptions = {
143
151
  * presence — see the security note in `isr-cache.ts`.
144
152
  */
145
153
  isOnDemandRevalidate?: boolean;
154
+ previewData?: PagesPreviewData | false;
146
155
  /**
147
156
  * The deployment ID used for deployment-skew protection. When set, it is
148
157
  * included as `x-nextjs-deployment-id` on all `_next/data` responses
@@ -179,7 +179,7 @@ function buildPagesCacheResponse(html, cacheState, fontLinkHeader, revalidateSec
179
179
  cacheControlMeta: cacheControl
180
180
  });
181
181
  const headers = new Headers({
182
- "Content-Type": "text/html",
182
+ "Content-Type": "text/html; charset=utf-8",
183
183
  ...buildCacheStateHeaders(cacheState)
184
184
  });
185
185
  applyCdnResponseHeaders(headers, { cacheControl: cacheControlHeader });
@@ -264,6 +264,12 @@ async function resolvePagesPageData(options) {
264
264
  }
265
265
  let pageProps = {};
266
266
  let gsspRes = null;
267
+ const previewData = options.isOnDemandRevalidate ? false : options.previewData ?? false;
268
+ const previewContext = previewData === false ? {} : {
269
+ draftMode: true,
270
+ preview: true,
271
+ previewData
272
+ };
267
273
  let sharedReqRes = null;
268
274
  function getSharedReqRes() {
269
275
  sharedReqRes ??= options.createGsspReqRes();
@@ -317,7 +323,8 @@ async function resolvePagesPageData(options) {
317
323
  resolvedUrl: options.resolvedUrl ?? options.routeUrl,
318
324
  locale: options.i18n.locale,
319
325
  locales: options.i18n.locales,
320
- defaultLocale: options.i18n.defaultLocale
326
+ defaultLocale: options.i18n.defaultLocale,
327
+ ...previewContext
321
328
  });
322
329
  if (isResponseSent(res)) return {
323
330
  kind: "response",
@@ -338,7 +345,7 @@ async function resolvePagesPageData(options) {
338
345
  response: buildPagesRedirectResponse(result.redirect, options, renderProps)
339
346
  };
340
347
  if (result?.notFound) return buildPagesNotFoundResult(options);
341
- if (result?.props !== void 0) isSerializableProps(options.routePattern, "getServerSideProps", pageProps);
348
+ if (result?.props !== void 0 && options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getServerSideProps", pageProps);
342
349
  gsspRes = res;
343
350
  }
344
351
  let isrRevalidateSeconds = null;
@@ -347,7 +354,7 @@ async function resolvePagesPageData(options) {
347
354
  const cacheKey = options.isrCacheKey("pages", pathname);
348
355
  const cached = await options.isrGet(cacheKey);
349
356
  const cachedValue = cached?.value.value;
350
- if (!options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq) {
357
+ if (!options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && !cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
351
358
  const hitResponse = buildPagesCacheResponse(cachedValue.html, "HIT", options.fontLinkHeader, void 0, options.expireSeconds, cached.value.cacheControl, cachedValue.status);
352
359
  const hitBotResult = applyBotETagAndCheck(hitResponse, cachedValue.html, options);
353
360
  if (hitBotResult) return hitBotResult;
@@ -356,7 +363,7 @@ async function resolvePagesPageData(options) {
356
363
  response: hitResponse
357
364
  };
358
365
  }
359
- if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && cached.isStale && !options.scriptNonce && !options.isDataReq) {
366
+ if (!options.isOnDemandRevalidate && cachedValue?.kind === "PAGES" && !cachedValue.generatedFromDataRequest && cached && cached.isStale && !options.scriptNonce && !options.isDataReq && previewData === false) {
360
367
  options.triggerBackgroundRegeneration(cacheKey, async function() {
361
368
  return options.runInFreshUnifiedContext(async () => {
362
369
  options.applyRequestContexts();
@@ -413,7 +420,7 @@ async function resolvePagesPageData(options) {
413
420
  response: staleResponse
414
421
  };
415
422
  }
416
- const generatedPageData = !options.isOnDemandRevalidate && cached?.isStale === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest && isUnknownRecord(cachedValue.pageData) ? cachedValue.pageData : null;
423
+ const generatedPageData = !options.isOnDemandRevalidate && previewData === false && cached?.isStale === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest && isUnknownRecord(cachedValue.pageData) ? cachedValue.pageData : null;
417
424
  if (!generatedPageData) {
418
425
  const shortCircuit = await loadForegroundAppInitialRenderProps();
419
426
  if (shortCircuit) return shortCircuit;
@@ -423,6 +430,7 @@ async function resolvePagesPageData(options) {
423
430
  locale: options.i18n.locale,
424
431
  locales: options.i18n.locales,
425
432
  defaultLocale: options.i18n.defaultLocale,
433
+ ...previewContext,
426
434
  revalidateReason: options.isOnDemandRevalidate ? "on-demand" : options.isBuildTimePrerendering ? "build" : "stale"
427
435
  });
428
436
  if (generatedPageData) {
@@ -444,10 +452,10 @@ async function resolvePagesPageData(options) {
444
452
  response: buildPagesRedirectResponse(result.redirect, options, renderProps)
445
453
  };
446
454
  if (result?.notFound) return buildPagesNotFoundResult(options);
447
- if (result?.props !== void 0) isSerializableProps(options.routePattern, "getStaticProps", pageProps);
448
- if (typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
449
- else if (cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest) isrRevalidateSeconds = cached?.value.cacheControl?.revalidate ?? 31536e3;
450
- if (shouldPersistFallbackData) {
455
+ if (result?.props !== void 0 && options.validatePropsSerialization !== false) isSerializableProps(options.routePattern, "getStaticProps", pageProps);
456
+ if (previewData === false && typeof result?.revalidate === "number" && result.revalidate > 0) isrRevalidateSeconds = result.revalidate;
457
+ else if (previewData === false && cachedValue?.kind === "PAGES" && cachedValue.generatedFromDataRequest) isrRevalidateSeconds = cached?.value.cacheControl?.revalidate ?? 31536e3;
458
+ if (shouldPersistFallbackData && previewData === false) {
451
459
  const revalidateSeconds = isrRevalidateSeconds ?? 31536e3;
452
460
  await options.isrSet(cacheKey, {
453
461
  kind: "PAGES",
@@ -3,8 +3,8 @@ import { patternToNextFormat } from "../routing/route-validation.js";
3
3
  import { getRequestExecutionContext } from "../shims/request-context.js";
4
4
  import { NEXTJS_DEPLOYMENT_ID_HEADER } from "./headers.js";
5
5
  import { reportRequestError } from "./instrumentation.js";
6
- import { NEVER_CACHE_CONTROL } from "./cache-control.js";
7
- import "./isr-decision.js";
6
+ import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, applyCdnResponseHeaders, shouldUseNextDeployCacheControl } from "./cache-control.js";
7
+ import { buildMissIsrCacheControl } from "./isr-decision.js";
8
8
  import { PRERENDER_REVALIDATE_HEADER, isOnDemandRevalidateRequest, isrCacheKey, isrGet, isrSet, triggerBackgroundRegeneration } from "./isr-cache.js";
9
9
  import { ensureFetchPatch } from "../shims/fetch-cache.js";
10
10
  import { appendAssetDeploymentIdQuery } from "../utils/deployment-id.js";
@@ -18,8 +18,8 @@ import { renderPagesPageResponse } from "./pages-page-response.js";
18
18
  import { hasPagesGetInitialProps } from "./pages-get-initial-props.js";
19
19
  import { buildNextDataNotFoundResponse, buildNextDataPropsJsonResponse, normalizePagesDataRequest, parseNextDataPathname } from "./pages-data-route.js";
20
20
  import { resolvePagesPageData } from "./pages-page-data.js";
21
- import { createPagesReqRes } from "./pages-node-compat.js";
22
21
  import { collectAssetTags, resolveClientModuleUrl } from "./pages-asset-tags.js";
22
+ import { createPagesReqRes, getPagesPreviewData } from "./pages-node-compat.js";
23
23
  //#region src/server/pages-page-handler.ts
24
24
  function shouldEmitPagesClientTraceMetadata(pageModule, appComponent) {
25
25
  if (typeof pageModule.getServerSideProps === "function") return true;
@@ -61,7 +61,7 @@ function createPagesPageHandler(opts) {
61
61
  const originalRequestPathAndSearch = originalRequestUrl.pathname + originalRequestUrl.search;
62
62
  let dataRequestPathname = null;
63
63
  let dataRequestSearch = "";
64
- const initialDataNorm = normalizePagesDataRequest(request, buildId);
64
+ const initialDataNorm = normalizePagesDataRequest(request, buildId, vinextConfig.basePath, hasMiddleware && vinextConfig.trailingSlash);
65
65
  if (!isDataReq) {
66
66
  if (initialDataNorm.notFoundResponse) return initialDataNorm.notFoundResponse;
67
67
  if (initialDataNorm.isDataReq) {
@@ -133,6 +133,7 @@ function createPagesPageHandler(opts) {
133
133
  const renderStatusCode = renderStatusCodeOverride ?? (routePattern === "/404" ? 404 : void 0);
134
134
  const query = mergeRouteParamsIntoQuery(parseQueryString(routeUrl), params);
135
135
  const pageModule = route.module;
136
+ const isStaticPropsRoute = typeof pageModule.getStaticProps === "function";
136
137
  const pagesNextData = buildPagesReadinessNextData({
137
138
  pageModule,
138
139
  appComponent: AppComponent,
@@ -177,7 +178,8 @@ function createPagesPageHandler(opts) {
177
178
  ...pagesNextData.__vinext,
178
179
  pageModuleUrl,
179
180
  appModuleUrl,
180
- hasMiddleware
181
+ hasMiddleware,
182
+ routeUrl
181
183
  }
182
184
  };
183
185
  const scriptNonce = getScriptNonceFromHeaderSources(request.headers, middlewareHeaders);
@@ -194,6 +196,7 @@ function createPagesPageHandler(opts) {
194
196
  request,
195
197
  url: originalRequestPathAndSearch
196
198
  });
199
+ const isOnDemandRevalidate = isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER));
197
200
  const pageDataResult = await resolvePagesPageData({
198
201
  isDataReq,
199
202
  err: err instanceof Error ? err : void 0,
@@ -217,7 +220,9 @@ function createPagesPageHandler(opts) {
217
220
  isrSet,
218
221
  expireSeconds: vinextConfig.expireTime,
219
222
  isBuildTimePrerendering: typeof process !== "undefined" && process.env && process.env.VINEXT_PRERENDER === "1",
220
- isOnDemandRevalidate: isOnDemandRevalidateRequest(request.headers.get(PRERENDER_REVALIDATE_HEADER)),
223
+ validatePropsSerialization: process.env.NODE_ENV !== "production" || process.env.VINEXT_PRERENDER === "1",
224
+ isOnDemandRevalidate,
225
+ previewData: getPagesPreviewData(request, { isOnDemandRevalidate }),
221
226
  pageModule,
222
227
  AppComponent,
223
228
  params,
@@ -300,6 +305,12 @@ function createPagesPageHandler(opts) {
300
305
  break;
301
306
  }
302
307
  if (!hasUserCacheControl) init.headers["Cache-Control"] = NEVER_CACHE_CONTROL;
308
+ } else if (isStaticPropsRoute) {
309
+ if (isrRevalidateSeconds) {
310
+ const headers = new Headers(init.headers);
311
+ applyCdnResponseHeaders(headers, { cacheControl: buildMissIsrCacheControl(isrRevalidateSeconds, vinextConfig.expireTime) });
312
+ for (const [key, value] of headers) init.headers[key] = value;
313
+ } else if (shouldUseNextDeployCacheControl()) init.headers["Cache-Control"] = BROWSER_REVALIDATE_CACHE_CONTROL;
303
314
  }
304
315
  if (routePattern !== "/_error" && routePattern !== "/500") {
305
316
  const deploymentId = process.env.__VINEXT_DEPLOYMENT_ID || process.env.NEXT_DEPLOYMENT_ID;
@@ -308,8 +319,8 @@ function createPagesPageHandler(opts) {
308
319
  return buildNextDataPropsJsonResponse(renderProps, safeJsonStringify, init);
309
320
  }
310
321
  const pageModuleIds = [];
311
- if (route.filePath) pageModuleIds.push(route.filePath);
312
322
  if (appAssetPath) pageModuleIds.push(appAssetPath);
323
+ if (route.filePath) pageModuleIds.push(route.filePath);
313
324
  return await renderPagesPageResponse({
314
325
  assetTags: collectAssetTags({
315
326
  manifest,
@@ -346,6 +357,7 @@ function createPagesPageHandler(opts) {
346
357
  isrCacheKey: pageIsrCacheKey,
347
358
  expireSeconds: vinextConfig.expireTime,
348
359
  isrRevalidateSeconds,
360
+ isStaticPropsRoute,
349
361
  isrSet,
350
362
  i18n: buildI18nRenderContext(i18nConfig, locale, currentDefaultLocale, domainLocales),
351
363
  isFallback: isFallbackRender,
@@ -91,6 +91,7 @@ type RenderPagesPageResponseOptions = {
91
91
  isrCacheKey: (router: string, pathname: string) => string;
92
92
  expireSeconds?: number;
93
93
  isrRevalidateSeconds: number | null;
94
+ isStaticPropsRoute?: boolean;
94
95
  isrSet: (key: string, data: CachedPagesValue, revalidateSeconds: number, tags?: string[], expireSeconds?: number) => Promise<void>;
95
96
  i18n: PagesI18nRenderContext;
96
97
  /**
@@ -1,7 +1,7 @@
1
1
  import { getRequestExecutionContext } from "../shims/request-context.js";
2
2
  import { reportRequestError } from "./instrumentation.js";
3
3
  import { setCacheStateHeaders } from "./cache-headers.js";
4
- import { NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders } from "./cache-control.js";
4
+ import { BROWSER_REVALIDATE_CACHE_CONTROL, NEVER_CACHE_CONTROL, NO_STORE_CACHE_CONTROL, applyCdnResponseHeaders, shouldUseNextDeployCacheControl } from "./cache-control.js";
5
5
  import { buildMissIsrCacheControl } from "./isr-decision.js";
6
6
  import { fnv1a52 } from "../utils/hash.js";
7
7
  import { encodeCacheTag } from "../utils/encode-cache-tag.js";
@@ -167,7 +167,7 @@ function applyGsspHeaders(headers, gsspRes, statusCode) {
167
167
  }
168
168
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") headers.set(key, String(value));
169
169
  }
170
- headers.set("Content-Type", "text/html");
170
+ headers.set("Content-Type", "text/html; charset=utf-8");
171
171
  return statusCode ?? gsspRes.statusCode;
172
172
  }
173
173
  async function renderPagesPageResponse(options) {
@@ -232,7 +232,7 @@ async function renderPagesPageResponse(options) {
232
232
  const markerIndex = shellHtml.indexOf(bodyMarker);
233
233
  const shellPrefix = shellHtml.slice(0, markerIndex);
234
234
  const shellSuffix = shellHtml.slice(markerIndex + 25);
235
- const responseHeaders = new Headers({ "Content-Type": "text/html" });
235
+ const responseHeaders = new Headers({ "Content-Type": "text/html; charset=utf-8" });
236
236
  const finalStatus = applyGsspHeaders(responseHeaders, options.gsspRes ?? options.documentReqRes?.res ?? null, options.statusCode);
237
237
  let responseBodyStream = bodyStream;
238
238
  if (!options.scriptNonce && options.isrRevalidateSeconds !== null && options.isrRevalidateSeconds > 0) {
@@ -264,7 +264,8 @@ async function renderPagesPageResponse(options) {
264
264
  tags: [encodeCacheTag(`_N_T_${stem || "/"}`)]
265
265
  });
266
266
  setCacheStateHeaders(responseHeaders, "MISS");
267
- } else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
267
+ } else if (options.isStaticPropsRoute && shouldUseNextDeployCacheControl()) responseHeaders.set("Cache-Control", BROWSER_REVALIDATE_CACHE_CONTROL);
268
+ else if (options.gsspRes && !userSetCacheControl) responseHeaders.set("Cache-Control", NEVER_CACHE_CONTROL);
268
269
  if (options.fontLinkHeader) responseHeaders.set("Link", options.fontLinkHeader);
269
270
  if (options.userAgent && isPagesStreamingBot(options.userAgent)) {
270
271
  const fullHtml = await readStreamAsText(compositeStream);
@@ -8,6 +8,13 @@ type PagesRenderOptions = {
8
8
  originalUrl?: string;
9
9
  };
10
10
  type FilesystemRoutePhase = "direct" | "beforeFiles" | "afterFiles" | "fallback";
11
+ type PageRouteMatch = {
12
+ route: {
13
+ isDynamic: boolean;
14
+ pattern?: string;
15
+ dataKind?: "static" | "server" | "none";
16
+ };
17
+ };
11
18
  declare function fetchWorkerFilesystemRoute(request: Request, requestPathname: string, phase: FilesystemRoutePhase, fetchAsset: (request: Request) => Promise<Response>): Promise<Response | false>;
12
19
  type MiddlewareResult = {
13
20
  continue: boolean;
@@ -37,12 +44,7 @@ type PagesPipelineDeps = {
37
44
  hasMiddleware: boolean;
38
45
  ctx?: unknown;
39
46
  rawSearch?: string;
40
- matchPageRoute?: ((pathname: string, request: Request) => {
41
- route: {
42
- isDynamic: boolean;
43
- pattern?: string;
44
- };
45
- } | null) | null;
47
+ matchPageRoute?: ((pathname: string, request: Request) => PageRouteMatch | null) | null;
46
48
  runMiddleware?: ((request: Request, ctx: unknown, opts: {
47
49
  isDataRequest: boolean;
48
50
  }) => Promise<MiddlewareResult>) | null;