vinext 0.1.2 → 0.1.4

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 (243) hide show
  1. package/dist/build/client-build-config.d.ts +11 -2
  2. package/dist/build/client-build-config.js +17 -6
  3. package/dist/build/prerender.d.ts +9 -1
  4. package/dist/build/prerender.js +42 -12
  5. package/dist/build/run-prerender.d.ts +10 -2
  6. package/dist/build/run-prerender.js +15 -1
  7. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  8. package/dist/client/app-nav-failure-handler.js +44 -0
  9. package/dist/client/pages-router-link-navigation.d.ts +33 -7
  10. package/dist/client/pages-router-link-navigation.js +32 -2
  11. package/dist/client/vinext-next-data.d.ts +18 -1
  12. package/dist/client/vinext-next-data.js +2 -0
  13. package/dist/client/window-next.d.ts +2 -1
  14. package/dist/client/window-next.js +12 -1
  15. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  16. package/dist/config/config-matchers.d.ts +11 -1
  17. package/dist/config/config-matchers.js +87 -16
  18. package/dist/config/next-config.d.ts +46 -4
  19. package/dist/config/next-config.js +147 -48
  20. package/dist/config/tsconfig-paths.js +14 -1
  21. package/dist/deploy.d.ts +30 -11
  22. package/dist/deploy.js +200 -112
  23. package/dist/entries/app-browser-entry.d.ts +9 -3
  24. package/dist/entries/app-browser-entry.js +21 -3
  25. package/dist/entries/app-rsc-entry.d.ts +2 -0
  26. package/dist/entries/app-rsc-entry.js +65 -5
  27. package/dist/entries/app-rsc-manifest.js +2 -0
  28. package/dist/entries/app-ssr-entry.js +1 -1
  29. package/dist/entries/pages-client-entry.js +66 -20
  30. package/dist/entries/pages-server-entry.js +47 -31
  31. package/dist/index.js +417 -102
  32. package/dist/plugins/dynamic-preload-metadata.js +2 -4
  33. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  34. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  35. package/dist/plugins/fonts.js +5 -4
  36. package/dist/plugins/optimize-imports.d.ts +2 -1
  37. package/dist/plugins/optimize-imports.js +11 -9
  38. package/dist/plugins/postcss.js +7 -7
  39. package/dist/plugins/strip-server-exports.d.ts +9 -7
  40. package/dist/plugins/strip-server-exports.js +493 -46
  41. package/dist/plugins/typeof-window.d.ts +14 -0
  42. package/dist/plugins/typeof-window.js +150 -0
  43. package/dist/routing/app-route-graph.d.ts +2 -1
  44. package/dist/routing/app-route-graph.js +46 -16
  45. package/dist/routing/file-matcher.d.ts +10 -1
  46. package/dist/routing/file-matcher.js +22 -1
  47. package/dist/routing/pages-router.js +3 -3
  48. package/dist/routing/utils.d.ts +35 -6
  49. package/dist/routing/utils.js +59 -7
  50. package/dist/server/api-handler.d.ts +6 -1
  51. package/dist/server/api-handler.js +21 -15
  52. package/dist/server/app-browser-action-result.d.ts +19 -6
  53. package/dist/server/app-browser-action-result.js +20 -11
  54. package/dist/server/app-browser-entry.js +175 -91
  55. package/dist/server/app-browser-error.d.ts +10 -6
  56. package/dist/server/app-browser-error.js +43 -8
  57. package/dist/server/app-browser-hydration.d.ts +2 -0
  58. package/dist/server/app-browser-hydration.js +1 -0
  59. package/dist/server/app-browser-navigation-controller.d.ts +5 -3
  60. package/dist/server/app-browser-navigation-controller.js +23 -2
  61. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  62. package/dist/server/app-browser-server-action-navigation.js +9 -0
  63. package/dist/server/app-browser-state.d.ts +1 -1
  64. package/dist/server/app-browser-state.js +19 -11
  65. package/dist/server/app-browser-stream.js +86 -43
  66. package/dist/server/app-browser-visible-commit.d.ts +1 -1
  67. package/dist/server/app-elements-wire.d.ts +6 -1
  68. package/dist/server/app-elements-wire.js +14 -4
  69. package/dist/server/app-elements.d.ts +2 -2
  70. package/dist/server/app-elements.js +2 -2
  71. package/dist/server/app-fallback-renderer.d.ts +1 -0
  72. package/dist/server/app-fallback-renderer.js +3 -1
  73. package/dist/server/app-optimistic-routing.js +2 -2
  74. package/dist/server/app-page-boundary-render.d.ts +1 -0
  75. package/dist/server/app-page-boundary-render.js +27 -14
  76. package/dist/server/app-page-cache-render.d.ts +53 -0
  77. package/dist/server/app-page-cache-render.js +91 -0
  78. package/dist/server/app-page-cache.d.ts +16 -2
  79. package/dist/server/app-page-cache.js +62 -1
  80. package/dist/server/app-page-dispatch.d.ts +26 -0
  81. package/dist/server/app-page-dispatch.js +149 -92
  82. package/dist/server/app-page-element-builder.d.ts +1 -0
  83. package/dist/server/app-page-element-builder.js +5 -2
  84. package/dist/server/app-page-execution.d.ts +6 -1
  85. package/dist/server/app-page-execution.js +21 -1
  86. package/dist/server/app-page-probe.d.ts +1 -0
  87. package/dist/server/app-page-probe.js +4 -0
  88. package/dist/server/app-page-render-observation.d.ts +3 -1
  89. package/dist/server/app-page-render-observation.js +17 -1
  90. package/dist/server/app-page-render.d.ts +12 -1
  91. package/dist/server/app-page-render.js +42 -4
  92. package/dist/server/app-page-request.d.ts +2 -0
  93. package/dist/server/app-page-request.js +2 -1
  94. package/dist/server/app-page-route-wiring.d.ts +3 -1
  95. package/dist/server/app-page-route-wiring.js +14 -5
  96. package/dist/server/app-page-stream.d.ts +15 -3
  97. package/dist/server/app-page-stream.js +11 -5
  98. package/dist/server/app-pages-bridge.d.ts +23 -1
  99. package/dist/server/app-pages-bridge.js +26 -17
  100. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  101. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  102. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  103. package/dist/server/app-ppr-fallback-shell.js +8 -1
  104. package/dist/server/app-route-handler-dispatch.js +9 -2
  105. package/dist/server/app-route-handler-policy.d.ts +1 -0
  106. package/dist/server/app-router-entry.js +5 -0
  107. package/dist/server/app-rsc-cache-busting.js +2 -0
  108. package/dist/server/app-rsc-handler.d.ts +28 -0
  109. package/dist/server/app-rsc-handler.js +195 -59
  110. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  111. package/dist/server/app-rsc-route-matching.js +8 -2
  112. package/dist/server/app-segment-config.d.ts +9 -1
  113. package/dist/server/app-segment-config.js +12 -3
  114. package/dist/server/app-server-action-execution.d.ts +1 -0
  115. package/dist/server/app-server-action-execution.js +47 -15
  116. package/dist/server/app-ssr-entry.d.ts +2 -0
  117. package/dist/server/app-ssr-entry.js +84 -39
  118. package/dist/server/before-interactive-head.d.ts +17 -0
  119. package/dist/server/before-interactive-head.js +35 -0
  120. package/dist/server/cache-control.js +4 -0
  121. package/dist/server/csp.js +1 -4
  122. package/dist/server/dev-server.d.ts +2 -2
  123. package/dist/server/dev-server.js +321 -83
  124. package/dist/server/hybrid-route-priority.d.ts +22 -0
  125. package/dist/server/hybrid-route-priority.js +33 -0
  126. package/dist/server/image-optimization.d.ts +18 -9
  127. package/dist/server/image-optimization.js +37 -23
  128. package/dist/server/implicit-tags.d.ts +2 -1
  129. package/dist/server/implicit-tags.js +4 -1
  130. package/dist/server/middleware-matcher.js +12 -3
  131. package/dist/server/middleware-runtime.d.ts +3 -4
  132. package/dist/server/middleware-runtime.js +2 -0
  133. package/dist/server/navigation-planner.d.ts +135 -41
  134. package/dist/server/navigation-planner.js +138 -0
  135. package/dist/server/navigation-trace.d.ts +9 -1
  136. package/dist/server/navigation-trace.js +9 -1
  137. package/dist/server/operation-token.d.ts +40 -0
  138. package/dist/server/operation-token.js +85 -0
  139. package/dist/server/pages-api-route.d.ts +6 -0
  140. package/dist/server/pages-api-route.js +13 -2
  141. package/dist/server/pages-asset-tags.d.ts +2 -1
  142. package/dist/server/pages-asset-tags.js +6 -2
  143. package/dist/server/pages-data-route.d.ts +9 -2
  144. package/dist/server/pages-data-route.js +18 -6
  145. package/dist/server/pages-dev-module-url.d.ts +4 -0
  146. package/dist/server/pages-dev-module-url.js +15 -0
  147. package/dist/server/pages-document-initial-props.d.ts +4 -15
  148. package/dist/server/pages-document-initial-props.js +27 -56
  149. package/dist/server/pages-get-initial-props.d.ts +54 -4
  150. package/dist/server/pages-get-initial-props.js +43 -1
  151. package/dist/server/pages-i18n.js +2 -2
  152. package/dist/server/pages-node-compat.js +2 -2
  153. package/dist/server/pages-page-data.d.ts +11 -2
  154. package/dist/server/pages-page-data.js +207 -34
  155. package/dist/server/pages-page-handler.d.ts +4 -2
  156. package/dist/server/pages-page-handler.js +62 -23
  157. package/dist/server/pages-page-response.d.ts +4 -1
  158. package/dist/server/pages-page-response.js +11 -8
  159. package/dist/server/pages-readiness.js +1 -1
  160. package/dist/server/pages-request-pipeline.d.ts +8 -7
  161. package/dist/server/pages-request-pipeline.js +126 -47
  162. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  163. package/dist/server/pregenerated-concrete-paths.js +2 -19
  164. package/dist/server/prerender-manifest.d.ts +33 -0
  165. package/dist/server/prerender-manifest.js +54 -0
  166. package/dist/server/prerender-route-params.d.ts +1 -2
  167. package/dist/server/prod-server.d.ts +3 -1
  168. package/dist/server/prod-server.js +50 -13
  169. package/dist/server/request-pipeline.d.ts +3 -15
  170. package/dist/server/request-pipeline.js +58 -47
  171. package/dist/server/rsc-stream-hints.d.ts +5 -1
  172. package/dist/server/rsc-stream-hints.js +6 -1
  173. package/dist/server/seed-cache.js +10 -18
  174. package/dist/server/static-file-cache.js +16 -4
  175. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  176. package/dist/shims/app-router-scroll-state.js +14 -2
  177. package/dist/shims/app-router-scroll.d.ts +3 -0
  178. package/dist/shims/app-router-scroll.js +28 -18
  179. package/dist/shims/before-interactive-context.d.ts +14 -3
  180. package/dist/shims/cache-runtime.js +3 -2
  181. package/dist/shims/cache.d.ts +1 -0
  182. package/dist/shims/cache.js +1 -1
  183. package/dist/shims/cdn-cache.d.ts +5 -5
  184. package/dist/shims/document.d.ts +15 -20
  185. package/dist/shims/document.js +5 -8
  186. package/dist/shims/dynamic-preload-chunks.js +6 -4
  187. package/dist/shims/error-boundary.d.ts +2 -0
  188. package/dist/shims/error-boundary.js +7 -0
  189. package/dist/shims/error.js +3 -2
  190. package/dist/shims/error.react-server.d.ts +9 -0
  191. package/dist/shims/error.react-server.js +6 -0
  192. package/dist/shims/fetch-cache.d.ts +3 -1
  193. package/dist/shims/fetch-cache.js +45 -20
  194. package/dist/shims/hash-scroll.js +6 -1
  195. package/dist/shims/headers.js +29 -4
  196. package/dist/shims/image.js +9 -2
  197. package/dist/shims/internal/als-registry.js +28 -1
  198. package/dist/shims/internal/app-route-detection.js +8 -17
  199. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  200. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  201. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  202. package/dist/shims/internal/navigation-untracked.js +55 -0
  203. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +6 -7
  204. package/dist/shims/internal/pages-data-fetch-dedup.js +67 -14
  205. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  206. package/dist/shims/internal/pages-data-target.js +17 -8
  207. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  208. package/dist/shims/internal/pages-router-accessor.js +13 -0
  209. package/dist/shims/internal/router-context.d.ts +2 -1
  210. package/dist/shims/internal/router-context.js +3 -1
  211. package/dist/shims/link.js +47 -19
  212. package/dist/shims/metadata.js +4 -4
  213. package/dist/shims/navigation.d.ts +8 -2
  214. package/dist/shims/navigation.js +63 -31
  215. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  216. package/dist/shims/ppr-fallback-shell.js +28 -7
  217. package/dist/shims/router.d.ts +18 -3
  218. package/dist/shims/router.js +512 -142
  219. package/dist/shims/script.js +8 -4
  220. package/dist/shims/server.d.ts +16 -1
  221. package/dist/shims/server.js +44 -12
  222. package/dist/shims/unified-request-context.js +1 -0
  223. package/dist/utils/built-asset-url.d.ts +4 -0
  224. package/dist/utils/built-asset-url.js +11 -0
  225. package/dist/utils/commonjs-loader.d.ts +16 -0
  226. package/dist/utils/commonjs-loader.js +100 -0
  227. package/dist/utils/deployment-id.d.ts +8 -0
  228. package/dist/utils/deployment-id.js +22 -0
  229. package/dist/utils/has-trailing-comma.d.ts +24 -0
  230. package/dist/utils/has-trailing-comma.js +62 -0
  231. package/dist/utils/html-limited-bots.d.ts +18 -1
  232. package/dist/utils/html-limited-bots.js +23 -1
  233. package/dist/utils/parse-cookie.d.ts +13 -0
  234. package/dist/utils/parse-cookie.js +52 -0
  235. package/dist/utils/path.d.ts +7 -1
  236. package/dist/utils/path.js +9 -1
  237. package/dist/utils/text-stream.d.ts +1 -1
  238. package/dist/utils/text-stream.js +2 -2
  239. package/dist/utils/vite-version.d.ts +12 -1
  240. package/dist/utils/vite-version.js +9 -1
  241. package/package.json +2 -2
  242. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  243. package/dist/shims/internal/parse-cookie-header.js +0 -30
