vinext 1.0.0-beta.5 → 1.0.0-beta.6

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 (127) hide show
  1. package/dist/build/prerender-paths.js +3 -4
  2. package/dist/build/prerender-phase.d.ts +15 -0
  3. package/dist/build/prerender-phase.js +37 -0
  4. package/dist/build/prerender-server-entry.js +3 -3
  5. package/dist/build/prerender-server-pool.js +2 -0
  6. package/dist/build/prerender.d.ts +6 -2
  7. package/dist/build/prerender.js +93 -17
  8. package/dist/build/run-prerender.js +9 -6
  9. package/dist/client/vinext-next-data.d.ts +2 -0
  10. package/dist/config/next-config.js +24 -7
  11. package/dist/deps/.pnpm/image-size@2.0.2/deps/image-size/dist/index.js +808 -0
  12. package/dist/entries/app-browser-entry.js +2 -1
  13. package/dist/entries/app-rsc-entry.js +25 -0
  14. package/dist/entries/pages-client-entry.js +8 -3
  15. package/dist/index.js +82 -159
  16. package/dist/plugins/pages-node-externals.d.ts +23 -0
  17. package/dist/plugins/pages-node-externals.js +206 -0
  18. package/dist/plugins/transitive-externals.d.ts +24 -0
  19. package/dist/plugins/transitive-externals.js +121 -0
  20. package/dist/plugins/typeof-window.d.ts +11 -1
  21. package/dist/plugins/typeof-window.js +141 -13
  22. package/dist/plugins/use-cache-callable.d.ts +12 -0
  23. package/dist/plugins/use-cache-callable.js +209 -0
  24. package/dist/server/api-handler.js +1 -1
  25. package/dist/server/app-browser-entry.js +5 -1
  26. package/dist/server/app-page-boundary-render.js +4 -7
  27. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  28. package/dist/server/app-page-cache-finalizer.js +5 -9
  29. package/dist/server/app-page-cache-render.d.ts +1 -0
  30. package/dist/server/app-page-cache-render.js +3 -1
  31. package/dist/server/app-page-cache.d.ts +2 -1
  32. package/dist/server/app-page-cache.js +11 -4
  33. package/dist/server/app-page-dispatch.js +3 -2
  34. package/dist/server/app-page-execution.js +19 -6
  35. package/dist/server/app-page-render.js +4 -1
  36. package/dist/server/app-page-response.js +7 -14
  37. package/dist/server/app-page-stream.d.ts +12 -0
  38. package/dist/server/app-page-stream.js +10 -3
  39. package/dist/server/app-prerender-endpoints.d.ts +1 -0
  40. package/dist/server/app-prerender-endpoints.js +12 -0
  41. package/dist/server/app-response-header-provenance.d.ts +13 -0
  42. package/dist/server/app-response-header-provenance.js +26 -0
  43. package/dist/server/app-route-handler-cache.d.ts +1 -0
  44. package/dist/server/app-route-handler-cache.js +2 -2
  45. package/dist/server/app-route-handler-dispatch.js +4 -2
  46. package/dist/server/app-route-handler-execution.js +10 -6
  47. package/dist/server/app-route-handler-policy.d.ts +1 -0
  48. package/dist/server/app-route-handler-response.d.ts +3 -1
  49. package/dist/server/app-route-handler-response.js +15 -5
  50. package/dist/server/app-route-handler-runtime.d.ts +1 -1
  51. package/dist/server/app-route-handler-runtime.js +218 -84
  52. package/dist/server/app-route-request-built-ins.d.ts +16 -0
  53. package/dist/server/app-route-request-built-ins.js +41 -0
  54. package/dist/server/app-router-entry.js +2 -2
  55. package/dist/server/app-rsc-cache-busting.d.ts +2 -0
  56. package/dist/server/app-rsc-cache-busting.js +24 -16
  57. package/dist/server/app-rsc-handler.d.ts +2 -0
  58. package/dist/server/app-rsc-handler.js +13 -11
  59. package/dist/server/app-rsc-response-finalizer.d.ts +2 -0
  60. package/dist/server/app-rsc-response-finalizer.js +4 -1
  61. package/dist/server/app-rsc-state-fingerprint.d.ts +14 -0
  62. package/dist/server/app-rsc-state-fingerprint.js +13 -0
  63. package/dist/server/app-server-action-execution.js +2 -2
  64. package/dist/server/app-ssr-entry.d.ts +6 -0
  65. package/dist/server/app-ssr-entry.js +15 -3
  66. package/dist/server/app-ssr-stream.d.ts +6 -1
  67. package/dist/server/app-ssr-stream.js +28 -4
  68. package/dist/server/cache-control.d.ts +11 -7
  69. package/dist/server/cache-control.js +23 -12
  70. package/dist/server/config-headers.d.ts +4 -0
  71. package/dist/server/config-headers.js +19 -3
  72. package/dist/server/dev-server.js +27 -10
  73. package/dist/server/headers.d.ts +7 -1
  74. package/dist/server/headers.js +9 -2
  75. package/dist/server/http-error-responses.d.ts +1 -1
  76. package/dist/server/http-error-responses.js +7 -2
  77. package/dist/server/metadata-route-build-data.js +1 -1
  78. package/dist/server/metadata-route-cache-policy.d.ts +6 -0
  79. package/dist/server/metadata-route-cache-policy.js +6 -0
  80. package/dist/server/metadata-route-response.d.ts +26 -1
  81. package/dist/server/metadata-route-response.js +219 -6
  82. package/dist/server/metadata-routes.js +1 -1
  83. package/dist/server/middleware-response-headers.js +4 -2
  84. package/dist/server/pages-dev-hydration.js +11 -4
  85. package/dist/server/pages-page-handler.d.ts +3 -1
  86. package/dist/server/pages-page-handler.js +4 -15
  87. package/dist/server/pages-page-response.js +10 -5
  88. package/dist/server/prerender-cache-life-header.d.ts +11 -0
  89. package/dist/server/prerender-cache-life-header.js +18 -0
  90. package/dist/server/prerender-manifest.d.ts +5 -2
  91. package/dist/server/prerender-manifest.js +4 -1
  92. package/dist/server/prod-server.js +6 -10
  93. package/dist/server/request-pipeline.js +2 -0
  94. package/dist/server/seed-cache.d.ts +4 -1
  95. package/dist/server/seed-cache.js +28 -4
  96. package/dist/server/worker-utils.js +1 -1
  97. package/dist/shims/cache-callable-runtime.d.ts +11 -0
  98. package/dist/shims/cache-callable-runtime.js +30 -0
  99. package/dist/shims/cache-request-state.d.ts +4 -1
  100. package/dist/shims/cache-request-state.js +6 -1
  101. package/dist/shims/cache-runtime.d.ts +6 -1
  102. package/dist/shims/cache-runtime.js +114 -24
  103. package/dist/shims/cache.d.ts +2 -2
  104. package/dist/shims/cache.js +2 -2
  105. package/dist/shims/cdn-cache.d.ts +18 -6
  106. package/dist/shims/cdn-cache.js +4 -2
  107. package/dist/shims/fetch-cache.js +4 -3
  108. package/dist/shims/font-local.js +3 -3
  109. package/dist/shims/head.d.ts +16 -1
  110. package/dist/shims/head.js +70 -8
  111. package/dist/shims/internal/app-route-prefetch-policy.d.ts +2 -0
  112. package/dist/shims/internal/app-route-prefetch-policy.js +9 -3
  113. package/dist/shims/internal/pages-data-target.d.ts +1 -1
  114. package/dist/shims/internal/pages-data-target.js +18 -5
  115. package/dist/shims/link.js +49 -54
  116. package/dist/shims/navigation-context-state.d.ts +4 -0
  117. package/dist/shims/navigation.d.ts +28 -1
  118. package/dist/shims/navigation.js +96 -10
  119. package/dist/shims/root-params.d.ts +3 -1
  120. package/dist/shims/root-params.js +7 -1
  121. package/dist/shims/router.js +177 -76
  122. package/dist/shims/server.js +2 -1
  123. package/dist/utils/encode-cache-tag.d.ts +1 -1
  124. package/dist/utils/encode-cache-tag.js +1 -1
  125. package/dist/utils/prerender-output-paths.d.ts +3 -1
  126. package/dist/utils/prerender-output-paths.js +6 -1
  127. package/package.json +4 -4
