vinext 0.1.2 → 0.1.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 (199) hide show
  1. package/dist/build/prerender.d.ts +9 -1
  2. package/dist/build/prerender.js +41 -12
  3. package/dist/build/run-prerender.d.ts +10 -2
  4. package/dist/build/run-prerender.js +15 -1
  5. package/dist/client/app-nav-failure-handler.d.ts +8 -0
  6. package/dist/client/app-nav-failure-handler.js +44 -0
  7. package/dist/client/vinext-next-data.d.ts +18 -1
  8. package/dist/client/window-next.d.ts +2 -1
  9. package/dist/client/window-next.js +12 -1
  10. package/dist/cloudflare/src/cache/cdn-adapter.runtime.js +6 -1
  11. package/dist/config/config-matchers.js +73 -14
  12. package/dist/config/next-config.d.ts +46 -4
  13. package/dist/config/next-config.js +147 -48
  14. package/dist/deploy.d.ts +30 -11
  15. package/dist/deploy.js +180 -99
  16. package/dist/entries/app-browser-entry.d.ts +9 -3
  17. package/dist/entries/app-browser-entry.js +21 -3
  18. package/dist/entries/app-rsc-entry.d.ts +2 -0
  19. package/dist/entries/app-rsc-entry.js +64 -5
  20. package/dist/entries/app-rsc-manifest.js +2 -0
  21. package/dist/entries/app-ssr-entry.js +1 -1
  22. package/dist/entries/pages-client-entry.js +53 -8
  23. package/dist/entries/pages-server-entry.js +41 -5
  24. package/dist/index.js +200 -62
  25. package/dist/plugins/extensionless-dynamic-import.d.ts +6 -0
  26. package/dist/plugins/extensionless-dynamic-import.js +152 -0
  27. package/dist/plugins/optimize-imports.d.ts +2 -1
  28. package/dist/plugins/optimize-imports.js +11 -9
  29. package/dist/plugins/postcss.js +7 -7
  30. package/dist/plugins/typeof-window.d.ts +14 -0
  31. package/dist/plugins/typeof-window.js +150 -0
  32. package/dist/routing/app-route-graph.d.ts +2 -1
  33. package/dist/routing/app-route-graph.js +44 -14
  34. package/dist/routing/file-matcher.d.ts +10 -1
  35. package/dist/routing/file-matcher.js +22 -1
  36. package/dist/routing/pages-router.js +3 -3
  37. package/dist/routing/utils.d.ts +35 -6
  38. package/dist/routing/utils.js +59 -7
  39. package/dist/server/api-handler.d.ts +6 -1
  40. package/dist/server/api-handler.js +21 -15
  41. package/dist/server/app-browser-action-result.d.ts +19 -6
  42. package/dist/server/app-browser-action-result.js +19 -10
  43. package/dist/server/app-browser-entry.js +167 -90
  44. package/dist/server/app-browser-error.d.ts +10 -6
  45. package/dist/server/app-browser-error.js +43 -8
  46. package/dist/server/app-browser-hydration.d.ts +2 -0
  47. package/dist/server/app-browser-hydration.js +1 -0
  48. package/dist/server/app-browser-navigation-controller.d.ts +4 -2
  49. package/dist/server/app-browser-navigation-controller.js +23 -2
  50. package/dist/server/app-browser-server-action-navigation.d.ts +6 -0
  51. package/dist/server/app-browser-server-action-navigation.js +9 -0
  52. package/dist/server/app-browser-stream.js +86 -43
  53. package/dist/server/app-elements-wire.d.ts +6 -1
  54. package/dist/server/app-elements-wire.js +14 -4
  55. package/dist/server/app-elements.d.ts +2 -2
  56. package/dist/server/app-elements.js +2 -2
  57. package/dist/server/app-fallback-renderer.d.ts +1 -0
  58. package/dist/server/app-fallback-renderer.js +3 -1
  59. package/dist/server/app-optimistic-routing.js +2 -2
  60. package/dist/server/app-page-boundary-render.d.ts +1 -0
  61. package/dist/server/app-page-boundary-render.js +27 -14
  62. package/dist/server/app-page-cache-render.d.ts +53 -0
  63. package/dist/server/app-page-cache-render.js +91 -0
  64. package/dist/server/app-page-cache.d.ts +16 -2
  65. package/dist/server/app-page-cache.js +62 -1
  66. package/dist/server/app-page-dispatch.d.ts +26 -0
  67. package/dist/server/app-page-dispatch.js +149 -92
  68. package/dist/server/app-page-element-builder.d.ts +1 -0
  69. package/dist/server/app-page-element-builder.js +5 -2
  70. package/dist/server/app-page-execution.d.ts +6 -1
  71. package/dist/server/app-page-execution.js +21 -1
  72. package/dist/server/app-page-probe.d.ts +1 -0
  73. package/dist/server/app-page-probe.js +4 -0
  74. package/dist/server/app-page-render-observation.d.ts +3 -1
  75. package/dist/server/app-page-render-observation.js +17 -1
  76. package/dist/server/app-page-render.d.ts +12 -1
  77. package/dist/server/app-page-render.js +42 -4
  78. package/dist/server/app-page-request.d.ts +2 -0
  79. package/dist/server/app-page-request.js +2 -1
  80. package/dist/server/app-page-route-wiring.d.ts +3 -1
  81. package/dist/server/app-page-route-wiring.js +14 -5
  82. package/dist/server/app-page-stream.d.ts +15 -3
  83. package/dist/server/app-page-stream.js +11 -5
  84. package/dist/server/app-pages-bridge.d.ts +18 -0
  85. package/dist/server/app-pages-bridge.js +22 -5
  86. package/dist/server/app-ppr-fallback-shell-render.d.ts +17 -0
  87. package/dist/server/app-ppr-fallback-shell-render.js +26 -0
  88. package/dist/server/app-ppr-fallback-shell.d.ts +13 -1
  89. package/dist/server/app-ppr-fallback-shell.js +8 -1
  90. package/dist/server/app-route-handler-dispatch.js +9 -2
  91. package/dist/server/app-route-handler-policy.d.ts +1 -0
  92. package/dist/server/app-router-entry.js +5 -0
  93. package/dist/server/app-rsc-cache-busting.js +2 -0
  94. package/dist/server/app-rsc-handler.d.ts +25 -0
  95. package/dist/server/app-rsc-handler.js +154 -54
  96. package/dist/server/app-rsc-route-matching.d.ts +3 -0
  97. package/dist/server/app-rsc-route-matching.js +2 -0
  98. package/dist/server/app-segment-config.d.ts +9 -1
  99. package/dist/server/app-segment-config.js +12 -3
  100. package/dist/server/app-server-action-execution.d.ts +1 -0
  101. package/dist/server/app-server-action-execution.js +42 -13
  102. package/dist/server/app-ssr-entry.d.ts +2 -0
  103. package/dist/server/app-ssr-entry.js +83 -10
  104. package/dist/server/cache-control.js +4 -0
  105. package/dist/server/dev-server.d.ts +2 -2
  106. package/dist/server/dev-server.js +244 -51
  107. package/dist/server/hybrid-route-priority.d.ts +22 -0
  108. package/dist/server/hybrid-route-priority.js +33 -0
  109. package/dist/server/image-optimization.d.ts +18 -9
  110. package/dist/server/image-optimization.js +37 -23
  111. package/dist/server/implicit-tags.d.ts +2 -1
  112. package/dist/server/implicit-tags.js +4 -1
  113. package/dist/server/navigation-planner.d.ts +133 -30
  114. package/dist/server/navigation-planner.js +114 -0
  115. package/dist/server/navigation-trace.d.ts +8 -1
  116. package/dist/server/navigation-trace.js +8 -1
  117. package/dist/server/pages-api-route.d.ts +6 -0
  118. package/dist/server/pages-api-route.js +13 -2
  119. package/dist/server/pages-asset-tags.d.ts +2 -1
  120. package/dist/server/pages-asset-tags.js +6 -2
  121. package/dist/server/pages-data-route.d.ts +8 -1
  122. package/dist/server/pages-data-route.js +11 -2
  123. package/dist/server/pages-get-initial-props.d.ts +54 -4
  124. package/dist/server/pages-get-initial-props.js +43 -1
  125. package/dist/server/pages-node-compat.js +2 -2
  126. package/dist/server/pages-page-data.d.ts +11 -2
  127. package/dist/server/pages-page-data.js +204 -33
  128. package/dist/server/pages-page-handler.d.ts +4 -2
  129. package/dist/server/pages-page-handler.js +59 -22
  130. package/dist/server/pages-page-response.d.ts +2 -1
  131. package/dist/server/pages-page-response.js +7 -4
  132. package/dist/server/pages-request-pipeline.d.ts +1 -0
  133. package/dist/server/pages-request-pipeline.js +73 -36
  134. package/dist/server/pregenerated-concrete-paths.d.ts +1 -17
  135. package/dist/server/pregenerated-concrete-paths.js +2 -19
  136. package/dist/server/prerender-manifest.d.ts +33 -0
  137. package/dist/server/prerender-manifest.js +54 -0
  138. package/dist/server/prerender-route-params.d.ts +1 -2
  139. package/dist/server/prod-server.js +9 -3
  140. package/dist/server/request-pipeline.d.ts +3 -15
  141. package/dist/server/request-pipeline.js +58 -47
  142. package/dist/server/rsc-stream-hints.d.ts +5 -1
  143. package/dist/server/rsc-stream-hints.js +6 -1
  144. package/dist/server/seed-cache.js +10 -18
  145. package/dist/shims/app-router-scroll-state.d.ts +3 -1
  146. package/dist/shims/app-router-scroll-state.js +14 -2
  147. package/dist/shims/app-router-scroll.d.ts +3 -0
  148. package/dist/shims/app-router-scroll.js +28 -18
  149. package/dist/shims/cache-runtime.js +3 -2
  150. package/dist/shims/cache.d.ts +1 -0
  151. package/dist/shims/cache.js +1 -1
  152. package/dist/shims/cdn-cache.d.ts +5 -5
  153. package/dist/shims/dynamic-preload-chunks.js +6 -4
  154. package/dist/shims/error-boundary.d.ts +2 -0
  155. package/dist/shims/error-boundary.js +7 -0
  156. package/dist/shims/error.js +3 -2
  157. package/dist/shims/error.react-server.d.ts +9 -0
  158. package/dist/shims/error.react-server.js +6 -0
  159. package/dist/shims/fetch-cache.d.ts +3 -1
  160. package/dist/shims/fetch-cache.js +45 -20
  161. package/dist/shims/hash-scroll.js +6 -1
  162. package/dist/shims/headers.js +29 -4
  163. package/dist/shims/internal/als-registry.js +28 -1
  164. package/dist/shims/internal/app-route-detection.js +8 -17
  165. package/dist/shims/internal/hybrid-client-route-owner.d.ts +31 -0
  166. package/dist/shims/internal/hybrid-client-route-owner.js +143 -0
  167. package/dist/shims/internal/navigation-untracked.d.ts +35 -0
  168. package/dist/shims/internal/navigation-untracked.js +55 -0
  169. package/dist/shims/internal/pages-data-target.d.ts +7 -2
  170. package/dist/shims/internal/pages-data-target.js +17 -8
  171. package/dist/shims/internal/pages-router-accessor.d.ts +19 -0
  172. package/dist/shims/internal/pages-router-accessor.js +13 -0
  173. package/dist/shims/internal/router-context.d.ts +2 -1
  174. package/dist/shims/internal/router-context.js +3 -1
  175. package/dist/shims/link.js +12 -5
  176. package/dist/shims/navigation.d.ts +8 -2
  177. package/dist/shims/navigation.js +61 -31
  178. package/dist/shims/ppr-fallback-shell.d.ts +5 -1
  179. package/dist/shims/ppr-fallback-shell.js +28 -7
  180. package/dist/shims/router.d.ts +13 -2
  181. package/dist/shims/router.js +419 -128
  182. package/dist/shims/server.d.ts +16 -1
  183. package/dist/shims/server.js +44 -12
  184. package/dist/shims/unified-request-context.js +1 -0
  185. package/dist/utils/built-asset-url.d.ts +4 -0
  186. package/dist/utils/built-asset-url.js +11 -0
  187. package/dist/utils/commonjs-loader.d.ts +16 -0
  188. package/dist/utils/commonjs-loader.js +100 -0
  189. package/dist/utils/deployment-id.d.ts +8 -0
  190. package/dist/utils/deployment-id.js +22 -0
  191. package/dist/utils/html-limited-bots.d.ts +18 -1
  192. package/dist/utils/html-limited-bots.js +23 -1
  193. package/dist/utils/parse-cookie.d.ts +13 -0
  194. package/dist/utils/parse-cookie.js +52 -0
  195. package/dist/utils/path.d.ts +7 -1
  196. package/dist/utils/path.js +9 -1
  197. package/package.json +2 -2
  198. package/dist/shims/internal/parse-cookie-header.d.ts +0 -14
  199. package/dist/shims/internal/parse-cookie-header.js +0 -30
