vinext 0.1.5 → 0.1.7

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 (93) hide show
  1. package/README.md +2 -2
  2. package/dist/build/client-build-config.d.ts +4 -6
  3. package/dist/build/client-build-config.js +27 -7
  4. package/dist/build/google-fonts/find-font-files-in-css.d.ts +16 -0
  5. package/dist/build/google-fonts/find-font-files-in-css.js +28 -0
  6. package/dist/build/prerender.js +1 -2
  7. package/dist/build/report.d.ts +1 -2
  8. package/dist/build/report.js +2 -11
  9. package/dist/build/run-prerender.js +1 -1
  10. package/dist/check.js +3 -3
  11. package/dist/client/pages-router-link-navigation.d.ts +10 -0
  12. package/dist/client/pages-router-link-navigation.js +3 -3
  13. package/dist/deploy.js +3 -3
  14. package/dist/entries/app-rsc-entry.d.ts +2 -1
  15. package/dist/entries/app-rsc-entry.js +35 -9
  16. package/dist/entries/app-rsc-manifest.js +76 -46
  17. package/dist/entries/pages-server-entry.js +1 -0
  18. package/dist/index.js +116 -74
  19. package/dist/init.d.ts +5 -4
  20. package/dist/init.js +27 -8
  21. package/dist/plugins/fonts.js +3 -1
  22. package/dist/plugins/middleware-export-validation.d.ts +5 -0
  23. package/dist/plugins/middleware-export-validation.js +36 -0
  24. package/dist/plugins/og-asset-ownership.d.ts +27 -0
  25. package/dist/plugins/og-asset-ownership.js +285 -0
  26. package/dist/plugins/og-assets.js +28 -10
  27. package/dist/plugins/remove-console.js +1 -1
  28. package/dist/plugins/strip-server-exports.js +2 -1
  29. package/dist/server/accept-encoding.d.ts +29 -0
  30. package/dist/server/accept-encoding.js +95 -0
  31. package/dist/server/app-action-request.d.ts +4 -0
  32. package/dist/server/app-action-request.js +9 -0
  33. package/dist/server/app-bfcache-identity.d.ts +12 -2
  34. package/dist/server/app-bfcache-identity.js +52 -23
  35. package/dist/server/app-browser-entry.js +8 -5
  36. package/dist/server/app-browser-navigation-controller.js +1 -1
  37. package/dist/server/app-browser-rsc-redirect.d.ts +2 -1
  38. package/dist/server/app-browser-rsc-redirect.js +8 -1
  39. package/dist/server/app-browser-state.js +1 -1
  40. package/dist/server/app-browser-visible-commit.js +1 -1
  41. package/dist/server/app-history-state.d.ts +1 -2
  42. package/dist/server/app-history-state.js +1 -1
  43. package/dist/server/app-middleware.d.ts +1 -0
  44. package/dist/server/app-middleware.js +1 -0
  45. package/dist/server/app-page-dispatch.d.ts +2 -2
  46. package/dist/server/app-page-head.js +2 -2
  47. package/dist/server/app-page-method.js +1 -1
  48. package/dist/server/app-page-request.d.ts +7 -0
  49. package/dist/server/app-page-request.js +19 -1
  50. package/dist/server/app-page-stream.js +2 -1
  51. package/dist/server/app-prerender-endpoints.d.ts +3 -3
  52. package/dist/server/app-prerender-endpoints.js +3 -4
  53. package/dist/server/app-route-handler-cache.js +1 -1
  54. package/dist/server/app-route-handler-dispatch.js +2 -2
  55. package/dist/server/app-route-handler-execution.js +3 -2
  56. package/dist/server/app-route-handler-policy.d.ts +1 -1
  57. package/dist/server/app-route-handler-policy.js +1 -6
  58. package/dist/server/app-route-module-loader.d.ts +62 -14
  59. package/dist/server/app-route-module-loader.js +61 -10
  60. package/dist/server/app-rsc-errors.d.ts +12 -1
  61. package/dist/server/app-rsc-errors.js +22 -2
  62. package/dist/server/app-rsc-handler.d.ts +16 -14
  63. package/dist/server/app-rsc-handler.js +47 -44
  64. package/dist/server/app-rsc-response-finalizer.js +5 -1
  65. package/dist/server/app-rsc-route-matching.d.ts +9 -0
  66. package/dist/server/app-rsc-route-matching.js +13 -0
  67. package/dist/server/app-server-action-execution.js +1 -1
  68. package/dist/server/app-ssr-entry.js +9 -7
  69. package/dist/server/headers.d.ts +5 -1
  70. package/dist/server/headers.js +5 -1
  71. package/dist/server/metadata-route-response.d.ts +3 -3
  72. package/dist/server/middleware-runtime.d.ts +2 -1
  73. package/dist/server/middleware-runtime.js +19 -8
  74. package/dist/server/middleware.d.ts +2 -2
  75. package/dist/server/middleware.js +26 -11
  76. package/dist/server/pages-request-pipeline.d.ts +3 -1
  77. package/dist/server/pages-request-pipeline.js +4 -2
  78. package/dist/server/prod-server.d.ts +1 -1
  79. package/dist/server/prod-server.js +85 -72
  80. package/dist/shims/dynamic.js +1 -1
  81. package/dist/shims/font-google-base.d.ts +1 -0
  82. package/dist/shims/font-google-base.js +6 -21
  83. package/dist/shims/internal/interpolate-as.d.ts +25 -0
  84. package/dist/shims/internal/interpolate-as.js +196 -0
  85. package/dist/shims/link.js +24 -1
  86. package/dist/shims/navigation.js +1 -1
  87. package/dist/shims/navigation.react-server.js +1 -1
  88. package/dist/shims/router.d.ts +1 -3
  89. package/dist/shims/router.js +62 -18
  90. package/dist/typegen.js +2 -10
  91. package/dist/utils/project.d.ts +7 -1
  92. package/dist/utils/project.js +16 -2
  93. package/package.json +3 -3