@@ -9,6 +9,7 @@ import { BLOCKED_PAGES, PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
9
9
  import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
10
10
  import { readPrerenderSecret } from "./server-manifest.js";
11
11
  import { startProdServer } from "../server/prod-server.js";
12
+ import { enterPrerenderPhase } from "./prerender-phase.js";
12
13
  import { buildUrlFromParams, resolveParentParams } from "./prerender.js";
13
14
  import fs from "node:fs";
14
15
  //#region src/build/prerender-paths.ts
@@ -96,15 +97,13 @@ async function shouldStartPathDiscoveryServer(options) {
96
97
  return false;
97
98
  }
98
99
  async function withPrerenderEndpoints(fn) {
99
- const previousPrerenderFlag = process.env.VINEXT_PRERENDER;
100
+ const restorePrerenderPhase = enterPrerenderPhase();
100
101
  const previousPathDiscoveryFlag = process.env[PRERENDER_PATH_DISCOVERY_ENV];
101
- process.env.VINEXT_PRERENDER = "1";
102
102
  process.env[PRERENDER_PATH_DISCOVERY_ENV] = "1";
103
103
  try {
104
104
  return await fn();
105
105
  } finally {
106
- if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
107
- else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
106
+ restorePrerenderPhase();
108
107
  if (previousPathDiscoveryFlag === void 0) delete process.env[PRERENDER_PATH_DISCOVERY_ENV];
109
108
  else process.env[PRERENDER_PATH_DISCOVERY_ENV] = previousPathDiscoveryFlag;
110
109
  }
@@ -0,0 +1,15 @@
1
+ //#region src/build/prerender-phase.d.ts
2
+ type PrerenderPhaseEnv = {
3
+ NEXT_PHASE?: string;
4
+ NODE_ENV?: string;
5
+ VINEXT_PRERENDER?: string;
6
+ };
7
+ /**
8
+ * Enter the same process environment phase that Next.js exposes while its
9
+ * static workers execute application code. The returned cleanup is nest-safe:
10
+ * direct prerender helpers can run inside the CLI's broader build scope without
11
+ * overwriting the caller's prior phase when they finish.
12
+ */
13
+ declare function enterPrerenderPhase(env?: PrerenderPhaseEnv): () => void;
14
+ //#endregion
15
+ export { enterPrerenderPhase };
@@ -0,0 +1,37 @@
1
+ import { PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
2
+ //#region src/build/prerender-phase.ts
3
+ const activePrerenderPhases = /* @__PURE__ */ new WeakMap();
4
+ /**
5
+ * Enter the same process environment phase that Next.js exposes while its
6
+ * static workers execute application code. The returned cleanup is nest-safe:
7
+ * direct prerender helpers can run inside the CLI's broader build scope without
8
+ * overwriting the caller's prior phase when they finish.
9
+ */
10
+ function enterPrerenderPhase(env = process.env) {
11
+ let state = activePrerenderPhases.get(env);
12
+ if (state) state.depth += 1;
13
+ else {
14
+ state = {
15
+ depth: 1,
16
+ previousPrerenderFlag: env.VINEXT_PRERENDER,
17
+ previousNextPhase: env.NEXT_PHASE
18
+ };
19
+ activePrerenderPhases.set(env, state);
20
+ }
21
+ let restored = false;
22
+ env.VINEXT_PRERENDER = "1";
23
+ env.NEXT_PHASE = PHASE_PRODUCTION_BUILD;
24
+ return () => {
25
+ if (restored) return;
26
+ restored = true;
27
+ state.depth -= 1;
28
+ if (state.depth > 0) return;
29
+ activePrerenderPhases.delete(env);
30
+ if (state.previousPrerenderFlag === void 0) delete env.VINEXT_PRERENDER;
31
+ else env.VINEXT_PRERENDER = state.previousPrerenderFlag;
32
+ if (state.previousNextPhase === void 0) delete env.NEXT_PHASE;
33
+ else env.NEXT_PHASE = state.previousNextPhase;
34
+ };
35
+ }
36
+ //#endregion
37
+ export { enterPrerenderPhase };
@@ -11,9 +11,9 @@ import { startProdServer } from "../server/prod-server.js";
11
11
  * vinext production server on an ephemeral port and reports it to the parent
12
12
  * over IPC. The parent then load-balances per-route fetches across the pool.
13
13
  *
14
- * `VINEXT_PRERENDER` / `VINEXT_PRERENDER_OUTDIR` are passed via the fork env so
15
- * they are set before any module loads (some server modules read the flag at
16
- * import time).
14
+ * `VINEXT_PRERENDER` / `NEXT_PHASE` / `VINEXT_PRERENDER_OUTDIR` are passed via
15
+ * the fork env so they are set before any module loads (some server and user
16
+ * modules read the phase at import time).
17
17
  */
18
18
  async function main() {
19
19
  const outDir = process.env.VINEXT_PRERENDER_OUTDIR;
@@ -1,4 +1,5 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
+ import { PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
2
3
  import fs from "node:fs";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import os from "node:os";
@@ -105,6 +106,7 @@ async function startPrerenderServerPool(outDir, size, entry = WORKER_ENTRY) {
105
106
  env: {
106
107
  ...process.env,
107
108
  VINEXT_PRERENDER: "1",
109
+ NEXT_PHASE: PHASE_PRODUCTION_BUILD,
108
110
  VINEXT_PRERENDER_OUTDIR: outDir
109
111
  },
110
112
  stdio: [
@@ -27,11 +27,15 @@ type PrerenderRouteResult = {
27
27
  */
28
28
  path?: string;
29
29
  /** Which router produced this route. Used by cache seeding. */
30
- router: "app" | "pages";
30
+ router: "app" | "pages" | "metadata";
31
31
  /** Response headers that must be replayed with the prerendered artifact. */
32
- headers?: Record<string, string>;
32
+ headers?: Record<string, string | string[]>;
33
+ /** Original HTTP status for prerendered App Route-style responses. */
34
+ responseStatus?: number;
33
35
  /** Cache tags collected while rendering this route. */
34
36
  tags?: string[];
37
+ /** Raw app-tree segments used to derive App Route implicit tags. */
38
+ routeSegments?: string[];
35
39
  /** Set to true when this is a PPR fallback shell. */
36
40
  fallback?: boolean;
37
41
  } | {
@@ -1,7 +1,7 @@
1
1
  import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  import { createValidFileMatcher, findFileWithExtensions } from "../routing/file-matcher.js";
3
3
  import { VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, VINEXT_PRERENDER_SECRET_HEADER, VINEXT_PRERENDER_SPECULATIVE_HEADER } from "../utils/protocol-headers.js";
4
- import { NEXT_CACHE_TAGS_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER } from "../server/headers.js";
4
+ import { NEXT_CACHE_TAGS_HEADER, VINEXT_METADATA_ROUTE_CACHE_HEADER, VINEXT_PRERENDER_CACHE_LIFE_HEADER, VINEXT_PRERENDER_METADATA_ROUTES_PATH } from "../server/headers.js";
5
5
  import { classifyAppRoute, classifyPagesRoute, getAppRouteRenderEntryPath } from "./report.js";
6
6
  import { resolveClientStaleTimeSeconds } from "../utils/cache-control-metadata.js";
7
7
  import { NoOpCacheHandler, getCacheHandler, setCacheHandler } from "../shims/cache-handler.js";
@@ -15,9 +15,12 @@ import { navigationRuntimeRscBootstrapExpression } from "../server/app-ssr-strea
15
15
  import { encodePrerenderRouteParams, serializePrerenderRouteParamsHeader } from "../server/prerender-route-params.js";
16
16
  import { readPrerenderSecret } from "./server-manifest.js";
17
17
  import { createAppPprFallbackShells, markAppPprDynamicFallbackShellHtml } from "../server/app-ppr-fallback-shell.js";
18
- import { getOutputPath, getRscOutputPath } from "../utils/prerender-output-paths.js";
18
+ import { getAppRouteOutputPath, getOutputPath, getRscOutputPath } from "../utils/prerender-output-paths.js";
19
19
  import { startProdServer } from "../server/prod-server.js";
20
20
  import { prerenderPoolAvailable, resolvePrerenderPoolSize, startPrerenderServerPool } from "./prerender-server-pool.js";
21
+ import { enterPrerenderPhase } from "./prerender-phase.js";
22
+ import { buildAppRouteCacheValue } from "../server/app-route-handler-response.js";
23
+ import { isMetadataResponseCacheable } from "../server/metadata-route-cache-policy.js";
21
24
  import fs from "node:fs";
22
25
  import os from "node:os";
23
26
  //#region src/build/prerender.ts
@@ -265,8 +268,7 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
265
268
  });
266
269
  const previousHandler = getCacheHandler();
267
270
  setCacheHandler(new NoOpCacheHandler());
268
- const previousPrerenderFlag = process.env.VINEXT_PRERENDER;
269
- process.env.VINEXT_PRERENDER = "1";
271
+ const restorePrerenderPhase = enterPrerenderPhase();
270
272
  let ownedProdServerHandle = null;
271
273
  let renderPool = null;
272
274
  try {
@@ -497,11 +499,13 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
497
499
  });
498
500
  return { routes: results };
499
501
  } finally {
500
- if (renderPool) await renderPool.close();
501
- setCacheHandler(previousHandler);
502
- if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
503
- else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
504
- if (ownedProdServerHandle) await new Promise((resolve) => ownedProdServerHandle.server.close(() => resolve()));
502
+ try {
503
+ if (renderPool) await renderPool.close();
504
+ setCacheHandler(previousHandler);
505
+ if (ownedProdServerHandle) await new Promise((resolve) => ownedProdServerHandle.server.close(() => resolve()));
506
+ } finally {
507
+ restorePrerenderPhase();
508
+ }
505
509
  }
506
510
  }
507
511
  /**
@@ -530,8 +534,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
530
534
  fs.mkdirSync(outDir, { recursive: true });
531
535
  const previousHandler = getCacheHandler();
532
536
  setCacheHandler(new NoOpCacheHandler());
533
- const previousPrerenderFlag = process.env.VINEXT_PRERENDER;
534
- process.env.VINEXT_PRERENDER = "1";
537
+ const restorePrerenderPhase = enterPrerenderPhase();
535
538
  const serverDir = path.dirname(rscBundlePath);
536
539
  let rscHandler;
537
540
  let staticParamsMap = {};
@@ -743,14 +746,83 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
743
746
  isSpeculative: false
744
747
  });
745
748
  }
749
+ if (metadataRoutes.length > 0) {
750
+ const response = await fetch(`${baseUrl}${VINEXT_PRERENDER_METADATA_ROUTES_PATH}`, { headers: secretHeaders });
751
+ const text = await response.text();
752
+ if (!response.ok) throw new Error(`[vinext] Failed to enumerate prerenderable metadata routes: ${parsePrerenderEndpointError(text)}`);
753
+ const metadataEntries = JSON.parse(text);
754
+ if (!Array.isArray(metadataEntries)) throw new Error("[vinext] Invalid prerender metadata route response");
755
+ const queuedMetadataPaths = /* @__PURE__ */ new Set();
756
+ for (const metadataEntry of metadataEntries) {
757
+ if (typeof metadataEntry !== "object" || metadataEntry === null || typeof Reflect.get(metadataEntry, "path") !== "string" || typeof Reflect.get(metadataEntry, "routePattern") !== "string" || !Array.isArray(Reflect.get(metadataEntry, "routeSegments"))) continue;
758
+ const { path: metadataPath, routePattern, routeSegments } = metadataEntry;
759
+ if (!metadataPath.startsWith("/") || !routePattern.startsWith("/") || routeSegments.some((segment) => typeof segment !== "string") || queuedMetadataPaths.has(metadataPath)) continue;
760
+ queuedMetadataPaths.add(metadataPath);
761
+ urlsToRender.push({
762
+ kind: "metadata",
763
+ metadataRouteSegments: routeSegments,
764
+ urlPath: metadataPath,
765
+ routePattern,
766
+ prerenderRouteParams: null,
767
+ revalidate: false,
768
+ isSpeculative: false
769
+ });
770
+ }
771
+ }
746
772
  /**
747
773
  * Render a single URL and return its result.
748
774
  * `onProgress` is intentionally not called here; the outer loop calls it
749
775
  * exactly once per URL after this function returns, keeping the callback
750
776
  * at a single, predictable call site.
751
777
  */
752
- async function renderUrl({ urlPath, routePattern, prerenderRouteParams, revalidate, isSpeculative, isFallback }) {
778
+ async function renderUrl({ kind, metadataRouteSegments, urlPath, routePattern, prerenderRouteParams, revalidate, isSpeculative, isFallback }) {
753
779
  try {
780
+ if (kind === "metadata") {
781
+ const request = new Request(`http://localhost${config.basePath ?? ""}${urlPath}`);
782
+ const response = await runWithHeadersContext(headersContextFromRequest(request), () => rscHandler(request));
783
+ if (!response.ok) {
784
+ await response.body?.cancel();
785
+ return {
786
+ route: routePattern,
787
+ status: "error",
788
+ error: `Metadata route returned ${response.status}`
789
+ };
790
+ }
791
+ const cacheControl = response.headers.get("cache-control") ?? "";
792
+ if (!isMetadataResponseCacheable(response)) {
793
+ await response.body?.cancel();
794
+ return {
795
+ route: routePattern,
796
+ status: "skipped",
797
+ reason: "dynamic"
798
+ };
799
+ }
800
+ const requestCacheLife = readPrerenderCacheLifeHeader(response.headers);
801
+ const collectedTags = readPrerenderCacheTagsHeader(response.headers);
802
+ const cacheValue = await buildAppRouteCacheValue(response);
803
+ cacheValue.headers[VINEXT_METADATA_ROUTE_CACHE_HEADER] = "1";
804
+ const outputPath = getAppRouteOutputPath(urlPath);
805
+ const fullPath = path.join(outDir, outputPath);
806
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
807
+ fs.writeFileSync(fullPath, new Uint8Array(cacheValue.body));
808
+ const renderedCacheControl = resolveRenderedCacheControl(requestCacheLife ?? {}, cacheControl, config.expireTime);
809
+ const renderedRevalidate = renderedCacheControl.revalidate ?? false;
810
+ const renderedStale = resolveClientStaleTimeSeconds(requestCacheLife ?? void 0);
811
+ return {
812
+ route: routePattern,
813
+ ...urlPath === routePattern ? {} : { path: urlPath },
814
+ status: "rendered",
815
+ outputFiles: [outputPath],
816
+ revalidate: renderedRevalidate,
817
+ ...typeof renderedRevalidate === "number" ? { expire: renderedCacheControl.expire } : {},
818
+ ...renderedStale === void 0 ? {} : { stale: renderedStale },
819
+ router: "metadata",
820
+ headers: cacheValue.headers,
821
+ responseStatus: cacheValue.status,
822
+ routeSegments: metadataRouteSegments ?? [],
823
+ tags: collectedTags
824
+ };
825
+ }
754
826
  const prerenderRouteParamsHeader = serializePrerenderRouteParamsHeader(prerenderRouteParams);
755
827
  const htmlHeaders = new Headers();
756
828
  if (prerenderRouteParamsHeader !== null) htmlHeaders.set(VINEXT_PRERENDER_ROUTE_PARAMS_HEADER, prerenderRouteParamsHeader);
@@ -919,11 +991,13 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
919
991
  ...outputFiles.length > 0 ? { outputFiles } : {}
920
992
  };
921
993
  } finally {
922
- if (renderPool) await renderPool.close();
923
- setCacheHandler(previousHandler);
924
- if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
925
- else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
926
- if (ownedProdServerHandle) await new Promise((resolve) => ownedProdServerHandle.server.close(() => resolve()));
994
+ try {
995
+ if (renderPool) await renderPool.close();
996
+ setCacheHandler(previousHandler);
997
+ if (ownedProdServerHandle) await new Promise((resolve) => ownedProdServerHandle.server.close(() => resolve()));
998
+ } finally {
999
+ restorePrerenderPhase();
1000
+ }
927
1001
  }
928
1002
  }
929
1003
  function resolveRenderedCacheControl(requestCacheLife, cacheControl, fallbackExpireSeconds) {
@@ -991,7 +1065,9 @@ function writePrerenderIndex(routes, outDir, options) {
991
1065
  ...typeof r.stale === "number" ? { stale: r.stale } : {},
992
1066
  router: r.router,
993
1067
  ...r.tags && r.tags.length > 0 ? { tags: r.tags } : {},
1068
+ ...r.routeSegments ? { routeSegments: r.routeSegments } : {},
994
1069
  ...r.headers ? { headers: r.headers } : {},
1070
+ ...typeof r.responseStatus === "number" ? { responseStatus: r.responseStatus } : {},
995
1071
  ...r.path ? { path: r.path } : {},
996
1072
  ...r.fallback ? { fallback: true } : {}
997
1073
  };
@@ -3,9 +3,11 @@ import { findDir } from "../utils/project.js";
3
3
  import { apiRouter, pagesRouter } from "../routing/pages-router.js";
4
4
  import { appRouter } from "../routing/app-router.js";
5
5
  import { scanMetadataFiles } from "../server/metadata-routes.js";
6
+ import { PHASE_PRODUCTION_BUILD } from "../shims/constants.js";
6
7
  import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
7
8
  import { readPrerenderSecret } from "./server-manifest.js";
8
9
  import { rememberCurrentServerEntryImportMtime, startProdServer } from "../server/prod-server.js";
10
+ import { enterPrerenderPhase } from "./prerender-phase.js";
9
11
  import { prerenderApp, prerenderPages, writePrerenderIndex } from "./prerender.js";
10
12
  import { injectPregeneratedConcretePaths } from "./inject-pregenerated-paths.js";
11
13
  import fs from "node:fs";
@@ -104,12 +106,10 @@ async function runPrerender(options) {
104
106
  const appDir = findDir(root, "app", "src/app");
105
107
  const pagesDir = findDir(root, "pages", "src/pages");
106
108
  if (!appDir && !pagesDir) return null;
107
- const previousPrerenderFlag = process.env.VINEXT_PRERENDER;
108
- process.env.VINEXT_PRERENDER = "1";
109
109
  const manifestDir = path.join(root, "dist", "server");
110
110
  const rscBundlePath = options.rscBundlePath ?? path.join(root, "dist", "server", "index.js");
111
111
  const serverDir = path.dirname(rscBundlePath);
112
- const config = options.nextConfig ? { ...options.nextConfig } : { ...await resolveNextConfig(await loadNextConfig(root), root) };
112
+ const config = options.nextConfig ? { ...options.nextConfig } : { ...await resolveNextConfig(await loadNextConfig(root, PHASE_PRODUCTION_BUILD), root) };
113
113
  const builtBuildId = readBuiltBuildId(serverDir);
114
114
  if (builtBuildId) config.buildId = builtBuildId;
115
115
  const mode = config.output === "export" ? "export" : "default";
@@ -121,6 +121,7 @@ async function runPrerender(options) {
121
121
  const outDir = mode === "export" ? path.join(root, "dist", "client") : path.join(root, "dist", "server", "prerendered-routes");
122
122
  let sharedProdServer = null;
123
123
  let sharedPrerenderSecret;
124
+ const restorePrerenderPhase = enterPrerenderPhase();
124
125
  try {
125
126
  if (appDir && pagesDir) {
126
127
  sharedProdServer = await startProdServer({
@@ -187,9 +188,11 @@ async function runPrerender(options) {
187
188
  if (result.outputFiles) allOutputFiles.push(...result.outputFiles);
188
189
  }
189
190
  } finally {
190
- if (sharedProdServer) await new Promise((resolve) => sharedProdServer.server.close(() => resolve()));
191
- if (previousPrerenderFlag === void 0) delete process.env.VINEXT_PRERENDER;
192
- else process.env.VINEXT_PRERENDER = previousPrerenderFlag;
191
+ try {
192
+ if (sharedProdServer) await new Promise((resolve) => sharedProdServer.server.close(() => resolve()));
193
+ } finally {
194
+ restorePrerenderPhase();
195
+ }
193
196
  }
194
197
  if (allRoutes.length === 0) {
195
198
  progress.finish(0, 0, 0);
@@ -6,6 +6,8 @@ type VinextLinkPrefetchRoute = {
6
6
  isDynamic: boolean;
7
7
  patternParts: string[];
8
8
  requiresDynamicNavigationRequest?: boolean;
9
+ /** The route has dynamic params above its root layout. */
10
+ hasRootParams?: true;
9
11
  };
10
12
  /**
11
13
  * Pages Router route pattern exposed to the client so the App Router's
@@ -332,7 +332,11 @@ async function loadNextConfig(root, phase = DEFAULT_PHASE) {
332
332
  const normalizedConfigPath = safeRealpath(path.resolve(configPath));
333
333
  try {
334
334
  const { runnerImport } = await import("vite");
335
- const { module: mod } = await runnerImport(configPath, {
335
+ const virtualConfigId = "virtual:vinext-resolved-next-config";
336
+ const resolvedVirtualConfigId = `\0${virtualConfigId}`;
337
+ const configPathLiteral = JSON.stringify(configPath);
338
+ const phaseLiteral = JSON.stringify(phase);
339
+ const { module: mod } = await runnerImport(virtualConfigId, {
336
340
  root,
337
341
  logLevel: "error",
338
342
  clearScreen: false,
@@ -351,13 +355,26 @@ async function loadNextConfig(root, phase = DEFAULT_PHASE) {
351
355
  ".json"
352
356
  ]
353
357
  },
354
- plugins: [...isTypeScriptConfig ? [cjsGlobalsInjectorPlugin(configPath)] : [], commonjs({ filter: (id) => {
355
- const idPath = id.startsWith("file://") ? fileURLToPath(id) : id.split("?")[0];
356
- if (safeRealpath(path.resolve(idPath)) === normalizedConfigPath) return false;
357
- return id.includes("node_modules") ? true : void 0;
358
- } })]
358
+ plugins: [
359
+ {
360
+ name: "vinext:resolve-next-config-value",
361
+ resolveId(id) {
362
+ if (id === virtualConfigId) return resolvedVirtualConfigId;
363
+ },
364
+ load(id) {
365
+ if (id !== resolvedVirtualConfigId) return;
366
+ return `import * as configModule from ${configPathLiteral};\nconst cjsModule = configModule[${JSON.stringify(VINEXT_CJS_EXPORTS_KEY)}];\nconst cjsInitial = configModule[${JSON.stringify(VINEXT_CJS_INITIAL_KEY)}];\nconst cjsExports = cjsModule && cjsModule.exports;\nconst cjsValue = cjsExports != null && (cjsExports !== cjsInitial || (typeof cjsExports === "object" && Object.keys(cjsExports).length > 0)) ? cjsExports : undefined;\nconst value = cjsValue ?? configModule.default ?? configModule;\nexport default typeof value === "function" ? await value(${phaseLiteral}, { defaultConfig: {} }) : value;\n`;
367
+ }
368
+ },
369
+ ...isTypeScriptConfig ? [cjsGlobalsInjectorPlugin(configPath)] : [],
370
+ commonjs({ filter: (id) => {
371
+ const idPath = id.startsWith("file://") ? fileURLToPath(id) : id.split("?")[0];
372
+ if (safeRealpath(path.resolve(idPath)) === normalizedConfigPath) return false;
373
+ return id.includes("node_modules") ? true : void 0;
374
+ } })
375
+ ]
359
376
  });
360
- return await unwrapConfig(mod, phase);
377
+ return mod.default;
361
378
  } catch (e) {
362
379
  if (isCjsError(e) && (filename.endsWith(".js") || filename.endsWith(".cjs"))) try {
363
380
  return await loadConfigViaRequire(configPath, root, phase);