@@ -7,6 +7,13 @@ type ClientAssetFileNameInfo = {
7
7
  readonly originalFileName?: string;
8
8
  readonly originalFileNames?: readonly string[];
9
9
  };
10
+ type RscFrameworkModuleInfo = {
11
+ importers: string[];
12
+ isEntry: boolean;
13
+ };
14
+ type RscFrameworkManualChunksMeta = {
15
+ getModuleInfo(id: string): RscFrameworkModuleInfo | null;
16
+ };
10
17
  /**
11
18
  * Routes client assets into Next-compatible subtrees: `.css` sources go to
12
19
  * `<assetsDir>/css/`, everything else to `<assetsDir>/media/`. Returned as a
@@ -80,18 +87,20 @@ declare function isRscFrameworkModule(id: string): boolean;
80
87
  * dedicated "framework" chunk in the RSC server build. Returns the bundler-
81
88
  * appropriate shape: rolldown's `codeSplitting` for Vite 8+, Rollup's
82
89
  * `manualChunks` for Vite 7. See {@link RSC_FRAMEWORK_CHUNK_TEST} for the
83
- * motivation (issue #1549).
90
+ * motivation (issue #1549). Framework modules that are only reachable through
91
+ * dynamic imports must stay out of the eager framework chunk (issue #2073).
84
92
  */