package/README.md CHANGED
@@ -404,7 +404,7 @@ These are deployed to Cloudflare Workers and updated on every push to `main`:
404
404
  | App Router (minimal) | Minimal App Router on Workers | [app-router-cloudflare.vinext.workers.dev](https://app-router-cloudflare.vinext.workers.dev) |
405
405
  | Pages Router (minimal) | Minimal Pages Router on Workers | [pages-router-cloudflare.vinext.workers.dev](https://pages-router-cloudflare.vinext.workers.dev) |
406
406
  | RealWorld API | REST API routes example | [realworld-api-rest.vinext.workers.dev](https://realworld-api-rest.vinext.workers.dev) |
407
- | Benchmarks Dashboard | Build performance tracking over time (D1-backed) | [benchmarks.vinext.workers.dev](https://benchmarks.vinext.workers.dev) |
407
+ | Benchmarks Dashboard | Build performance tracking over time (D1-backed) | [vinext-web.vinext.workers.dev/benchmarks](https://vinext-web.vinext.workers.dev/benchmarks) |
408
408
  | App Router + Nitro | App Router deployed via Nitro (multi-platform) | [examples/app-router-nitro](examples/app-router-nitro) |
409
409
 
410
410
  ## API coverage
@@ -614,7 +614,7 @@ We measure three things:
614
614
  - **Client bundle size** — gzipped output of each build.
615
615
  - **Dev server cold start** — 10 runs, randomized execution order. Vite's dependency optimizer cache is cleared before each run.
616
616
 
617
- Benchmarks run on GitHub CI runners (2-core Ubuntu) on every merge to `main`. See the launch numbers in the [announcement blog post](https://blog.cloudflare.com/vinext/) and the latest results at **[benchmarks.vinext.workers.dev](https://benchmarks.vinext.workers.dev)**.
617
+ Benchmarks run on GitHub CI runners (2-core Ubuntu) on every merge to `main`. See the launch numbers in the [announcement blog post](https://blog.cloudflare.com/vinext/) and the latest results at **[vinext-web.vinext.workers.dev/benchmarks](https://vinext-web.vinext.workers.dev/benchmarks)**.
618
618
 
619
619
  <details>
620
620
  <summary>Why the bundle size difference?</summary>
@@ -26,12 +26,10 @@ declare function createClientAssetFileNames(assetsDir: string): (assetInfo: Clie
26
26
  *
27
27
  * Splits the client bundle into:
28
28
  * - "framework" — React, ReactDOM, and scheduler (loaded on every page)
29
- * - "vinext" — vinext shims (router, head, link, etc.)
29
+ * - "vinext" — shared vinext runtime shims
30
30
  *
31
- * All other vendor code is left to Rollup's default chunk-splitting
32
- * algorithm. Rollup automatically deduplicates shared modules into
33
- * common chunks based on the import graph — no manual intervention
34
- * needed.
31
+ * Route-owned client shims and all other vendor code are left to the bundler's
32
+ * graph-based chunking so they stay behind their client-reference boundaries.
35
33
  *
36
34
  * Why not split every npm package into its own chunk?
37
35
  * - Per-package splitting (`vendor-X`) creates 50-200+ chunks for a
@@ -48,7 +46,7 @@ declare function createClientAssetFileNames(assetsDir: string): (assetInfo: Clie
48
46
  * well: shared dependencies between routes get their own chunks,
49
47
  * and route-specific code stays in route chunks.
50
48
  */
51
- declare function createClientManualChunks(shimsDir: string): (id: string) => string | undefined;
49
+ declare function createClientManualChunks(shimsDir: string, preserveRouteBoundaries?: boolean): (id: string) => string | undefined;
52
50
  /**
53
51
  * Rollup output config with manualChunks for client code-splitting.
54
52
  * Used by both CLI builds and multi-environment builds.
@@ -1,4 +1,18 @@
1
1
  //#region src/build/client-build-config.ts
2
+ const ROUTE_OWNED_CLIENT_SHIMS = new Set([
3
+ "compat-router",
4
+ "dynamic",
5
+ "dynamic-preload-chunks",
6
+ "form",
7
+ "image",
8
+ "layout-segment-context",
9
+ "legacy-image",
10
+ "link",
11
+ "offline",
12
+ "router",
13
+ "script",
14
+ "web-vitals"
15
+ ]);
2
16
  const NEXT_CLIENT_CSS_ASSET_FILE_NAMES = "css/[name].[hash:8][extname]";
3
17
  const NEXT_CLIENT_STATIC_MEDIA_FILE_NAMES = "media/[name].[hash:8][extname]";
4
18
  function joinAssetFileNamePattern(assetsDir, pattern) {
@@ -48,12 +62,10 @@ function getPackageName(id) {
48
62
  *
49
63
  * Splits the client bundle into:
50
64
  * - "framework" — React, ReactDOM, and scheduler (loaded on every page)
51
- * - "vinext" — vinext shims (router, head, link, etc.)
65
+ * - "vinext" — shared vinext runtime shims
52
66
  *
53
- * All other vendor code is left to Rollup's default chunk-splitting
54
- * algorithm. Rollup automatically deduplicates shared modules into
55
- * common chunks based on the import graph — no manual intervention
56
- * needed.
67
+ * Route-owned client shims and all other vendor code are left to the bundler's
68
+ * graph-based chunking so they stay behind their client-reference boundaries.
57
69
  *
58
70
  * Why not split every npm package into its own chunk?
59
71
  * - Per-package splitting (`vendor-X`) creates 50-200+ chunks for a
@@ -70,7 +82,7 @@ function getPackageName(id) {
70
82
  * well: shared dependencies between routes get their own chunks,
71
83
  * and route-specific code stays in route chunks.
72
84
  */
73
- function createClientManualChunks(shimsDir) {
85
+ function createClientManualChunks(shimsDir, preserveRouteBoundaries = false) {
74
86
  return function clientManualChunks(id) {
75
87
  if (id.includes("node_modules")) {
76
88
  const pkg = getPackageName(id);
@@ -78,7 +90,15 @@ function createClientManualChunks(shimsDir) {
78
90
  if (pkg === "react" || pkg === "react-dom" || pkg === "scheduler") return "framework";
79
91
  return;
80
92
  }
81
- if (id.startsWith(shimsDir)) return "vinext";
93
+ if (id.startsWith(shimsDir)) {
94
+ if (preserveRouteBoundaries) {
95
+ const relativeId = id.slice(shimsDir.length).split("?", 1)[0] ?? "";
96
+ const extensionIndex = relativeId.lastIndexOf(".");
97
+ const shimName = extensionIndex === -1 ? relativeId : relativeId.slice(0, extensionIndex);
98
+ if (ROUTE_OWNED_CLIENT_SHIMS.has(shimName)) return void 0;
99
+ }
100
+ return "vinext";
101
+ }
82
102
  };
83
103
  }
84
104
  /**
@@ -0,0 +1,16 @@
1
+ //#region src/build/google-fonts/find-font-files-in-css.d.ts
2
+ /**
3
+ * Find all font files in the CSS response and determine which files should be preloaded.
4
+ * In Google Fonts responses, the @font-face's subset is above it in a comment.
5
+ * Walk through the CSS from top to bottom, keeping track of the current subset.
6
+ *
7
+ * Ported from Next.js:
8
+ * packages/font/src/google/find-font-files-in-css.ts
9
+ * https://github.com/vercel/next.js/blob/canary/packages/font/src/google/find-font-files-in-css.ts
10
+ */
11
+ declare function findFontFilesInCss(css: string, subsetsToPreload?: string[]): {
12
+ googleFontFileUrl: string;
13
+ preloadFontFile: boolean;
14
+ }[];
15
+ //#endregion
16
+ export { findFontFilesInCss };
@@ -0,0 +1,28 @@
1
+ //#region src/build/google-fonts/find-font-files-in-css.ts
2
+ /**
3
+ * Find all font files in the CSS response and determine which files should be preloaded.
4
+ * In Google Fonts responses, the @font-face's subset is above it in a comment.
5
+ * Walk through the CSS from top to bottom, keeping track of the current subset.
6
+ *
7
+ * Ported from Next.js:
8
+ * packages/font/src/google/find-font-files-in-css.ts
9
+ * https://github.com/vercel/next.js/blob/canary/packages/font/src/google/find-font-files-in-css.ts
10
+ */
11
+ function findFontFilesInCss(css, subsetsToPreload) {
12
+ const fontFiles = [];
13
+ let currentSubset = "";
14
+ for (const line of css.split("\n")) {
15
+ const newSubset = /\/\* (.+?) \*\//.exec(line)?.[1];
16
+ if (newSubset) currentSubset = newSubset;
17
+ else {
18
+ const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1];
19
+ if (googleFontFileUrl && !fontFiles.some((foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl)) fontFiles.push({
20
+ googleFontFileUrl,
21
+ preloadFontFile: !!subsetsToPreload?.includes(currentSubset)
22
+ });
23
+ }
24
+ }
25
+ return fontFiles;
26
+ }
27
+ //#endregion
28
+ export { findFontFilesInCss };
@@ -306,7 +306,6 @@ async function prerenderPages({ routes, apiRoutes, pagesDir, outDir, config, mod
306
306
  for (const route of bundlePageRoutes) {
307
307
  if (BLOCKED_PAGES.includes(route.pattern)) continue;
308
308
  if (route.pattern === "/404") continue;
309
- if (!routes.find((r) => r.filePath === route.filePath || r.pattern === route.pattern)) continue;
310
309
  const { type, revalidate: classifiedRevalidate } = classifyPagesRoute(route.filePath);
311
310
  if (type === "ssr") {
312
311
  if (mode === "export") results.push({
@@ -648,7 +647,7 @@ async function prerenderApp({ routes, metadataRoutes = [], outDir, config, mode,
648
647
  });
649
648
  continue;
650
649
  }
651
- if (!Array.isArray(paramSets) || paramSets.length === 0) {
650
+ if (paramSets.length === 0) {
652
651
  results.push({
653
652
  route: route.pattern,
654
653
  status: "skipped",
@@ -117,7 +117,6 @@ declare function buildReportRows(options: {
117
117
  * ○ Static ◐ ISR ƒ Dynamic λ API
118
118
  */
119
119
  declare function formatBuildReport(rows: RouteRow[], routerLabel?: string): string;
120
- declare function findDir(root: string, ...candidates: string[]): string | null;
121
120
  /**
122
121
  * Scans the project at `root`, classifies all routes, and prints the
123
122
  * Next.js-style build report to stdout.
@@ -130,4 +129,4 @@ declare function printBuildReport(options: {
130
129
  prerenderResult?: PrerenderResult;
131
130
  }): Promise<void>;
132
131
  //#endregion
133
- export { RouteRow, RouteType, buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, findDir, formatBuildReport, getAppRouteRenderEntryPath, hasNamedExport, printBuildReport };
132
+ export { RouteRow, RouteType, buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, formatBuildReport, getAppRouteRenderEntryPath, hasNamedExport, printBuildReport };
@@ -1,5 +1,5 @@
1
+ import { findDir } from "../utils/project.js";
1
2
  import fs from "node:fs";
2
- import path from "node:path";
3
3
  import { parseSync } from "vite";
4
4
  //#region src/build/report.ts
5
5
  /**
@@ -470,15 +470,6 @@ function formatBuildReport(rows, routerLabel = "app") {
470
470
  }
471
471
  return lines.join("\n");
472
472
  }
473
- function findDir(root, ...candidates) {
474
- for (const candidate of candidates) {
475
- const full = path.join(root, candidate);
476
- try {
477
- if (fs.statSync(full).isDirectory()) return full;
478
- } catch {}
479
- }
480
- return null;
481
- }
482
473
  /**
483
474
  * Scans the project at `root`, classifies all routes, and prints the
484
475
  * Next.js-style build report to stdout.
@@ -510,4 +501,4 @@ async function printBuildReport(options) {
510
501
  }
511
502
  }
512
503
  //#endregion
513
- export { buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, findDir, formatBuildReport, getAppRouteRenderEntryPath, hasNamedExport, printBuildReport };
504
+ export { buildReportRows, classifyAppRoute, classifyLayoutSegmentConfig, classifyPagesRoute, extractExportConstNumber, extractExportConstString, extractGetStaticPropsRevalidate, formatBuildReport, getAppRouteRenderEntryPath, hasNamedExport, printBuildReport };
@@ -1,7 +1,7 @@
1
+ import { findDir } from "../utils/project.js";
1
2
  import { apiRouter, pagesRouter } from "../routing/pages-router.js";
2
3
  import { appRouter } from "../routing/app-router.js";
3
4
  import { scanMetadataFiles } from "../server/metadata-routes.js";
4
- import { findDir } from "./report.js";
5
5
  import { loadNextConfig, resolveNextConfig } from "../config/next-config.js";
6
6
  import { readPrerenderSecret } from "./server-manifest.js";
7
7
  import { startProdServer } from "../server/prod-server.js";
package/dist/check.js CHANGED
@@ -1,4 +1,4 @@
1
- import { detectPackageManager } from "./utils/project.js";
1
+ import { detectPackageManager, findDir } from "./utils/project.js";
2
2
  import { normalizePathSeparators } from "./utils/path.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
@@ -780,8 +780,8 @@ function checkLibraries(root) {
780
780
  */
781
781
  function checkConventions(root) {
782
782
  const items = [];
783
- const pagesDir = fs.existsSync(path.join(root, "pages")) ? path.join(root, "pages") : fs.existsSync(path.join(root, "src", "pages")) ? path.join(root, "src", "pages") : null;
784
- const appDirPath = fs.existsSync(path.join(root, "app")) ? path.join(root, "app") : fs.existsSync(path.join(root, "src", "app")) ? path.join(root, "src", "app") : null;
783
+ const pagesDir = findDir(root, "pages", path.join("src", "pages"));
784
+ const appDirPath = findDir(root, "app", path.join("src", "app"));
785
785
  const hasPages = pagesDir !== null;
786
786
  const hasApp = appDirPath !== null;
787
787
  const hasProxy = fs.existsSync(path.join(root, "proxy.ts")) || fs.existsSync(path.join(root, "proxy.js"));
@@ -11,6 +11,15 @@ type PagesRouterLinkRuntime = {
11
11
  };
12
12
  type PagesRouterLinkNavigation = {
13
13
  href: string;
14
+ /**
15
+ * Optional `as` mask — the browser-bar URL when it differs from `href`
16
+ * (a Pages Router route pattern, e.g. `<Link href="/posts/[id]" as="/posts/1">`).
17
+ * When present, `router.push/replace(href, as, ...)` is invoked with both
18
+ * values so the upstream (url, as) pair survives end-to-end. Mirrors
19
+ * Next.js's Link → Router contract: packages/next/src/client/link.tsx
20
+ * → router.push(href, as).
21
+ */
22
+ as?: string;
14
23
  replace: boolean;
15
24
  scroll: boolean;
16
25
  shallow?: boolean;
@@ -30,6 +39,7 @@ declare function resolvePagesRouterQueryOnlyHref(href: string, {
30
39
  }): string;
31
40
  declare function navigatePagesRouterLink(router: PagesRouterLinkRuntime, {
32
41
  href,
42
+ as,
33
43
  replace,
34
44
  scroll,
35
45
  shallow,
@@ -14,15 +14,15 @@ function resolvePagesRouterQueryOnlyHref(href, { asPath, basePath, fallbackHref,
14
14
  return href;
15
15
  }
16
16
  }
17
- async function navigatePagesRouterLink(router, { href, replace, scroll, shallow, locale, interpolateDynamicRoute = false }) {
17
+ async function navigatePagesRouterLink(router, { href, as, replace, scroll, shallow, locale, interpolateDynamicRoute = false }) {
18
18
  const routerOptions = {
19
19
  scroll,
20
20
  locale
21
21
  };
22
22
  if (interpolateDynamicRoute) routerOptions._vinextInterpolateDynamicRoute = true;
23
23
  if (shallow !== void 0) routerOptions.shallow = shallow;
24
- if (replace) await router.replace(href, void 0, routerOptions);
25
- else await router.push(href, void 0, routerOptions);
24
+ if (replace) await router.replace(href, as, routerOptions);
25
+ else await router.push(href, as, routerOptions);
26
26
  }
27
27
  async function navigatePagesRouterLinkWithFallback({ router, loadRouter, navigation, fallback }) {
28
28
  let pagesRouter = router;
package/dist/deploy.js CHANGED
@@ -1,4 +1,4 @@
1
- import { detectPackageManager as detectPackageManager$1, ensureESModule as ensureESModule$1, findInNodeModules, renameCJSConfigs as renameCJSConfigs$1 } from "./utils/project.js";
1
+ import { detectPackageManager as detectPackageManager$1, ensureESModule as ensureESModule$1, findDir, findInNodeModules, hasAppDir, renameCJSConfigs as renameCJSConfigs$1 } from "./utils/project.js";
2
2
  import { parsePositiveIntegerArg } from "./cli-args.js";
3
3
  import { escapeRegExp } from "./utils/regex.js";
4
4
  import { loadNextConfig, resolveNextConfig } from "./config/next-config.js";
@@ -108,8 +108,8 @@ function formatMissingCloudflarePluginError(options) {
108
108
  return `[vinext] Missing @cloudflare/vite-plugin in ${configRef}.\n\n Cloudflare Workers builds require the cloudflare() plugin.\n Add it to ${configRef}:\n\n import { cloudflare } from "@cloudflare/vite-plugin";\n\n export default defineConfig({\n plugins: [\n vinext(),\n cloudflare(${cfArg}),\n ],\n });\n\n Or delete ${configRef} and re-run \`vinext deploy\` to auto-generate it.`;
109
109
  }
110
110
  function detectProject(root) {
111
- const hasApp = fs.existsSync(path.join(root, "app")) || fs.existsSync(path.join(root, "src", "app"));
112
- const hasPages = fs.existsSync(path.join(root, "pages")) || fs.existsSync(path.join(root, "src", "pages"));
111
+ const hasApp = hasAppDir(root);
112
+ const hasPages = findDir(root, "pages", path.join("src", "pages")) !== null;
113
113
  const isAppRouter = hasApp;
114
114
  const isPagesRouter = !hasApp && hasPages;
115
115
  const hasViteConfig = fs.existsSync(path.join(root, "vite.config.ts")) || fs.existsSync(path.join(root, "vite.config.js")) || fs.existsSync(path.join(root, "vite.config.mjs"));
@@ -44,7 +44,8 @@ type AppRouterConfig = {
44
44
  reactMaxHeadersLength?: number; /** Maximum in-memory cache size in bytes. 0 disables the default memory cache. */
45
45
  cacheMaxMemorySize?: number; /** Inline app CSS into production HTML (from experimental.inlineCss). */
46
46
  inlineCss?: boolean; /** Enables Next.js Cache Components semantics for App Router document HTML. */
47
- cacheComponents?: boolean; /** Internationalization routing config for middleware matcher locale handling. */
47
+ cacheComponents?: boolean; /** Whether the RSC build discovered any server references. Defaults to true. */
48
+ hasServerActions?: boolean; /** Internationalization routing config for middleware matcher locale handling. */
48
49
  i18n?: NextI18nConfig | null;
49
50
  imageConfig?: ImageConfig;
50
51
  /**
@@ -20,6 +20,8 @@ const middlewareRequestHeadersPath = resolveEntryPath("../server/middleware-requ
20
20
  const normalizePathModulePath = resolveEntryPath("../server/normalize-path.js", import.meta.url);
21
21
  const appRouteHandlerDispatchPath = resolveEntryPath("../server/app-route-handler-dispatch.js", import.meta.url);
22
22
  const appRouteHandlerResponsePath = resolveEntryPath("../server/app-route-handler-response.js", import.meta.url);
23
+ const appMiddlewarePath = resolveEntryPath("../server/app-middleware.js", import.meta.url);
24
+ const metadataRouteResponsePath = resolveEntryPath("../server/metadata-route-response.js", import.meta.url);
23
25
  const appServerActionExecutionPath = resolveEntryPath("../server/app-server-action-execution.js", import.meta.url);
24
26
  const appRscErrorsPath = resolveEntryPath("../server/app-rsc-errors.js", import.meta.url);
25
27
  const appPageExecutionPath = resolveEntryPath("../server/app-page-execution.js", import.meta.url);
@@ -73,6 +75,7 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
73
75
  const cacheMaxMemorySize = config?.cacheMaxMemorySize;
74
76
  const inlineCss = config?.inlineCss === true;
75
77
  const cacheComponents = config?.cacheComponents === true;
78
+ const hasServerActions = config?.hasServerActions !== false;
76
79
  const i18nConfig = config?.i18n ?? null;
77
80
  const hasPagesDir = config?.hasPagesDir ?? false;
78
81
  const publicFiles = config?.publicFiles ?? [];
@@ -93,12 +96,12 @@ async function __loadPrerenderPagesRoutes() {
93
96
  import ${JSON.stringify(serverGlobalsPath)};
94
97
  import {
95
98
  renderToReadableStream as _renderToReadableStream,
96
- decodeAction,
99
+ ${hasServerActions ? `decodeAction,
97
100
  decodeFormState,
98
101
  decodeReply,
99
102
  loadServerAction,
100
- createTemporaryReferenceSet,
101
- } from "@vitejs/plugin-rsc/rsc";
103
+ createTemporaryReferenceSet,` : ""}
104
+ } from ${JSON.stringify(hasServerActions ? "@vitejs/plugin-rsc/rsc" : "@vitejs/plugin-rsc/react/rsc")};
102
105
  import { createClientManifest as _createClientManifest } from "@vitejs/plugin-rsc/core/rsc";
103
106
  import { prerender as _prerender } from "@vitejs/plugin-rsc/vendor/react-server-dom/static.edge";
104
107
  import { createRscPrerenderer, createRscRenderer } from ${JSON.stringify(rscStreamHintsPath)};
@@ -112,7 +115,8 @@ import { getNavigationContext as _getNavigationContext } from "next/navigation";
112
115
  import { configureMemoryCacheHandler as __configureMemoryCacheHandler } from "vinext/shims/cache-handler";
113
116
  import { headersContextFromRequest, getDraftModeCookieHeader, getAndClearPendingCookies, consumeDynamicUsage, consumeInvalidDynamicUsageError, setHeadersAccessPhase } from "next/headers";
114
117
  import { mergeMetadata, resolveModuleMetadata, mergeViewport, resolveModuleViewport } from "vinext/metadata";
115
- ${middlewarePath ? `import * as middlewareModule from ${JSON.stringify(normalizePathSeparators(middlewarePath))};` : ""}
118
+ ${middlewarePath ? `import * as middlewareModule from ${JSON.stringify(normalizePathSeparators(middlewarePath))};
119
+ import { applyAppMiddleware as __applyAppMiddleware } from ${JSON.stringify(appMiddlewarePath)};` : ""}
116
120
  ${instrumentationPath ? `import * as _instrumentation from ${JSON.stringify(normalizePathSeparators(instrumentationPath))};
117
121
  import { ensureInstrumentationRegistered as __ensureInstrumentationRegistered } from ${JSON.stringify(instrumentationRuntimePath)};` : ""}
118
122
  import { createAppRscHandler } from "vinext/server/app-rsc-handler";
@@ -123,7 +127,8 @@ ${hasPagesDir ? `import {
123
127
  applyRouteHandlerMiddlewareContext as __applyRouteHandlerMiddlewareContext,
124
128
  } from ${JSON.stringify(appRouteHandlerResponsePath)};` : ""}
125
129
  const __loadAppRouteHandlerDispatch = () => import(${JSON.stringify(appRouteHandlerDispatchPath)});
126
- const __loadAppServerActionExecution = () => import(${JSON.stringify(appServerActionExecutionPath)});
130
+ ${hasServerActions ? `const __loadAppServerActionExecution = () => import(${JSON.stringify(appServerActionExecutionPath)});` : ""}
131
+ ${(metadataRoutes?.length ?? 0) > 0 ? `const __loadMetadataRouteResponse = () => import(${JSON.stringify(metadataRouteResponsePath)});` : ""}
127
132
  import {
128
133
  sanitizeErrorForClient as __sanitizeErrorForClient,
129
134
  } from ${JSON.stringify(appRscErrorsPath)};
@@ -683,6 +688,7 @@ export default createAppRscHandler({
683
688
  ${instrumentationPath ? `ensureInstrumentation() {
684
689
  return __ensureInstrumentationRegistered(_instrumentation);
685
690
  },` : ""}
691
+ ${hasServerActions ? `
686
692
  async handleProgressiveActionRequest({
687
693
  actionId,
688
694
  cleanPathname,
@@ -860,13 +866,33 @@ export default createAppRscHandler({
860
866
  },
861
867
  });
862
868
  },
869
+ ` : ""}
863
870
  i18nConfig: __i18nConfig,
864
- isMiddlewareProxy: ${JSON.stringify(middlewarePath ? isProxyFile(middlewarePath) : false)},
865
871
  ${hasPagesDir ? `loadPrerenderPagesRoutes: __loadPrerenderPagesRoutes,` : ""}
866
- makeThenableParams,
872
+ ${(metadataRoutes?.length ?? 0) > 0 ? `async handleMetadataRouteRequest(cleanPathname) {
873
+ const { handleMetadataRouteRequest: __handleMetadataRouteRequest } =
874
+ await __loadMetadataRouteResponse();
875
+ return __handleMetadataRouteRequest({
876
+ metadataRoutes,
877
+ cleanPathname,
878
+ makeThenableParams,
879
+ });
880
+ },` : ""}
867
881
  matchRoute,
868
- metadataRoutes,
869
- middlewareModule: ${middlewarePath ? "middlewareModule" : "null"},
882
+ ${middlewarePath ? `runMiddleware({ cleanPathname, context, isDataRequest, request }) {
883
+ return __applyAppMiddleware({
884
+ basePath: __basePath,
885
+ cleanPathname,
886
+ context,
887
+ filePath: ${JSON.stringify(middlewarePath ? normalizePathSeparators(middlewarePath) : "")},
888
+ i18nConfig: __i18nConfig,
889
+ isDataRequest,
890
+ isProxy: ${JSON.stringify(isProxyFile(middlewarePath))},
891
+ module: middlewareModule,
892
+ request,
893
+ trailingSlash: __trailingSlash,
894
+ });
895
+ },` : ""}
870
896
  publicFiles: __publicFiles,
871
897
  renderNotFound({ isRscRequest, matchedParams, middlewareContext, request, route, scriptNonce }) {
872
898
  const __isEdge = route ? __isEdgeRuntime(__resolveAppPageSegmentConfig({ layouts: route.layouts, page: route.page }).runtime) : false;
@@ -50,58 +50,68 @@ function registerRouteModules(routes, imports) {
50
50
  for (const route of routes) {
51
51
  if (route.pagePath) imports.getLazyLoaderVar(route.pagePath);
52
52
  if (route.routePath) imports.getLazyLoaderVar(route.routePath);
53
- for (const layout of route.layouts) imports.getImportVar(layout);
54
- for (const tmpl of route.templates) imports.getImportVar(tmpl);
55
- if (route.loadingPath) imports.getImportVar(route.loadingPath);
56
- if (route.errorPath) imports.getImportVar(route.errorPath);
53
+ for (const layout of route.layouts) imports.getLazyLoaderVar(layout);
54
+ for (const tmpl of route.templates) imports.getLazyLoaderVar(tmpl);
55
+ if (route.loadingPath) imports.getLazyLoaderVar(route.loadingPath);
56
+ if (route.errorPath) imports.getLazyLoaderVar(route.errorPath);
57
57
  if (route.layoutErrorPaths) {
58
- for (const ep of route.layoutErrorPaths) if (ep) imports.getImportVar(ep);
58
+ for (const ep of route.layoutErrorPaths) if (ep) imports.getLazyLoaderVar(ep);
59
59
  }
60
- if (route.errorPaths) for (const ep of route.errorPaths) imports.getImportVar(ep);
61
- if (route.notFoundPath) imports.getImportVar(route.notFoundPath);
60
+ if (route.errorPaths) for (const ep of route.errorPaths) imports.getLazyLoaderVar(ep);
61
+ if (route.notFoundPath) imports.getLazyLoaderVar(route.notFoundPath);
62
62
  if (route.notFoundPaths) {
63
- for (const nfp of route.notFoundPaths) if (nfp) imports.getImportVar(nfp);
63
+ for (const nfp of route.notFoundPaths) if (nfp) imports.getLazyLoaderVar(nfp);
64
64
  }
65
- if (route.forbiddenPath) imports.getImportVar(route.forbiddenPath);
65
+ if (route.forbiddenPath) imports.getLazyLoaderVar(route.forbiddenPath);
66
66
  if (route.forbiddenPaths) {
67
- for (const fp of route.forbiddenPaths) if (fp) imports.getImportVar(fp);
67
+ for (const fp of route.forbiddenPaths) if (fp) imports.getLazyLoaderVar(fp);
68
68
  }
69
- if (route.unauthorizedPath) imports.getImportVar(route.unauthorizedPath);
69
+ if (route.unauthorizedPath) imports.getLazyLoaderVar(route.unauthorizedPath);
70
70
  if (route.unauthorizedPaths) {
71
- for (const up of route.unauthorizedPaths) if (up) imports.getImportVar(up);
71
+ for (const up of route.unauthorizedPaths) if (up) imports.getLazyLoaderVar(up);
72
72
  }
73
73
  for (const slot of route.parallelSlots) {
74
- if (slot.pagePath) imports.getImportVar(slot.pagePath);
75
- if (slot.defaultPath) imports.getImportVar(slot.defaultPath);
76
- if (slot.layoutPath) imports.getImportVar(slot.layoutPath);
77
- if (slot.loadingPath) imports.getImportVar(slot.loadingPath);
78
- if (slot.errorPath) imports.getImportVar(slot.errorPath);
74
+ if (slot.pagePath) imports.getLazyLoaderVar(slot.pagePath);
75
+ if (slot.defaultPath) imports.getLazyLoaderVar(slot.defaultPath);
76
+ if (slot.layoutPath) imports.getLazyLoaderVar(slot.layoutPath);
77
+ if (slot.loadingPath) imports.getLazyLoaderVar(slot.loadingPath);
78
+ if (slot.errorPath) imports.getLazyLoaderVar(slot.errorPath);
79
79
  for (const ir of slot.interceptingRoutes) {
80
80
  imports.getLazyLoaderVar(ir.pagePath);
81
- for (const layoutPath of ir.layoutPaths) imports.getImportVar(layoutPath);
81
+ for (const layoutPath of ir.layoutPaths) imports.getLazyLoaderVar(layoutPath);
82
82
  }
83
83
  }
84
84
  for (const ir of route.siblingIntercepts ?? []) {
85
85
  imports.getLazyLoaderVar(ir.pagePath);
86
- for (const layoutPath of ir.layoutPaths) imports.getImportVar(layoutPath);
86
+ for (const layoutPath of ir.layoutPaths) imports.getLazyLoaderVar(layoutPath);
87
87
  }
88
88
  }
89
89
  }
90
+ function moduleArray(length) {
91
+ return `[${Array.from({ length }, () => "null").join(", ")}]`;
92
+ }
93
+ function lazyLoaderArray(filePaths, imports) {
94
+ return `[${filePaths.map((filePath) => filePath ? imports.getLazyLoaderVar(filePath) : "null").join(", ")}]`;
95
+ }
90
96
  function buildRouteEntries(routes, imports) {
91
97
  return routes.map((route, routeIdx) => {
92
98
  const staticSiblings = route.isDynamic ? computeAppRouteStaticSiblings(routes, route) : [];
93
- const layoutVars = route.layouts.map((l) => imports.getImportVar(l));
94
- const templateVars = route.templates.map((t) => imports.getImportVar(t));
95
- const notFoundVars = (route.notFoundPaths ?? []).map((nf) => nf ? imports.getImportVar(nf) : "null");
96
- const forbiddenVars = (route.forbiddenPaths ?? []).map((fp) => fp ? imports.getImportVar(fp) : "null");
97
- const unauthorizedVars = (route.unauthorizedPaths ?? []).map((up) => up ? imports.getImportVar(up) : "null");
99
+ const layoutLoaders = lazyLoaderArray(route.layouts, imports);
100
+ const templateLoaders = lazyLoaderArray(route.templates, imports);
101
+ const notFoundPaths = route.notFoundPaths ?? [];
102
+ const forbiddenPaths = route.forbiddenPaths ?? [];
103
+ const unauthorizedPaths = route.unauthorizedPaths ?? [];
104
+ const notFoundLoaders = lazyLoaderArray(notFoundPaths, imports);
105
+ const forbiddenLoaders = lazyLoaderArray(forbiddenPaths, imports);
106
+ const unauthorizedLoaders = lazyLoaderArray(unauthorizedPaths, imports);
98
107
  const siblingInterceptEntries = (route.siblingIntercepts ?? []).map((ir) => ` {
99
108
  convention: ${JSON.stringify(ir.convention)},
100
109
  targetPattern: ${JSON.stringify(ir.targetPattern)},
101
110
  sourceMatchPattern: ${JSON.stringify(ir.sourceMatchPattern)},
102
111
  sourcePageSegments: ${JSON.stringify(ir.sourcePageSegments)},
103
112
  slotId: ${JSON.stringify(ir.slotId ?? null)},
104
- interceptLayouts: [${ir.layoutPaths.map((l) => imports.getImportVar(l)).join(", ")}],
113
+ interceptLayouts: ${moduleArray(ir.layoutPaths.length)},
114
+ __loadInterceptLayouts: ${lazyLoaderArray(ir.layoutPaths, imports)},
105
115
  page: null,
106
116
  __pageLoader: ${imports.getLazyLoaderVar(ir.pagePath)},
107
117
  params: ${JSON.stringify(ir.params)},
@@ -112,7 +122,8 @@ function buildRouteEntries(routes, imports) {
112
122
  targetPattern: ${JSON.stringify(ir.targetPattern)},
113
123
  sourceMatchPattern: ${JSON.stringify(ir.sourceMatchPattern)},
114
124
  sourcePageSegments: ${JSON.stringify(ir.sourcePageSegments)},
115
- interceptLayouts: [${ir.layoutPaths.map((layoutPath) => imports.getImportVar(layoutPath)).join(", ")}],
125
+ interceptLayouts: ${moduleArray(ir.layoutPaths.length)},
126
+ __loadInterceptLayouts: ${lazyLoaderArray(ir.layoutPaths, imports)},
116
127
  page: null,
117
128
  __pageLoader: ${imports.getLazyLoaderVar(ir.pagePath)},
118
129
  params: ${JSON.stringify(ir.params)},
@@ -120,11 +131,16 @@ function buildRouteEntries(routes, imports) {
120
131
  return ` ${JSON.stringify(slot.key)}: {
121
132
  id: ${JSON.stringify(slot.id ?? null)},
122
133
  name: ${JSON.stringify(slot.name)},
123
- page: ${slot.pagePath ? imports.getImportVar(slot.pagePath) : "null"},
124
- default: ${slot.defaultPath ? imports.getImportVar(slot.defaultPath) : "null"},
125
- layout: ${slot.layoutPath ? imports.getImportVar(slot.layoutPath) : "null"},
126
- loading: ${slot.loadingPath ? imports.getImportVar(slot.loadingPath) : "null"},
127
- error: ${slot.errorPath ? imports.getImportVar(slot.errorPath) : "null"},
134
+ page: null,
135
+ __loadPage: ${slot.pagePath ? imports.getLazyLoaderVar(slot.pagePath) : "null"},
136
+ default: null,
137
+ __loadDefault: ${slot.defaultPath ? imports.getLazyLoaderVar(slot.defaultPath) : "null"},
138
+ layout: null,
139
+ __loadLayout: ${slot.layoutPath ? imports.getLazyLoaderVar(slot.layoutPath) : "null"},
140
+ loading: null,
141
+ __loadLoading: ${slot.loadingPath ? imports.getLazyLoaderVar(slot.loadingPath) : "null"},
142
+ error: null,
143
+ __loadError: ${slot.errorPath ? imports.getLazyLoaderVar(slot.errorPath) : "null"},
128
144
  layoutIndex: ${slot.layoutIndex},
129
145
  routeSegments: ${JSON.stringify(slot.routeSegments)},
130
146
  slotPatternParts: ${slot.slotPatternParts ? JSON.stringify(slot.slotPatternParts) : "null"},
@@ -134,8 +150,10 @@ ${interceptEntries.join(",\n")}
134
150
  ],
135
151
  }`;
136
152
  });
137
- const layoutErrorVars = (route.layoutErrorPaths || []).map((ep) => ep ? imports.getImportVar(ep) : "null");
138
- const errorVars = (route.errorPaths ?? []).map((ep) => imports.getImportVar(ep));
153
+ const layoutErrorPaths = route.layoutErrorPaths ?? [];
154
+ const errorPaths = route.errorPaths ?? [];
155
+ const layoutErrorLoaders = lazyLoaderArray(layoutErrorPaths, imports);
156
+ const errorLoaders = lazyLoaderArray(errorPaths, imports);
139
157
  const loadPageField = route.pagePath ? imports.getLazyLoaderVar(route.pagePath) : "null";
140
158
  const loadRouteHandlerField = route.routePath ? imports.getLazyLoaderVar(route.routePath) : "null";
141
159
  return ` {
@@ -152,13 +170,17 @@ ${interceptEntries.join(",\n")}
152
170
  __loadPage: ${loadPageField},
153
171
  routeHandler: null,
154
172
  __loadRouteHandler: ${loadRouteHandlerField},
155
- layouts: [${layoutVars.join(", ")}],
173
+ layouts: ${moduleArray(route.layouts.length)},
174
+ __loadLayouts: ${layoutLoaders},
156
175
  routeSegments: ${JSON.stringify(route.routeSegments)},
157
176
  templateTreePositions: ${JSON.stringify(route.templateTreePositions)},
158
177
  layoutTreePositions: ${JSON.stringify(route.layoutTreePositions)},
159
- templates: [${templateVars.join(", ")}],
160
- errors: [${layoutErrorVars.join(", ")}],
161
- errorPaths: [${errorVars.join(", ")}],
178
+ templates: ${moduleArray(route.templates.length)},
179
+ __loadTemplates: ${templateLoaders},
180
+ errors: ${moduleArray(layoutErrorPaths.length)},
181
+ __loadErrors: ${layoutErrorLoaders},
182
+ errorPaths: ${moduleArray(errorPaths.length)},
183
+ __loadErrorPaths: ${errorLoaders},
162
184
  errorTreePositions: ${JSON.stringify(route.errorTreePositions ?? null)},
163
185
  slots: {
164
186
  ${slotEntries.join(",\n")}
@@ -166,14 +188,22 @@ ${slotEntries.join(",\n")}
166
188
  siblingIntercepts: [
167
189
  ${siblingInterceptEntries.join(",\n")}
168
190
  ],
169
- loading: ${route.loadingPath ? imports.getImportVar(route.loadingPath) : "null"},
170
- error: ${route.errorPath ? imports.getImportVar(route.errorPath) : "null"},
171
- notFound: ${route.notFoundPath ? imports.getImportVar(route.notFoundPath) : "null"},
172
- notFounds: [${notFoundVars.join(", ")}],
173
- forbidden: ${route.forbiddenPath ? imports.getImportVar(route.forbiddenPath) : "null"},
174
- forbiddens: [${forbiddenVars.join(", ")}],
175
- unauthorized: ${route.unauthorizedPath ? imports.getImportVar(route.unauthorizedPath) : "null"},
176
- unauthorizeds: [${unauthorizedVars.join(", ")}],
191
+ loading: null,
192
+ __loadLoading: ${route.loadingPath ? imports.getLazyLoaderVar(route.loadingPath) : "null"},
193
+ error: null,
194
+ __loadError: ${route.errorPath ? imports.getLazyLoaderVar(route.errorPath) : "null"},
195
+ notFound: null,
196
+ __loadNotFound: ${route.notFoundPath ? imports.getLazyLoaderVar(route.notFoundPath) : "null"},
197
+ notFounds: ${moduleArray(notFoundPaths.length)},
198
+ __loadNotFounds: ${notFoundLoaders},
199
+ forbidden: null,
200
+ __loadForbidden: ${route.forbiddenPath ? imports.getLazyLoaderVar(route.forbiddenPath) : "null"},
201
+ forbiddens: ${moduleArray(forbiddenPaths.length)},
202
+ __loadForbiddens: ${forbiddenLoaders},
203
+ unauthorized: null,
204
+ __loadUnauthorized: ${route.unauthorizedPath ? imports.getLazyLoaderVar(route.unauthorizedPath) : "null"},
205
+ unauthorizeds: ${moduleArray(unauthorizedPaths.length)},
206
+ __loadUnauthorizeds: ${unauthorizedLoaders},
177
207
  }`;
178
208
  });
179
209
  }
@@ -217,7 +247,7 @@ function buildGenerateStaticParamsEntries(routes, imports, namesByPattern) {
217
247
  const sourcesByPattern = /* @__PURE__ */ new Map();
218
248
  for (const route of routes) {
219
249
  if (!route.isDynamic) continue;
220
- for (const [index, layoutPath] of route.layouts.entries()) appendStaticParamSource(sourcesByPattern, createRoutePatternPrefix(route.routeSegments, route.layoutTreePositions[index] ?? 0)?.pattern ?? null, `${imports.getImportVar(layoutPath)}?.generateStaticParams`);
250
+ for (const [index, layoutPath] of route.layouts.entries()) appendStaticParamSource(sourcesByPattern, createRoutePatternPrefix(route.routeSegments, route.layoutTreePositions[index] ?? 0)?.pattern ?? null, `{ load: ${imports.getLazyLoaderVar(layoutPath)} }`);
221
251
  if (route.pagePath) appendStaticParamSource(sourcesByPattern, route.pattern, `{ load: ${imports.getLazyLoaderVar(route.pagePath)} }`);
222
252
  }
223
253
  return Array.from(sourcesByPattern.entries()).map(([pattern, sources]) => {
@@ -103,6 +103,7 @@ export async function runMiddleware(request, ctx, options) {
103
103
  return __runGeneratedMiddleware({
104
104
  basePath: vinextConfig.basePath,
105
105
  ctx,
106
+ filePath: ${JSON.stringify(normalizePathSeparators(middlewarePath))},
106
107
  i18nConfig,
107
108
  isDataRequest: options?.isDataRequest === true,
108
109
  isProxy: ${JSON.stringify(isProxyFile(middlewarePath))},