@@ -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
  }
@@ -642,7 +667,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
642
667
  revalidate,
643
668
  isSpeculative: false
644
669
  });
645
- if (config.cacheComponents === true) for (const fallbackShell of createAppPprFallbackShells(route, params)) {
670
+ if (config.cacheComponents === true && isExperimentalPprFallbackShellGenerationEnabled()) for (const fallbackShell of createAppPprFallbackShells(route, params)) {
646
671
  if (queuedRouteUrls.has(fallbackShell.pathname)) continue;
647
672
  queuedRouteUrls.add(fallbackShell.pathname);
648
673
  urlsToRender.push({
@@ -650,7 +675,8 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
650
675
  routePattern: route.pattern,
651
676
  prerenderRouteParams: encodePrerenderRouteParams(route.pattern, fallbackShell.params, fallbackShell.fallbackParamNames),
652
677
  revalidate,
653
- isSpeculative: false
678
+ isSpeculative: false,
679
+ isFallback: true
654
680
  });
655
681
  }
656
682
  }
@@ -660,7 +686,8 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
660
686
  results.push({
661
687
  route: route.pattern,
662
688
  status: "error",
663
- error: `Failed to call generateStaticParams(): ${detail}`
689
+ error: `Failed to call generateStaticParams(): ${detail}`,
690
+ ...e instanceof PrerenderUserFunctionError ? { fatal: true } : {}
664
691
  });