85
93
  declare function createRscFrameworkChunkOutputConfig(viteMajorVersion: number): {
86
94
  codeSplitting: {
87
95
  groups: {
88
96
  name: string;
89
97
  test: RegExp;
98
+ entriesAware: boolean;
90
99
  }[];
91
100
  };
92
101
  manualChunks?: undefined;
93
102
  } | {
94
- manualChunks(id: string): string | undefined;
103
+ manualChunks(id: string, meta: RscFrameworkManualChunksMeta): string | undefined;
95
104
  codeSplitting?: undefined;
96
105
  };
97
106
  /**
@@ -33,9 +33,10 @@ function createClientAssetFileNames(assetsDir) {
33
33
  * (node_modules/.pnpm/pkg@ver/node_modules/pkg).
34
34
  */
35
35
  function getPackageName(id) {
36
- const nmIdx = id.lastIndexOf("node_modules/");
36
+ const normalizedId = id.replaceAll("\\", "/");
37
+ const nmIdx = normalizedId.lastIndexOf("node_modules/");
37
38
  if (nmIdx === -1) return null;
38
- const rest = id.slice(nmIdx + 13);
39
+ const rest = normalizedId.slice(nmIdx + 13);
39
40
  if (rest.startsWith("@")) {
40
41
  const parts = rest.split("/");
41
42
  return parts.length >= 2 ? parts[0] + "/" + parts[1] : null;
@@ -153,20 +154,30 @@ function isRscFrameworkModule(id) {
153
154
  const pkg = getPackageName(id);
154
155
  return pkg !== null && FRAMEWORK_PACKAGES.includes(pkg);
155
156
  }
157
+ function isStaticallyReachableFromEntry(id, meta, visited = /* @__PURE__ */ new Set()) {
158
+ if (visited.has(id)) return false;
159
+ visited.add(id);
160
+ const moduleInfo = meta.getModuleInfo(id);
161
+ if (!moduleInfo) return false;
162
+ if (moduleInfo.isEntry) return true;
163
+ return moduleInfo.importers.some((importer) => isStaticallyReachableFromEntry(importer, meta, visited));
164
+ }
156
165
  /**
157
166
  * Output config that isolates React (and the RSC flight runtime) into a
158
167
  * dedicated "framework" chunk in the RSC server build. Returns the bundler-
159
168
  * appropriate shape: rolldown's `codeSplitting` for Vite 8+, Rollup's
160
169
  * `manualChunks` for Vite 7. See {@link RSC_FRAMEWORK_CHUNK_TEST} for the
161
- * motivation (issue #1549).
170
+ * motivation (issue #1549). Framework modules that are only reachable through
171
+ * dynamic imports must stay out of the eager framework chunk (issue #2073).
162
172
  */
163
173
  function createRscFrameworkChunkOutputConfig(viteMajorVersion) {
164
174
  if (viteMajorVersion >= 8) return { codeSplitting: { groups: [{
165
175
  name: "framework",
166
- test: RSC_FRAMEWORK_CHUNK_TEST
176
+ test: RSC_FRAMEWORK_CHUNK_TEST,
177
+ entriesAware: true
167
178
  }] } };
168
- return { manualChunks(id) {
169
- return isRscFrameworkModule(id) ? "framework" : void 0;
179
+ return { manualChunks(id, meta) {
180
+ return isRscFrameworkModule(id) && isStaticallyReachableFromEntry(id, meta) ? "framework" : void 0;
170
181
  } };
171
182
  }
172
183
  /**
@@ -22,7 +22,8 @@ type PrerenderRouteResult = {
22
22
  * Omitted for non-dynamic routes where pattern === path.
23
23
  */
24
24
  path?: string; /** Which router produced this route. Used by cache seeding. */
25
- router: "app" | "pages";
25
+ router: "app" | "pages"; /** Set to true when this is a PPR fallback shell. */
26
+ fallback?: boolean;
26
27
  } | {
27
28
  route: string;
28
29
  status: "skipped";
@@ -31,6 +32,13 @@ type PrerenderRouteResult = {
31
32
  route: string;
32
33
  status: "error";
33
34
  error: string;
35
+ /**
36
+ * Set when the error must fail the build in ALL modes (default included),
37
+ * not just `output: 'export'`. Used for a thrown generateStaticParams /
38
+ * getStaticPaths, which Next.js treats as a fatal build error rather than a
39
+ * silently-skipped route. Refs cloudflare/vinext#1982
40
+ */
41
+ fatal?: true;
34
42
  };
35
43
  /** Called after each route is resolved (rendered, skipped, or error). */
36
44
  type PrerenderProgressCallback = (update: {
@@ -7,11 +7,11 @@ import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from
7
7
  import { BLOCKED_PAGES } from "../shims/constants.js";
8
8
  import { concatUint8Arrays, decodeRscEmbeddedChunk } from "../server/app-rsc-embedded-chunks.js";
9
9
  import { navigationRuntimeRscBootstrapExpression } from "../server/app-ssr-stream.js";
10
+ import { createAppPprFallbackShells, markAppPprDynamicFallbackShellHtml } from "../server/app-ppr-fallback-shell.js";
10
11
  import { encodePrerenderRouteParams, serializePrerenderRouteParamsHeader } from "../server/prerender-route-params.js";
11
12
  import { readPrerenderSecret } from "./server-manifest.js";
12
13
  import { getOutputPath, getRscOutputPath } from "../utils/prerender-output-paths.js";
13
14
  import { startProdServer } from "../server/prod-server.js";
14
- import { createAppPprFallbackShells } from "../server/app-ppr-fallback-shell.js";
15
15
  import fs from "node:fs";
16
16
  import path from "node:path";
17
17
  import os from "node:os";
@@ -33,9 +33,21 @@ import os from "node:os";
33
33
  * 'default' — skips SSR routes (served at request time); ISR routes rendered
34
34
  * 'export' — SSR routes are build errors; ISR treated as static (no revalidate)
35
35
  */
36
+ const EXPERIMENTAL_PPR_FALLBACK_SHELLS_ENV = "__VINEXT_EXPERIMENTAL_PPR_FALLBACK_SHELLS";
37
+ function isExperimentalPprFallbackShellGenerationEnabled(env = process.env) {
38
+ return env[EXPERIMENTAL_PPR_FALLBACK_SHELLS_ENV] === "1";
39
+ }
36
40
  function getErrorMessageWithStack(err) {
37
41
  return err.stack || err.message;
38
42
  }
43
+ var PrerenderUserFunctionError = class extends Error {};
44
+ function parsePrerenderEndpointError(text) {
45
+ try {
46
+ const parsed = JSON.parse(text);
47
+ if (parsed && typeof parsed === "object" && "error" in parsed) return typeof parsed.error === "string" && parsed.error.length > 0 ? parsed.error : "Unknown prerender endpoint error";
48
+ } catch {}
49
+ return text || "Unknown prerender endpoint error";
50
+ }
39
51
  /** Sentinel path used to trigger 404 rendering without a real route match. */
40
52
  const NOT_FOUND_SENTINEL_PATH = "/__vinext_nonexistent_for_404__";
41
53
  const DEFAULT_CONCURRENCY = Math.min(os.availableParallelism(), 8);
@@ -275,6 +287,7 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
275
287
  const res = await fetch(`${baseUrl}/__vinext/prerender/pages-static-paths?${search}`, { headers: secretHeaders });
276
288
  const text = await res.text();
277
289
  if (!res.ok) {
290
+ if (res.status === 500) throw new PrerenderUserFunctionError(parsePrerenderEndpointError(text));
278
291
  console.warn(`[vinext] Warning: /__vinext/prerender/pages-static-paths returned ${res.status} for ${r.pattern}. Dynamic paths will be skipped. This may indicate a stale or missing prerender secret.`);
279
292
  return {
280
293
  paths: [],
@@ -322,10 +335,21 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
322
335
  });
323
336
  continue;
324
337
  }
325
- const pathsResult = await route.module.getStaticPaths({
326
- locales: [],
327
- defaultLocale: ""
328
- });
338
+ let pathsResult;
339
+ try {
340
+ pathsResult = await route.module.getStaticPaths({
341
+ locales: [],
342
+ defaultLocale: ""
343
+ });
344
+ } catch (e) {
345
+ results.push({
346
+ route: route.pattern,
347
+ status: "error",
348
+ error: `Failed to call getStaticPaths(): ${e.message}`,
349
+ ...e instanceof PrerenderUserFunctionError ? { fatal: true } : {}
350
+ });
351
+ continue;
352
+ }
329
353
  const fallback = pathsResult?.fallback ?? false;
330
354
  if (mode === "export" && fallback !== false) {
331
355
  results.push({
@@ -521,6 +545,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
521
545
  const res = await fetch(`${baseUrl}/__vinext/prerender/static-params?${search}`, { headers: secretHeaders });
522
546
  const text = await res.text();
523
547
  if (!res.ok) {
548
+ if (res.status === 500) throw new PrerenderUserFunctionError(parsePrerenderEndpointError(text));
524
549
  console.warn(`[vinext] Warning: /__vinext/prerender/static-params returned ${res.status} for ${pattern}. Static params will be skipped. This may indicate a stale or missing prerender secret.`);
525
550
  return null;
526
551
  }
@@ -634,6 +659,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
634
659
  for (const params of paramSets) {
635
660
  if (params === null || params === void 0) throw new Error(`generateStaticParams() for ${route.pattern} returned an entry with no params object.`);
636
661
  const urlPath = buildUrlFromParams(route.pattern, params);
662
+ if (queuedRouteUrls.has(urlPath)) continue;
637
663
  queuedRouteUrls.add(urlPath);
638
664
  urlsToRender.push({
639
665
  urlPath,
@@ -642,7 +668,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
642
668
  revalidate,
643
669
  isSpeculative: false
644
670
  });
645
- if (config.cacheComponents === true) for (const fallbackShell of createAppPprFallbackShells(route, params)) {
671
+ if (config.cacheComponents === true && isExperimentalPprFallbackShellGenerationEnabled()) for (const fallbackShell of createAppPprFallbackShells(route, params)) {
646
672
  if (queuedRouteUrls.has(fallbackShell.pathname)) continue;
647
673
  queuedRouteUrls.add(fallbackShell.pathname);
648
674
  urlsToRender.push({
@@ -650,7 +676,8 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
650
676
  routePattern: route.pattern,
651
677
  prerenderRouteParams: encodePrerenderRouteParams(route.pattern, fallbackShell.params, fallbackShell.fallbackParamNames),
652
678
  revalidate,
653
- isSpeculative: false
679
+ isSpeculative: false,
680
+ isFallback: true
654
681
  });
655
682
  }
656
683
  }
@@ -660,7 +687,8 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
660
687
  results.push({
661
688
  route: route.pattern,
662
689
  status: "error",
663
- error: `Failed to call generateStaticParams(): ${detail}`
690
+ error: `Failed to call generateStaticParams(): ${detail}`,
691
+ ...e instanceof PrerenderUserFunctionError ? { fatal: true } : {}
664
692
  });
665
693
  }
666
694
  else if (type === "unknown") urlsToRender.push({
@@ -684,7 +712,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
684
712
  * exactly once per URL after this function returns, keeping the callback
685
713
  * at a single, predictable call site.
686
714
  */
687
- async function renderUrl({ urlPath, routePattern, prerenderRouteParams, revalidate, isSpeculative }) {
715
+ async function renderUrl({ urlPath, routePattern, prerenderRouteParams, revalidate, isSpeculative, isFallback }) {
688
716
  try {
689
717
  const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParams);
690
718
  const htmlHeaders = new Headers();
@@ -736,7 +764,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
736
764
  status: "error",
737
765
  error: "RSC handler returned no prerender HTML"
738
766
  };
739
- const html = htmlRender.html;
767
+ const html = isFallback ? markAppPprDynamicFallbackShellHtml(htmlRender.html) : htmlRender.html;
740
768
  let rscData = extractRscPayloadFromPrerenderedHtml(html);
741
769
  if (rscData === null) {
742
770
  const rscHeaders = new Headers({
@@ -772,7 +800,8 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
772
800
  revalidate: renderedRevalidate,
773
801
  ...typeof renderedRevalidate === "number" ? { expire: renderedCacheControl.expire } : {},
774
802
  router: "app",
775
- ...urlPath !== routePattern ? { path: urlPath } : {}
803
+ ...urlPath !== routePattern ? { path: urlPath } : {},
804
+ ...isFallback ? { fallback: true } : {}
776
805
  };
777
806
  } catch (e) {
778
807
  if (isSpeculative) return {
@@ -878,7 +907,8 @@ function writePrerenderIndex(routes, outDir, options) {
878
907
  revalidate: r.revalidate,
879
908
  ...typeof r.revalidate === "number" ? { expire: r.expire } : {},
880
909
  router: r.router,
881
- ...r.path ? { path: r.path } : {}
910
+ ...r.path ? { path: r.path } : {},
911
+ ...r.fallback ? { fallback: true } : {}
882
912
  };
883
913
  if (r.status === "skipped") return {
884
914
  route: r.route,
@@ -1,5 +1,5 @@
1
1
  import { ResolvedNextConfig } from "../config/next-config.js";
2
- import { PrerenderResult } from "./prerender.js";
2
+ import { PrerenderResult, PrerenderRouteResult } from "./prerender.js";
3
3
 
4
4
  //#region src/build/run-prerender.d.ts
5
5
  type RunPrerenderOptions = {
@@ -48,6 +48,14 @@ type RunPrerenderOptions = {
48
48
  * If a required production bundle does not exist, an error is thrown directing
49
49
  * the user to run `vinext build` first.
50
50
  */
51
+ /**
52
+ * Throw if any route is a `fatal` error (a thrown generateStaticParams /
53
+ * getStaticPaths). These fail the build in ALL modes — default included —
54
+ * matching `next build`, unlike intentionally-skipped dynamic/SSR routes and
55
+ * non-fatal errors (e.g. transport failures), which only fail under
56
+ * `output: 'export'`. Exported for direct unit testing. Refs cloudflare/vinext#1982
57
+ */
58
+ declare function assertNoFatalPrerenderRoutes(routes: readonly PrerenderRouteResult[]): void;
51
59
  declare function runPrerender(options: RunPrerenderOptions): Promise<PrerenderResult | null>;
52
60
  //#endregion
53
- export { runPrerender };
61
+ export { assertNoFatalPrerenderRoutes, runPrerender };
@@ -82,6 +82,19 @@ function readBuiltBuildId(serverDir) {
82
82
  * If a required production bundle does not exist, an error is thrown directing
83
83
  * the user to run `vinext build` first.
84
84
  */
85
+ /**
86
+ * Throw if any route is a `fatal` error (a thrown generateStaticParams /
87
+ * getStaticPaths). These fail the build in ALL modes — default included —
88
+ * matching `next build`, unlike intentionally-skipped dynamic/SSR routes and
89
+ * non-fatal errors (e.g. transport failures), which only fail under
90
+ * `output: 'export'`. Exported for direct unit testing. Refs cloudflare/vinext#1982
91
+ */
92
+ function assertNoFatalPrerenderRoutes(routes) {
93
+ const fatalRoutes = routes.filter((r) => r.status === "error" && r.fatal === true);
94
+ if (fatalRoutes.length === 0) return;
95
+ const fatalList = fatalRoutes.map((r) => ` ${r.route}: ${r.error}`).join("\n");
96
+ throw new Error(`Prerender failed: ${fatalRoutes.length} route${fatalRoutes.length !== 1 ? "s" : ""} errored during static generation.\n${fatalList}`);
97
+ }
85
98
  async function runPrerender(options) {
86
99
  const { root } = options;
87
100
  const appDir = findDir(root, "app", "src/app");
@@ -197,6 +210,7 @@ async function runPrerender(options) {
197
210
  } finally {
198
211
  progress.finish(rendered, skipped, errors);
199
212
  }
213
+ assertNoFatalPrerenderRoutes(allRoutes);
200
214
  if (mode === "export" && errors > 0) {
201
215
  const errorRoutes = allRoutes.filter((r) => r.status === "error").map((r) => ` ${r.route}: ${r.error}`).join("\n");
202
216
  throw new Error(`Static export failed: ${errors} route${errors !== 1 ? "s" : ""} cannot be statically exported.\n${errorRoutes}\n\nRemove server-side data fetching (getServerSideProps, force-dynamic, revalidate) from these routes, or remove \`output: "export"\` from next.config.js.`);
@@ -207,4 +221,4 @@ async function runPrerender(options) {
207
221
  };
208
222
  }
209
223
  //#endregion
210
- export { runPrerender };
224
+ export { assertNoFatalPrerenderRoutes, runPrerender };
@@ -0,0 +1,8 @@
1
+ //#region src/client/app-nav-failure-handler.d.ts
2
+ declare function stageAppNavigationFailureTarget(href: string): void;
3
+ declare function getAppNavigationFailureTarget(href: string): URL | null;
4
+ declare function clearAppNavigationFailureTarget(target?: string | URL): void;
5
+ declare function handleAppNavigationFailure(error: unknown): boolean;
6
+ declare function installAppNavigationFailureListeners(): () => void;
7
+ //#endregion
8
+ export { clearAppNavigationFailureTarget, getAppNavigationFailureTarget, handleAppNavigationFailure, installAppNavigationFailureListeners, stageAppNavigationFailureTarget };
@@ -0,0 +1,44 @@
1
+ import { installWindowNext } from "./window-next.js";
2
+ //#region src/client/app-nav-failure-handler.ts
3
+ function getPendingUrl() {
4
+ if (typeof window === "undefined") return null;
5
+ return window.next?.__pendingUrl ?? null;
6
+ }
7
+ function stageAppNavigationFailureTarget(href) {
8
+ if (!process.env.__NEXT_APP_NAV_FAIL_HANDLING || typeof window === "undefined") return;
9
+ installWindowNext({ __pendingUrl: new URL(href, window.location.href) });
10
+ }
11
+ function getAppNavigationFailureTarget(href) {
12
+ const pendingUrl = getPendingUrl();
13
+ if (pendingUrl === null || typeof window === "undefined") return null;
14
+ return pendingUrl.href === new URL(href, window.location.href).href ? pendingUrl : null;
15
+ }
16
+ function clearAppNavigationFailureTarget(target) {
17
+ if (typeof window === "undefined" || window.next?.__pendingUrl === void 0) return;
18
+ if (target instanceof URL) {
19
+ if (window.next.__pendingUrl !== target) return;
20
+ } else if (target !== void 0 && window.next.__pendingUrl.href !== new URL(target, window.location.href).href) return;
21
+ delete window.next.__pendingUrl;
22
+ }
23
+ function handleAppNavigationFailure(error) {
24
+ if (!process.env.__NEXT_APP_NAV_FAIL_HANDLING || typeof window === "undefined") return false;
25
+ const pendingUrl = getPendingUrl();
26
+ if (pendingUrl === null || pendingUrl.href === window.location.href) return false;
27
+ console.error("Error occurred during navigation, falling back to hard navigation", error);
28
+ window.location.assign(pendingUrl.href);
29
+ return true;
30
+ }
31
+ function installAppNavigationFailureListeners() {
32
+ if (!process.env.__NEXT_APP_NAV_FAIL_HANDLING || typeof window === "undefined") return () => {};
33
+ const listener = (event) => {
34
+ handleAppNavigationFailure("reason" in event ? event.reason : event.error);
35
+ };
36
+ window.addEventListener("error", listener);
37
+ window.addEventListener("unhandledrejection", listener);
38
+ return () => {
39
+ window.removeEventListener("error", listener);
40
+ window.removeEventListener("unhandledrejection", listener);
41
+ };
42
+ }
43
+ //#endregion
44
+ export { clearAppNavigationFailureTarget, getAppNavigationFailureTarget, handleAppNavigationFailure, installAppNavigationFailureListeners, stageAppNavigationFailureTarget };
@@ -3,23 +3,49 @@ type PagesRouterLinkTransitionOptions = {
3
3
  scroll?: boolean;
4
4
  shallow?: boolean;
5
5
  locale?: string | false;
6
+ _vinextInterpolateDynamicRoute?: boolean;
6
7
  };
7
8
  type PagesRouterLinkRuntime = {
8
9
  push(url: string, as?: string, options?: PagesRouterLinkTransitionOptions): Promise<boolean>;
9
10
  replace(url: string, as?: string, options?: PagesRouterLinkTransitionOptions): Promise<boolean>;
10
11
  };
12
+ type PagesRouterLinkNavigation = {
13
+ href: string;
14
+ replace: boolean;
15
+ scroll: boolean;
16
+ shallow?: boolean;
17
+ locale?: string | false;
18
+ interpolateDynamicRoute?: boolean;
19
+ };
20
+ declare function resolvePagesRouterQueryOnlyHref(href: string, {
21
+ asPath,
22
+ basePath,
23
+ fallbackHref,
24
+ locales
25
+ }: {
26
+ asPath?: string;
27
+ basePath: string;
28
+ fallbackHref: string;
29
+ locales?: readonly string[];
30
+ }): string;
11
31
  declare function navigatePagesRouterLink(router: PagesRouterLinkRuntime, {
12
32
  href,
13
33
  replace,
14
34
  scroll,
15
35
  shallow,
16
- locale
36
+ locale,
37
+ interpolateDynamicRoute
38
+ }: PagesRouterLinkNavigation): Promise<void>;
39
+ declare function navigatePagesRouterLinkWithFallback({
40
+ router,
41
+ loadRouter,
42
+ navigation,
43
+ fallback
17
44
  }: {
18
- href: string;
19
- replace: boolean;
20
- scroll: boolean;
21
- shallow?: boolean;
22
- locale?: string | false;
45
+ router?: PagesRouterLinkRuntime;
46
+ loadRouter: () => Promise<PagesRouterLinkRuntime | undefined>;
47
+ navigation: PagesRouterLinkNavigation;
48
+ fallback: () => void;
23
49
  }): Promise<void>;
24
50
  //#endregion
25
- export { navigatePagesRouterLink };
51
+ export { navigatePagesRouterLink, navigatePagesRouterLinkWithFallback, resolvePagesRouterQueryOnlyHref };
@@ -1,12 +1,42 @@
1
+ import { stripBasePath } from "../utils/base-path.js";
2
+ import { getLocalePathPrefix } from "../utils/domain-locale.js";
1
3
  //#region src/client/pages-router-link-navigation.ts
2
- async function navigatePagesRouterLink(router, { href, replace, scroll, shallow, locale }) {
4
+ function resolvePagesRouterQueryOnlyHref(href, { asPath, basePath, fallbackHref, locales }) {
5
+ if (!href.startsWith("?")) return href;
6
+ try {
7
+ const fallbackUrl = new URL(fallbackHref);
8
+ const base = new URL(asPath ?? `${stripBasePath(fallbackUrl.pathname, basePath)}${fallbackUrl.search}${fallbackUrl.hash}`, "http://vinext.local");
9
+ const locale = getLocalePathPrefix(base.pathname, locales);
10
+ if (locale) base.pathname = base.pathname.slice(locale.length + 1) || "/";
11
+ const resolved = new URL(href, base);
12
+ return resolved.href.slice(resolved.origin.length);
13
+ } catch {
14
+ return href;
15
+ }
16
+ }
17
+ async function navigatePagesRouterLink(router, { href, replace, scroll, shallow, locale, interpolateDynamicRoute = false }) {
3
18
  const routerOptions = {
4
19
  scroll,
5
20
  locale
6
21
  };
22
+ if (interpolateDynamicRoute) routerOptions._vinextInterpolateDynamicRoute = true;
7
23
  if (shallow !== void 0) routerOptions.shallow = shallow;
8
24
  if (replace) await router.replace(href, void 0, routerOptions);
9
25
  else await router.push(href, void 0, routerOptions);
10
26
  }
27
+ async function navigatePagesRouterLinkWithFallback({ router, loadRouter, navigation, fallback }) {
28
+ let pagesRouter = router;
29
+ if (!pagesRouter) try {
30
+ pagesRouter = await loadRouter();
31
+ } catch {
32
+ fallback();
33
+ return;
34
+ }
35
+ if (!pagesRouter) {
36
+ fallback();
37
+ return;
38
+ }
39
+ await navigatePagesRouterLink(pagesRouter, navigation);
40
+ }
11
41
  //#endregion
12
- export { navigatePagesRouterLink };
42
+ export { navigatePagesRouterLink, navigatePagesRouterLinkWithFallback, resolvePagesRouterQueryOnlyHref };
@@ -3,6 +3,23 @@ import { NEXT_DATA } from "../shims/internal/utils.js";
3
3
  //#region src/client/vinext-next-data.d.ts
4
4
  type VinextLinkPrefetchRoute = {
5
5
  canPrefetchLoadingShell: boolean;
6
+ documentOnly?: boolean;
7
+ isDynamic: boolean;
8
+ patternParts: string[];
9
+ };
10
+ /**
11
+ * Pages Router route pattern exposed to the client so the App Router's
12
+ * navigation runtime can decide whether a soft-navigated URL should be
13
+ * handled by Pages (hard nav) or App (RSC). Mirrors the public shape of
14
+ * `VinextLinkPrefetchRoute` so a single trie matcher handles both.
15
+ *
16
+ * `canPrefetchLoadingShell` is always `false` for Pages routes — Pages
17
+ * does not have a separate loading boundary and its prefetch surface is
18
+ * `_next/data/<buildId>/<page>.json`.
19
+ */
20
+ type VinextPagesLinkPrefetchRoute = {
21
+ canPrefetchLoadingShell: false;
22
+ documentOnly?: boolean;
6
23
  isDynamic: boolean;
7
24
  patternParts: string[];
8
25
  };
@@ -24,4 +41,4 @@ declare function extractVinextNextDataJson(html: string): string | null;
24
41
  declare function parseVinextNextDataJson(json: string): BrowserVinextNextData;
25
42
  declare function applyVinextLocaleGlobals(target: VinextLocaleGlobalTarget, nextData: VinextNextData): void;
26
43
  //#endregion
27
- export { VinextLinkPrefetchRoute, VinextNextData, applyVinextLocaleGlobals, extractVinextNextDataJson, parseVinextNextDataJson };
44
+ export { VinextLinkPrefetchRoute, VinextNextData, VinextPagesLinkPrefetchRoute, applyVinextLocaleGlobals, extractVinextNextDataJson, parseVinextNextDataJson };
@@ -1,6 +1,8 @@
1
1
  import { isUnknownRecord } from "../utils/record.js";
2
2
  //#region src/client/vinext-next-data.ts
3
3
  function extractVinextNextDataJson(html) {
4
+ const canonical = /<script\b(?=[^>]*\bid=["']__NEXT_DATA__["'])(?=[^>]*\btype=["']application\/json["'])[^>]*>([\s\S]*?)<\/script>/.exec(html);
5
+ if (canonical) return canonical[1];
4
6
  const assignment = /<script(?:\s[^>]*)?>\s*window\.__NEXT_DATA__\s*=\s*/.exec(html);
5
7
  if (!assignment || assignment.index === void 0) return null;
6
8
  let start = assignment.index + assignment[0].length;
@@ -163,5 +163,6 @@ type WindowNext = {
163
163
  * resets state.
164
164
  */
165
165
  declare function installWindowNext(fields: Partial<WindowNext>): void;
166
+ declare function setWindowNextInternalSourcePage(sourcePage: string | null): void;
166
167
  //#endregion
167
- export { PagesRouterPublicInstance, installWindowNext };
168
+ export { PagesRouterPublicInstance, installWindowNext, setWindowNextInternalSourcePage };
@@ -42,5 +42,16 @@ function installWindowNext(fields) {
42
42
  ...fields
43
43
  };
44
44
  }
45
+ function setWindowNextInternalSourcePage(sourcePage) {
46
+ if (typeof window === "undefined") return;
47
+ installWindowNext({});
48
+ if (sourcePage === null) {
49
+ delete window.next?.__internal_src_page;
50
+ return;
51
+ }
52
+ const next = window.next;
53
+ if (!next) return;
54
+ next.__internal_src_page = sourcePage;
55
+ }
45
56
  //#endregion
46
- export { installWindowNext };
57
+ export { installWindowNext, setWindowNextInternalSourcePage };
@@ -73,7 +73,12 @@ var CloudflareCdnCacheAdapter = class {
73
73
  async set(_key, _data, _ctx) {}
74
74
  buildResponseHeaders(input) {
75
75
  if (!input.cacheControl) return { "Cache-Control": NO_STORE };
76
- if (/\b(?:no-store|no-cache|private)\b/.test(input.cacheControl)) return { "Cache-Control": input.cacheControl };
76
+ if (/\b(?:no-store|no-cache|private)\b/.test(input.cacheControl)) return {
77
+ "Cache-Control": input.cacheControl,
78
+ "CDN-Cache-Control": null,
79
+ "Cloudflare-CDN-Cache-Control": null,
80
+ "Cache-Tag": null
81
+ };
77
82
  const headers = {
78
83
  "Cache-Control": BROWSER_REVALIDATE,
79
84
  "CDN-Cache-Control": toEdgeCacheControl(input.cacheControl)
@@ -148,6 +148,16 @@ declare function matchRedirect(pathname: string, redirects: NextRedirect[], ctx:
148
148
  * when evaluating rewrites, so this parameter is required.
149
149
  */
150
150
  declare function matchRewrite(pathname: string, rewrites: NextRewrite[], ctx: RequestContext, basePathState?: BasePathMatchState): string | null;
151
+ /**
152
+ * Check whether a rewrite source can match a pathname without evaluating its
153
+ * request-dependent `has` / `missing` conditions.
154
+ *
155
+ * Dev uses this only as a conservative preflight before middleware runs. The
156
+ * conditions may become true after middleware overrides request headers, so
157
+ * evaluating them against the original request would incorrectly skip the
158
+ * Pages request pipeline for file-looking paths.
159
+ */
160
+ declare function matchesRewriteSource(pathname: string, rewrite: NextRewrite, basePathState?: BasePathMatchState): boolean;
151
161
  /**
152
162
  * Sanitize a redirect/rewrite destination to collapse protocol-relative URLs.
153
163
  *
@@ -244,4 +254,4 @@ declare function applyLocaleToRoutes<T extends NextRedirect | NextRewrite>(route
244
254
  trailingSlash?: boolean;
245
255
  }): T[];
246
256
  //#endregion
247
- export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };
257
+ export { BasePathMatchState, RequestContext, applyLocaleToRoutes, applyMiddlewareRequestHeaders, checkHasConditions, escapeHeaderSource, isExternalUrl, isSafeRegex, matchConfigPattern, matchHeaders, matchRedirect, matchRewrite, matchesRewriteSource, normalizeHost, parseCookies, preserveRedirectDestinationQuery, proxyExternalRequest, requestContextFromRequest, safeRegExp, sanitizeDestination };