665
692
  }
666
693
  else if (type === "unknown") urlsToRender.push({
@@ -684,7 +711,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
684
711
  * exactly once per URL after this function returns, keeping the callback
685
712
  * at a single, predictable call site.
686
713
  */
687
- async function renderUrl({ urlPath, routePattern, prerenderRouteParams, revalidate, isSpeculative }) {
714
+ async function renderUrl({ urlPath, routePattern, prerenderRouteParams, revalidate, isSpeculative, isFallback }) {
688
715
  try {
689
716
  const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParams);
690
717
  const htmlHeaders = new Headers();
@@ -736,7 +763,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
736
763
  status: "error",
737
764
  error: "RSC handler returned no prerender HTML"
738
765
  };
739
- const html = htmlRender.html;
766
+ const html = isFallback ? markAppPprDynamicFallbackShellHtml(htmlRender.html) : htmlRender.html;
740
767
  let rscData = extractRscPayloadFromPrerenderedHtml(html);
741
768
  if (rscData === null) {
742
769
  const rscHeaders = new Headers({
@@ -772,7 +799,8 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
772
799
  revalidate: renderedRevalidate,
773
800
  ...typeof renderedRevalidate === "number" ? { expire: renderedCacheControl.expire } : {},
774
801
  router: "app",
775
- ...urlPath !== routePattern ? { path: urlPath } : {}
802
+ ...urlPath !== routePattern ? { path: urlPath } : {},
803
+ ...isFallback ? { fallback: true } : {}
776
804
  };
777
805
  } catch (e) {
778
806
  if (isSpeculative) return {
@@ -878,7 +906,8 @@ function writePrerenderIndex(routes, outDir, options) {
878
906
  revalidate: r.revalidate,
879
907
  ...typeof r.revalidate === "number" ? { expire: r.expire } : {},
880
908
  router: r.router,
881
- ...r.path ? { path: r.path } : {}
909
+ ...r.path ? { path: r.path } : {},
910
+ ...r.fallback ? { fallback: true } : {}
882
911
  };
883
912
  if (r.status === "skipped") return {
884
913
  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,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 };
@@ -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)
@@ -1,5 +1,6 @@
1
1
  import { VINEXT_MW_CTX_HEADER, VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER } from "../server/headers.js";
2
2
  import { buildRequestHeadersFromMiddlewareResponse } from "../server/middleware-request-headers.js";
3
+ import { parseCookieHeader } from "../utils/parse-cookie.js";
3
4
  //#region src/config/config-matchers.ts
4
5
  /**
5
6
  * Cache for compiled regex patterns in matchConfigPattern.
@@ -343,16 +344,7 @@ function shouldEvaluateRule(ruleBasePath, state) {
343
344
  * Parse a Cookie header string into a key-value record.
344
345
  */
345
346
  function parseCookies(cookieHeader) {
346
- if (!cookieHeader) return {};
347
- const cookies = {};
348
- for (const part of cookieHeader.split(";")) {
349
- const eq = part.indexOf("=");
350
- if (eq === -1) continue;
351
- const key = part.slice(0, eq).trim();
352
- const value = part.slice(eq + 1).trim();
353
- if (key) cookies[key] = value;
354
- }
355
- return cookies;
347
+ return parseCookieHeader(cookieHeader);
356
348
  }
357
349
  /**
358
350
  * Build a RequestContext from a Web Request object.
@@ -442,8 +434,8 @@ function matchSingleCondition(condition, ctx) {
442
434
  return _matchConditionValue(headerValue, condition.value);
443
435
  }
444
436
  case "cookie": {
437
+ if (!Object.hasOwn(ctx.cookies, condition.key)) return null;
445
438
  const cookieValue = ctx.cookies[condition.key];
446
- if (cookieValue === void 0) return null;
447
439
  return _matchConditionValue(cookieValue, condition.value);
448
440
  }
449
441
  case "query": {
@@ -541,7 +533,7 @@ function matchConfigPattern(pathname, pattern) {
541
533
  pathname = stripTrailingSlashForConfigMatch(pathname);
542
534
  const catchAllAnchor = /:[\w-]+[*+]/.test(pattern);
543
535
  const namedParamCount = (pattern.match(/:[\w-]+/g) || []).length;
544
- if (pattern.includes("(") || pattern.includes("\\") || /:[\w-]+[*+][^/]/.test(pattern) || /:[\w-]+\./.test(pattern) || catchAllAnchor && namedParamCount > 1) try {
536
+ if (pattern.includes("(") || pattern.includes("\\") || /:[\w-]+[*+][^/]/.test(pattern) || /:[\w-]+\./.test(pattern) || /[^/]:[\w-]+/.test(pattern) || catchAllAnchor && namedParamCount > 1) try {
545
537
  const compiled = getCachedRegex(_compiledPatternCache, pattern, () => {
546
538
  const paramNames = [];
547
539
  let regexStr = "";
@@ -716,10 +708,11 @@ function matchRewrite(pathname, rewrites, ctx, basePathState = _BASEPATH_DEFAULT
716
708
  if (params) {
717
709
  const conditionParams = rewrite.has || rewrite.missing ? collectConditionParams(rewrite.has, rewrite.missing, ctx) : _emptyParams();
718
710
  if (!conditionParams) continue;
719
- return substituteAndSanitizeDestination(rewrite.destination, {
711
+ const rewriteParams = {
720
712
  ...params,
721
713
  ...conditionParams
722
- });
714
+ };
715
+ return substituteAndSanitizeRewriteDestination(rewrite.destination, rewriteParams);
723
716
  }
724
717
  }
725
718
  return null;
@@ -753,6 +746,72 @@ function substituteAndSanitizeDestination(destination, params) {
753
746
  return sanitizeDestination(substituteDestinationParams(destination, params));
754
747
  }
755
748
  /**
749
+ * Match Next.js's rewrite-specific prepareDestination behavior: source params
750
+ * that are not consumed by the destination path/host are exposed to the target
751
+ * page through query.
752
+ *
753
+ * https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/prepare-destination.ts
754
+ */
755
+ function substituteAndSanitizeRewriteDestination(destination, params) {
756
+ const rewritten = substituteAndSanitizeDestination(destination, params);
757
+ if (!shouldAppendRewriteParamsToQuery(destination, params)) return rewritten;
758
+ const existingQueryKeys = getDestinationQueryKeys(destination);
759
+ const paramsToAppend = [];
760
+ for (const [key, value] of Object.entries(params)) {
761
+ if (key === "nextInternalLocale" || existingQueryKeys.has(key)) continue;
762
+ paramsToAppend.push([key, value]);
763
+ }
764
+ if (paramsToAppend.length === 0) return rewritten;
765
+ return appendQueryParams(rewritten, paramsToAppend);
766
+ }
767
+ function shouldAppendRewriteParamsToQuery(destination, params) {
768
+ const keys = Object.keys(params).filter((key) => key !== "nextInternalLocale");
769
+ if (keys.length === 0) return false;
770
+ return !destinationPathOrHostUsesParam(destination, keys);
771
+ }
772
+ function destinationPathOrHostUsesParam(destination, keys) {
773
+ const pathAndHost = getDestinationPathAndHost(destination);
774
+ if (!pathAndHost) return false;
775
+ for (const key of keys) {
776
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
777
+ if (new RegExp(`:${escapedKey}([+*])?(?![A-Za-z0-9_])`).test(pathAndHost)) return true;
778
+ }
779
+ return false;
780
+ }
781
+ function getDestinationPathAndHost(destination) {
782
+ const hashIndex = destination.indexOf("#");
783
+ const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
784
+ const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
785
+ const queryIndex = beforeHash.indexOf("?");
786
+ const beforeQuery = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
787
+ const schemeMatch = /^[a-z][a-z0-9+.-]*:\/\//i.exec(beforeQuery);
788
+ if (!schemeMatch) return `${beforeQuery}${hash}`;
789
+ const withoutScheme = beforeQuery.slice(schemeMatch[0].length);
790
+ const slashIndex = withoutScheme.indexOf("/");
791
+ if (slashIndex === -1) return `${withoutScheme}${hash}`;
792
+ return `${withoutScheme.slice(0, slashIndex)}${withoutScheme.slice(slashIndex)}${hash}`;
793
+ }
794
+ function getDestinationQueryKeys(destination) {
795
+ const hashIndex = destination.indexOf("#");
796
+ const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
797
+ const queryIndex = beforeHash.indexOf("?");
798
+ if (queryIndex === -1) return /* @__PURE__ */ new Set();
799
+ const query = beforeHash.slice(queryIndex + 1);
800
+ return new Set(new URLSearchParams(query).keys());
801
+ }
802
+ function appendQueryParams(url, params) {
803
+ const hashIndex = url.indexOf("#");
804
+ const beforeHash = hashIndex === -1 ? url : url.slice(0, hashIndex);
805
+ const hash = hashIndex === -1 ? "" : url.slice(hashIndex);
806
+ const queryIndex = beforeHash.indexOf("?");
807
+ const base = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);
808
+ const query = queryIndex === -1 ? "" : beforeHash.slice(queryIndex + 1);
809
+ const merged = new URLSearchParams(query);
810
+ for (const [key, value] of params) merged.append(key, value);
811
+ const search = merged.toString();
812
+ return `${base}${search ? `?${search}` : ""}${hash}`;
813
+ }
814
+ /**
756
815
  * Sanitize a redirect/rewrite destination to collapse protocol-relative URLs.
757
816
  *
758
817
  * After parameter substitution, a destination like `/:path*` can become
@@ -117,14 +117,20 @@ type NextConfig = {
117
117
  domains?: string[];
118
118
  unoptimized?: boolean; /** Allowed device widths for image optimization. Defaults to Next.js defaults: [640, 750, 828, 1080, 1200, 1920, 2048, 3840] */
119
119
  deviceSizes?: number[]; /** Allowed image sizes for fixed-width images. Defaults to Next.js defaults: [16, 32, 48, 64, 96, 128, 256, 384] */
120
- imageSizes?: number[]; /** Allow SVG images through the image optimization endpoint. SVG can contain scripts, so only enable if you trust all image sources. */
120
+ imageSizes?: number[]; /** Allowed image qualities. When unset, any quality from 1-100 is permitted (matches Next.js). */
121
+ qualities?: number[]; /** Allow SVG images through the image optimization endpoint. SVG can contain scripts, so only enable if you trust all image sources. */
121
122
  dangerouslyAllowSVG?: boolean; /** Allow image optimization for hostnames that resolve to private IP addresses. This is a security risk (SSRF) — only enable for private networks when you understand the risk. */
122
123
  dangerouslyAllowLocalIP?: boolean; /** Content-Disposition header for image responses. Defaults to "inline". */
123
124
  contentDispositionType?: "inline" | "attachment"; /** Content-Security-Policy header for image responses. Defaults to "script-src 'none'; frame-src 'none'; sandbox;" */
124
125
  contentSecurityPolicy?: string;
125
126
  }; /** Build output mode: 'export' for full static export, 'standalone' for single server */
126
127
  output?: "export" | "standalone"; /** File extensions treated as routable pages/routes (Next.js pageExtensions) */
127
- pageExtensions?: string[];
128
+ pageExtensions?: string[]; /** Turbopack-compatible module resolution options. */
129
+ turbopack?: {
130
+ resolveAlias?: Record<string, unknown>;
131
+ resolveExtensions?: string[];
132
+ [key: string]: unknown;
133
+ };
128
134
  /**
129
135
  * Module specifiers that are required for side effects on the client before
130
136
  * hydration, in array order, ahead of the user's `instrumentation-client.{ts,js}`.
@@ -182,6 +188,7 @@ type NextConfig = {
182
188
  defineServer?: Record<string, string | number | boolean>;
183
189
  };
184
190
  experimental?: {
191
+ /** Enables hard-navigation recovery when App Router navigation rendering fails. */appNavFailHandling?: boolean;
185
192
  /**
186
193
  * Enables the experimental App Router gesture transition API:
187
194
  * `useRouter().experimental_gesturePush()`.
@@ -236,8 +243,11 @@ type ResolvedNextConfig = {
236
243
  trailingSlash: boolean;
237
244
  output: "" | "export" | "standalone";
238
245
  pageExtensions: string[];
246
+ resolveExtensions: string[] | null;
247
+ serverResolveExtensions: string[] | null;
239
248
  instrumentationClientInject: string[];
240
249
  cacheComponents: boolean;
250
+ appNavFailHandling: boolean;
241
251
  /**
242
252
  * Enables the experimental App Router gesture transition API:
243
253
  * `useRouter().experimental_gesturePush()`.
@@ -337,6 +347,13 @@ type ResolvedNextConfig = {
337
347
  * `test/e2e/optimized-loading` test fixture.
338
348
  */
339
349
  disableOptimizedLoading: boolean;
350
+ /**
351
+ * Mirrors Next.js `experimental.scrollRestoration`. When true, the Pages
352
+ * Router client takes ownership of browser history scroll restoration by
353
+ * setting `window.history.scrollRestoration = "manual"` and snapshotting
354
+ * scroll positions per history entry.
355
+ */
356
+ scrollRestoration: boolean;
340
357
  /**
341
358
  * Build-time constant replacement map applied to BOTH client and server
342
359
  * bundles. Sourced from `compiler.define` in next.config. Values are
@@ -378,6 +395,28 @@ type ResolvedNextConfig = {
378
395
  dynamic: number;
379
396
  static: number;
380
397
  };
398
+ /**
399
+ * Mirrors Next.js `experimental.useLightningcss`. When `true`, switch
400
+ * Vite's CSS pipeline from PostCSS to lightningcss for both transforms
401
+ * and minification, so the user's `lightningCssFeatures` config takes
402
+ * effect (without this flag set, Next.js's own
403
+ * `lightningCssFeatures` option is also a no-op).
404
+ *
405
+ * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/useLightningcss
406
+ */
407
+ useLightningcss: boolean;
408
+ /**
409
+ * Resolved `experimental.lightningCssFeatures` from next.config, converted
410
+ * from dash-case feature names into the numeric bitmask form expected by
411
+ * the lightningcss `transform()` API (`include` / `exclude` options). When
412
+ * the user did not supply the option, both masks are `0` (a no-op).
413
+ *
414
+ * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/lightningCssFeatures
415
+ */
416
+ lightningCssFeatures: {
417
+ include: number;
418
+ exclude: number;
419
+ };
381
420
  };
382
421
  /**
383
422
  * Whole-word substring check for any of the CJS-style globals that the
@@ -457,11 +496,14 @@ declare function normalizeAssetPrefix(value: unknown): string;
457
496
  * resolve it once and share it — see `__VINEXT_SHARED_RSC_COMPATIBILITY_ID`.
458
497
  */
459
498
  declare function createRscCompatibilityId(nextConfig: Pick<ResolvedNextConfig, "deploymentId">): string;
499
+ declare function lightningCssFeatureNamesToMask(names: readonly string[]): number;
460
500
  /**
461
501
  * Resolve a NextConfig into a fully-resolved ResolvedNextConfig.
462
502
  * Awaits async functions for redirects/rewrites/headers.
463
503
  */
464
- declare function resolveNextConfig(config: NextConfig | null, root?: string): Promise<ResolvedNextConfig>;
504
+ declare function resolveNextConfig(config: NextConfig | null, root?: string, options?: {
505
+ dev?: boolean;
506
+ }): Promise<ResolvedNextConfig>;
465
507
  /**
466
508
  * Extract MDX compilation options (remark/rehype/recma plugins) from
467
509
  * a Next.js config that uses @next/mdx.
@@ -489,4 +531,4 @@ declare function extractMdxOptions(config: NextConfig, root?: string): Promise<M
489
531
  */
490
532
  declare function detectNextIntlConfig(root: string, resolved: ResolvedNextConfig): void;
491
533
  //#endregion
492
- export { HasCondition, MdxOptions, NextConfig, NextConfigInput, NextHeader, NextI18nConfig, NextRedirect, NextRewrite, PHASE_PRODUCTION_BUILD, ResolvedNextConfig, createRscCompatibilityId, detectNextIntlConfig, extractMdxOptions, findNextConfigPath, loadNextConfig, normalizeAssetPrefix, parseBodySizeLimit, reassignsModuleExports, referencesCjsGlobals, resolveNextConfig, resolveNextConfigInput };
534
+ export { HasCondition, MdxOptions, NextConfig, NextConfigInput, NextHeader, NextI18nConfig, NextRedirect, NextRewrite, PHASE_PRODUCTION_BUILD, ResolvedNextConfig, createRscCompatibilityId, detectNextIntlConfig, extractMdxOptions, findNextConfigPath, lightningCssFeatureNamesToMask, loadNextConfig, normalizeAssetPrefix, parseBodySizeLimit, reassignsModuleExports, referencesCjsGlobals, resolveNextConfig, resolveNextConfigInput };