vinext 0.1.6 → 0.1.8

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 (114) hide show
  1. package/dist/build/client-build-config.d.ts +4 -6
  2. package/dist/build/client-build-config.js +27 -7
  3. package/dist/build/prerender.js +1 -2
  4. package/dist/check.d.ts +18 -0
  5. package/dist/check.js +37 -17
  6. package/dist/cli.js +2 -2
  7. package/dist/config/next-config.d.ts +2 -1
  8. package/dist/config/next-config.js +3 -0
  9. package/dist/config/server-external-packages.d.ts +4 -0
  10. package/dist/config/server-external-packages.js +91 -0
  11. package/dist/deploy.d.ts +7 -0
  12. package/dist/deploy.js +25 -18
  13. package/dist/entries/app-rsc-entry.d.ts +2 -1
  14. package/dist/entries/app-rsc-entry.js +89 -20
  15. package/dist/entries/app-rsc-manifest.js +8 -0
  16. package/dist/entries/pages-client-entry.js +1 -0
  17. package/dist/entries/pages-server-entry.js +1 -0
  18. package/dist/index.js +87 -34
  19. package/dist/init.js +2 -1
  20. package/dist/plugins/middleware-server-only.d.ts +8 -6
  21. package/dist/plugins/middleware-server-only.js +8 -7
  22. package/dist/plugins/og-assets.js +6 -6
  23. package/dist/plugins/optimize-imports.js +1 -1
  24. package/dist/plugins/remove-console.js +1 -1
  25. package/dist/plugins/strip-server-exports.js +2 -1
  26. package/dist/routing/app-route-graph.d.ts +6 -2
  27. package/dist/routing/app-route-graph.js +61 -13
  28. package/dist/routing/app-router.d.ts +5 -0
  29. package/dist/routing/app-router.js +5 -0
  30. package/dist/routing/file-matcher.d.ts +5 -0
  31. package/dist/routing/file-matcher.js +7 -1
  32. package/dist/server/app-action-request.d.ts +4 -0
  33. package/dist/server/app-action-request.js +9 -0
  34. package/dist/server/app-bfcache-identity.d.ts +12 -2
  35. package/dist/server/app-bfcache-identity.js +52 -23
  36. package/dist/server/app-browser-entry.js +42 -147
  37. package/dist/server/app-browser-history-controller.d.ts +2 -1
  38. package/dist/server/app-browser-history-controller.js +6 -2
  39. package/dist/server/app-browser-server-action-client.d.ts +32 -0
  40. package/dist/server/app-browser-server-action-client.js +128 -0
  41. package/dist/server/app-fallback-renderer.d.ts +3 -1
  42. package/dist/server/app-fallback-renderer.js +6 -2
  43. package/dist/server/app-history-state.d.ts +1 -2
  44. package/dist/server/app-history-state.js +1 -1
  45. package/dist/server/app-page-boundary-render.d.ts +3 -0
  46. package/dist/server/app-page-boundary-render.js +14 -3
  47. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  48. package/dist/server/app-page-cache-finalizer.js +8 -2
  49. package/dist/server/app-page-dispatch.d.ts +33 -4
  50. package/dist/server/app-page-dispatch.js +62 -90
  51. package/dist/server/app-page-element-builder.d.ts +7 -1
  52. package/dist/server/app-page-element-builder.js +56 -19
  53. package/dist/server/app-page-head.d.ts +16 -1
  54. package/dist/server/app-page-head.js +44 -22
  55. package/dist/server/app-page-method.js +1 -1
  56. package/dist/server/app-page-params.d.ts +2 -1
  57. package/dist/server/app-page-params.js +8 -1
  58. package/dist/server/app-page-ppr-runtime.d.ts +7 -0
  59. package/dist/server/app-page-ppr-runtime.js +70 -0
  60. package/dist/server/app-page-probe.d.ts +1 -0
  61. package/dist/server/app-page-probe.js +1 -1
  62. package/dist/server/app-page-render.d.ts +4 -1
  63. package/dist/server/app-page-render.js +8 -3
  64. package/dist/server/app-page-request.d.ts +8 -1
  65. package/dist/server/app-page-request.js +23 -11
  66. package/dist/server/app-page-route-wiring.d.ts +6 -1
  67. package/dist/server/app-page-route-wiring.js +30 -8
  68. package/dist/server/app-page-search-params-observation.d.ts +4 -2
  69. package/dist/server/app-page-search-params-observation.js +11 -7
  70. package/dist/server/app-page-stream.js +2 -1
  71. package/dist/server/app-ppr-fallback-shell-render.d.ts +4 -3
  72. package/dist/server/app-route-handler-cache.js +1 -1
  73. package/dist/server/app-route-handler-dispatch.js +3 -2
  74. package/dist/server/app-route-handler-execution.js +5 -3
  75. package/dist/server/app-route-handler-policy.d.ts +1 -1
  76. package/dist/server/app-route-handler-policy.js +1 -6
  77. package/dist/server/app-route-module-loader.d.ts +2 -0
  78. package/dist/server/app-route-module-loader.js +1 -0
  79. package/dist/server/app-router-entry.js +7 -6
  80. package/dist/server/app-rsc-errors.d.ts +12 -1
  81. package/dist/server/app-rsc-errors.js +29 -3
  82. package/dist/server/app-rsc-handler.d.ts +15 -13
  83. package/dist/server/app-rsc-handler.js +28 -33
  84. package/dist/server/app-rsc-response-finalizer.js +5 -1
  85. package/dist/server/app-rsc-route-matching.d.ts +7 -0
  86. package/dist/server/app-rsc-route-matching.js +36 -3
  87. package/dist/server/app-segment-config.d.ts +1 -0
  88. package/dist/server/app-segment-config.js +32 -2
  89. package/dist/server/app-server-action-execution.d.ts +4 -0
  90. package/dist/server/app-server-action-execution.js +42 -11
  91. package/dist/server/app-ssr-entry.js +11 -6
  92. package/dist/server/app-static-generation.d.ts +1 -0
  93. package/dist/server/app-static-generation.js +1 -0
  94. package/dist/server/headers.d.ts +3 -1
  95. package/dist/server/headers.js +3 -1
  96. package/dist/server/metadata-route-response.d.ts +1 -1
  97. package/dist/server/prod-server.js +15 -6
  98. package/dist/server/worker-utils.d.ts +2 -1
  99. package/dist/server/worker-utils.js +7 -1
  100. package/dist/shims/dynamic.js +1 -1
  101. package/dist/shims/error-boundary.d.ts +19 -1
  102. package/dist/shims/error-boundary.js +11 -1
  103. package/dist/shims/headers.d.ts +3 -1
  104. package/dist/shims/headers.js +16 -5
  105. package/dist/shims/link.js +16 -12
  106. package/dist/shims/metadata.d.ts +3 -2
  107. package/dist/shims/metadata.js +8 -4
  108. package/dist/shims/router.js +13 -2
  109. package/dist/typegen.js +6 -5
  110. package/dist/utils/path.d.ts +2 -1
  111. package/dist/utils/path.js +1 -1
  112. package/dist/utils/project.d.ts +4 -0
  113. package/dist/utils/project.js +5 -1
  114. package/package.json +7 -3
@@ -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
  /**
@@ -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",
package/dist/check.d.ts CHANGED
@@ -59,22 +59,40 @@ type CheckResult = {
59
59
  declare function hasFreeCjsGlobal(content: string): boolean;
60
60
  /**
61
61
  * Scan source files for `import ... from 'next/...'` statements.
62
+ *
63
+ * `root` must be forward-slash: it is passed to `findSourceFiles` (which
64
+ * requires it) and used as the base of `path.posix.relative`, which only yields
65
+ * a canonical relative path when both operands are forward-slash.
62
66
  */
63
67
  declare function scanImports(root: string): CheckItem[];
64
68
  /**
65
69
  * Analyze next.config.js/mjs/ts for supported and unsupported options.
70
+ *
71
+ * `root` must be forward-slash — joined with `path.posix.join`. Only called
72
+ * from `runCheck`, which normalizes it.
66
73
  */
67
74
  declare function analyzeConfig(root: string): CheckItem[];
68
75
  /**
69
76
  * Check package.json dependencies for known libraries.
77
+ *
78
+ * `root` must be forward-slash — joined with `path.posix.join`. Only called
79
+ * from `runCheck`, which normalizes it.
70
80
  */
71
81
  declare function checkLibraries(root: string): CheckItem[];
72
82
  /**
73
83
  * Check file conventions (pages, app directory, middleware, etc.)
84
+ *
85
+ * `root` must be forward-slash — joined with `path.posix.join`, passed to
86
+ * `findDir`, and used as the base of `path.posix.relative`. Only called from
87
+ * `runCheck`, which normalizes it.
74
88
  */
75
89
  declare function checkConventions(root: string): CheckItem[];
76
90
  /**
77
91
  * Run the full compatibility check.
92
+ *
93
+ * `root` must be forward-slash — callers normalize it at the CLI entry, and it
94
+ * is forwarded to `scanImports` / `checkConventions` / `findDir`, which build
95
+ * paths with `path.posix.*`.
78
96
  */
79
97
  declare function runCheck(root: string): CheckResult;
80
98
  /**
package/dist/check.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { detectPackageManager, findDir } from "./utils/project.js";
2
- import { normalizePathSeparators } from "./utils/path.js";
3
2
  import fs from "node:fs";
4
3
  import path from "node:path";
5
4
  import { parseAst } from "vite";
@@ -331,6 +330,11 @@ const LIBRARY_SUPPORT = {
331
330
  };
332
331
  /**
333
332
  * Recursively find all source files in a directory.
333
+ *
334
+ * `dir` must be forward-slash, and the returned paths are forward-slash too:
335
+ * each entry is joined with `path.posix.join`, which only stays canonical when
336
+ * the base already is. This keeps downstream substring checks (e.g.
337
+ * `f.includes("/api/")`) and reported paths consistent across platforms.
334
338
  */
335
339
  function findSourceFiles(dir, extensions = [
336
340
  ".ts",
@@ -343,7 +347,7 @@ function findSourceFiles(dir, extensions = [
343
347
  if (!fs.existsSync(dir)) return results;
344
348
  const entries = fs.readdirSync(dir, { withFileTypes: true });
345
349
  for (const entry of entries) {
346
- const fullPath = normalizePathSeparators(path.join(dir, entry.name));
350
+ const fullPath = path.posix.join(dir, entry.name);
347
351
  if (entry.isDirectory()) {
348
352
  if (entry.name === "node_modules" || entry.name === ".next" || entry.name === "dist" || entry.name === ".git") continue;
349
353
  results.push(...findSourceFiles(fullPath, extensions));
@@ -547,6 +551,10 @@ function hasFreeCjsGlobal(content) {
547
551
  }
548
552
  /**
549
553
  * Scan source files for `import ... from 'next/...'` statements.
554
+ *
555
+ * `root` must be forward-slash: it is passed to `findSourceFiles` (which
556
+ * requires it) and used as the base of `path.posix.relative`, which only yields
557
+ * a canonical relative path when both operands are forward-slash.
550
558
  */
551
559
  function scanImports(root) {
552
560
  const files = findSourceFiles(root);
@@ -564,7 +572,7 @@ function scanImports(root) {
564
572
  if (mod.startsWith("next/") || mod === "next" || mod === "server-only" || mod === "client-only") {
565
573
  const normalized = mod === "next" ? "next" : mod;
566
574
  if (!importUsage.has(normalized)) importUsage.set(normalized, []);
567
- const relFile = normalizePathSeparators(path.relative(root, file));
575
+ const relFile = path.posix.relative(root, file);
568
576
  const usedInFiles = importUsage.get(normalized) ?? [];
569
577
  if (!usedInFiles.includes(relFile)) usedInFiles.push(relFile);
570
578
  }
@@ -691,6 +699,9 @@ function collectConfigKeys(source) {
691
699
  }
692
700
  /**
693
701
  * Analyze next.config.js/mjs/ts for supported and unsupported options.
702
+ *
703
+ * `root` must be forward-slash — joined with `path.posix.join`. Only called
704
+ * from `runCheck`, which normalizes it.
694
705
  */
695
706
  function analyzeConfig(root) {
696
707
  const configFiles = [
@@ -702,7 +713,7 @@ function analyzeConfig(root) {
702
713
  ];
703
714
  let configPath = null;
704
715
  for (const f of configFiles) {
705
- const p = path.join(root, f);
716
+ const p = path.posix.join(root, f);
706
717
  if (fs.existsSync(p)) {
707
718
  configPath = p;
708
719
  break;
@@ -757,9 +768,12 @@ function analyzeConfig(root) {
757
768
  }
758
769
  /**
759
770
  * Check package.json dependencies for known libraries.
771
+ *
772
+ * `root` must be forward-slash — joined with `path.posix.join`. Only called
773
+ * from `runCheck`, which normalizes it.
760
774
  */
761
775
  function checkLibraries(root) {
762
- const pkgPath = path.join(root, "package.json");
776
+ const pkgPath = path.posix.join(root, "package.json");
763
777
  if (!fs.existsSync(pkgPath)) return [];
764
778
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
765
779
  const allDeps = {
@@ -777,17 +791,19 @@ function checkLibraries(root) {
777
791
  }
778
792
  /**
779
793
  * Check file conventions (pages, app directory, middleware, etc.)
794
+ *
795
+ * `root` must be forward-slash — joined with `path.posix.join`, passed to
796
+ * `findDir`, and used as the base of `path.posix.relative`. Only called from
797
+ * `runCheck`, which normalizes it.
780
798
  */
781
799
  function checkConventions(root) {
782
800
  const items = [];
783
- const pagesDir = findDir(root, "pages", path.join("src", "pages"));
784
- const appDirPath = findDir(root, "app", path.join("src", "app"));
785
- const hasPages = pagesDir !== null;
786
- const hasApp = appDirPath !== null;
787
- const hasProxy = fs.existsSync(path.join(root, "proxy.ts")) || fs.existsSync(path.join(root, "proxy.js"));
788
- const hasMiddleware = fs.existsSync(path.join(root, "middleware.ts")) || fs.existsSync(path.join(root, "middleware.js"));
801
+ const pagesDir = findDir(root, "pages", "src/pages");
802
+ const appDirPath = findDir(root, "app", "src/app");
803
+ const hasProxy = fs.existsSync(path.posix.join(root, "proxy.ts")) || fs.existsSync(path.posix.join(root, "proxy.js"));
804
+ const hasMiddleware = fs.existsSync(path.posix.join(root, "middleware.ts")) || fs.existsSync(path.posix.join(root, "middleware.js"));
789
805
  if (pagesDir !== null) {
790
- const isSrc = pagesDir.includes(path.join("src", "pages"));
806
+ const isSrc = pagesDir.includes("src/pages");
791
807
  items.push({
792
808
  name: isSrc ? "Pages Router (src/pages/)" : "Pages Router (pages/)",
793
809
  status: "supported"
@@ -813,7 +829,7 @@ function checkConventions(root) {
813
829
  });
814
830
  }
815
831
  if (appDirPath !== null) {
816
- const isSrc = appDirPath.includes(path.join("src", "app"));
832
+ const isSrc = appDirPath.includes("src/app");
817
833
  items.push({
818
834
  name: isSrc ? "App Router (src/app/)" : "App Router (app/)",
819
835
  status: "supported"
@@ -858,12 +874,12 @@ function checkConventions(root) {
858
874
  name: "middleware.ts (deprecated in Next.js 16)",
859
875
  status: "supported"
860
876
  });
861
- if (!hasPages && !hasApp) items.push({
877
+ if (pagesDir === null && appDirPath === null) items.push({
862
878
  name: "No pages/ or app/ directory found",
863
879
  status: "unsupported",
864
880
  detail: "vinext requires a pages/ or app/ directory"
865
881
  });
866
- const pkgPath = path.join(root, "package.json");
882
+ const pkgPath = path.posix.join(root, "package.json");
867
883
  if (fs.existsSync(pkgPath)) {
868
884
  if (JSON.parse(fs.readFileSync(pkgPath, "utf-8")).type !== "module") items.push({
869
885
  name: "Missing \"type\": \"module\" in package.json",
@@ -877,7 +893,7 @@ function checkConventions(root) {
877
893
  const cjsGlobalFiles = [];
878
894
  for (const file of allSourceFiles) {
879
895
  const content = fs.readFileSync(file, "utf-8");
880
- const rel = normalizePathSeparators(path.relative(root, file));
896
+ const rel = path.posix.relative(root, file);
881
897
  if (viewTransitionRegex.test(content)) viewTransitionFiles.push(rel);
882
898
  if (hasFreeCjsGlobal(content)) cjsGlobalFiles.push(rel);
883
899
  }
@@ -892,7 +908,7 @@ function checkConventions(root) {
892
908
  "postcss.config.js",
893
909
  "postcss.config.cjs"
894
910
  ]) {
895
- const configPath = path.join(root, configFile);
911
+ const configPath = path.posix.join(root, configFile);
896
912
  if (fs.existsSync(configPath)) {
897
913
  const content = fs.readFileSync(configPath, "utf-8");
898
914
  if (/plugins\s*:\s*\[\s*['"]/.test(content)) items.push({
@@ -913,6 +929,10 @@ function checkConventions(root) {
913
929
  }
914
930
  /**
915
931
  * Run the full compatibility check.
932
+ *
933
+ * `root` must be forward-slash — callers normalize it at the CLI entry, and it
934
+ * is forwarded to `scanImports` / `checkConventions` / `findDir`, which build
935
+ * paths with `path.posix.*`.
916
936
  */
917
937
  function runCheck(root) {
918
938
  const imports = scanImports(root);
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { detectPackageManager, ensureViteConfigCompatibility, hasAppDir, hasViteConfig } from "./utils/project.js";
3
3
  import { formatReport, runCheck } from "./check.js";
4
4
  import { parseArgs } from "./cli-args.js";
5
+ import { normalizePathSeparators } from "./utils/path.js";
5
6
  import { PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
6
7
  import { createRscCompatibilityId, loadNextConfig, resolveNextConfig } from "./config/next-config.js";
7
8
  import { generateRouteTypes } from "./typegen.js";
@@ -440,10 +441,9 @@ async function deployCommand() {
440
441
  }
441
442
  async function check() {
442
443
  if (parseArgs(rawArgs).help) return printHelp("check");
443
- const root = process.cwd();
444
444
  console.log(`\n vinext check\n`);
445
445
  console.log(" Scanning project...\n");
446
- const result = runCheck(root);
446
+ const result = runCheck(normalizePathSeparators(process.cwd()));
447
447
  console.log(formatReport(result));
448
448
  }
449
449
  async function typegen() {
@@ -272,7 +272,8 @@ type ResolvedNextConfig = {
272
272
  aliases: Record<string, string>; /** Extra allowed origins for dev server access (from allowedDevOrigins). */
273
273
  allowedDevOrigins: string[]; /** Extra allowed origins for server action CSRF validation (from experimental.serverActions.allowedOrigins). */
274
274
  serverActionsAllowedOrigins: string[]; /** Packages whose barrel imports should be optimized (from experimental.optimizePackageImports). */
275
- optimizePackageImports: string[]; /** Inline app CSS into production HTML (from experimental.inlineCss). */
275
+ optimizePackageImports: string[]; /** Packages explicitly requested for server/client transpilation. */
276
+ transpilePackages: string[]; /** Inline app CSS into production HTML (from experimental.inlineCss). */
276
277
  inlineCss: boolean; /** Parsed body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). Defaults to 1MB. */
277
278
  serverActionsBodySizeLimit: number; /** Verbatim body size limit config value (e.g. "2mb") for the "Body exceeded {limit} limit" error. Defaults to "1 MB". */
278
279
  serverActionsBodySizeLimitLabel: string; /** Route-level expire fallback in seconds for ISR entries with numeric revalidate. */
@@ -569,6 +569,7 @@ async function resolveNextConfig(config, root = process.cwd(), options = {}) {
569
569
  allowedDevOrigins: [],
570
570
  serverActionsAllowedOrigins: [],
571
571
  optimizePackageImports: [],
572
+ transpilePackages: [],
572
573
  inlineCss: false,
573
574
  serverActionsBodySizeLimit: 1 * 1024 * 1024,
574
575
  serverActionsBodySizeLimitLabel: "1 MB",
@@ -666,6 +667,7 @@ async function resolveNextConfig(config, root = process.cwd(), options = {}) {
666
667
  const topLevelServerExternalPackages = Array.isArray(config.serverExternalPackages) ? readStringArray(config.serverExternalPackages) : void 0;
667
668
  const legacyServerComponentsExternal = readStringArray(experimental?.serverComponentsExternalPackages);
668
669
  const serverExternalPackages = topLevelServerExternalPackages ?? legacyServerComponentsExternal;
670
+ const transpilePackages = readStringArray(config.transpilePackages);
669
671
  if (experimental?.swcEnvOptions !== void 0) console.warn("[vinext] next.config option \"experimental.swcEnvOptions\" is not applicable and will be ignored (vinext uses Vite, not SWC). A Vite-compatible polyfill solution may be explored in the future.");
670
672
  if (experimental?.rootParams !== void 0) console.warn("[vinext] `experimental.rootParams` is no longer needed, because `next/root-params` is available by default. You can remove it from next.config.(js|mjs|ts).");
671
673
  const useLightningcss = experimental?.useLightningcss === true;
@@ -728,6 +730,7 @@ async function resolveNextConfig(config, root = process.cwd(), options = {}) {
728
730
  allowedDevOrigins,
729
731
  serverActionsAllowedOrigins,
730
732
  optimizePackageImports,
733
+ transpilePackages,
731
734
  inlineCss,
732
735
  serverActionsBodySizeLimit,
733
736
  serverActionsBodySizeLimitLabel,
@@ -0,0 +1,4 @@
1
+ //#region src/config/server-external-packages.d.ts
2
+ declare function mergeServerExternalPackages(userPackages?: readonly string[], transpilePackages?: readonly string[]): string[];
3
+ //#endregion
4
+ export { mergeServerExternalPackages };
@@ -0,0 +1,91 @@
1
+ //#region src/config/server-external-packages.ts
2
+ const DEFAULT_SERVER_EXTERNAL_PACKAGES = [
3
+ "@alinea/generated",
4
+ "@appsignal/nodejs",
5
+ "@aws-sdk/client-s3",
6
+ "@aws-sdk/s3-presigned-post",
7
+ "@blockfrost/blockfrost-js",
8
+ "@highlight-run/node",
9
+ "@huggingface/transformers",
10
+ "@jpg-store/lucid-cardano",
11
+ "@libsql/client",
12
+ "@mikro-orm/core",
13
+ "@mikro-orm/knex",
14
+ "@node-rs/argon2",
15
+ "@node-rs/bcrypt",
16
+ "@prisma/client",
17
+ "@react-pdf/renderer",
18
+ "@sentry/profiling-node",
19
+ "@sparticuz/chromium",
20
+ "@sparticuz/chromium-min",
21
+ "@statsig/statsig-node-core",
22
+ "@swc/core",
23
+ "@xenova/transformers",
24
+ "@zenstackhq/runtime",
25
+ "argon2",
26
+ "autoprefixer",
27
+ "aws-crt",
28
+ "bcrypt",
29
+ "better-sqlite3",
30
+ "canvas",
31
+ "chromadb-default-embed",
32
+ "config",
33
+ "cpu-features",
34
+ "cypress",
35
+ "dd-trace",
36
+ "eslint",
37
+ "express",
38
+ "firebase-admin",
39
+ "htmlrewriter",
40
+ "import-in-the-middle",
41
+ "isolated-vm",
42
+ "jest",
43
+ "jsdom",
44
+ "keyv",
45
+ "libsql",
46
+ "mdx-bundler",
47
+ "mongodb",
48
+ "mongoose",
49
+ "newrelic",
50
+ "next-mdx-remote",
51
+ "next-seo",
52
+ "node-cron",
53
+ "node-pty",
54
+ "node-web-audio-api",
55
+ "onnxruntime-node",
56
+ "oslo",
57
+ "pg",
58
+ "pino",
59
+ "pino-pretty",
60
+ "pino-roll",
61
+ "playwright",
62
+ "playwright-core",
63
+ "postcss",
64
+ "prettier",
65
+ "prisma",
66
+ "puppeteer",
67
+ "puppeteer-core",
68
+ "ravendb",
69
+ "require-in-the-middle",
70
+ "rimraf",
71
+ "sharp",
72
+ "shiki",
73
+ "sqlite3",
74
+ "thread-stream",
75
+ "ts-morph",
76
+ "ts-node",
77
+ "typescript",
78
+ "vscode-oniguruma",
79
+ "webpack",
80
+ "websocket",
81
+ "zeromq"
82
+ ];
83
+ function mergeServerExternalPackages(userPackages = [], transpilePackages = []) {
84
+ const transpiled = new Set(transpilePackages);
85
+ const conflicts = userPackages.filter((name) => transpiled.has(name));
86
+ if (conflicts.length > 0) throw new Error(`The packages specified in the 'transpilePackages' conflict with the 'serverExternalPackages': ${conflicts.join(", ")}`);
87
+ const defaults = DEFAULT_SERVER_EXTERNAL_PACKAGES.filter((name) => !transpiled.has(name));
88
+ return [...new Set([...defaults, ...userPackages])];
89
+ }
90
+ //#endregion
91
+ export { mergeServerExternalPackages };
package/dist/deploy.d.ts CHANGED
@@ -58,6 +58,13 @@ declare function formatMissingCloudflarePluginError(options: {
58
58
  isAppRouter: boolean;
59
59
  configFile?: string;
60
60
  }): string;
61
+ /**
62
+ * Detect the project structure (router, config, worker entry, package name).
63
+ *
64
+ * `root` must be forward-slash — it is joined with `path.posix.*`, passed to
65
+ * `findDir` / `hasAppDir`, and used as the base of `path.posix.basename`.
66
+ * Callers normalize it at the deploy entry.
67
+ */
61
68
  declare function detectProject(root: string): ProjectInfo;
62
69
  /** @see {@link _ensureESModule} */
63
70
  declare const ensureESModule: typeof ensureESModule$1;
package/dist/deploy.js CHANGED
@@ -1,6 +1,7 @@
1
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
+ import { normalizePathSeparators } from "./utils/path.js";
4
5
  import { loadNextConfig, resolveNextConfig } from "./config/next-config.js";
5
6
  import { getReactUpgradeDeps } from "./init.js";
6
7
  import { runTPR } from "./cloudflare/tpr.js";
@@ -107,23 +108,30 @@ function formatMissingCloudflarePluginError(options) {
107
108
  const configRef = options.configFile ? options.configFile : "your Vite config";
108
109
  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
110
  }
111
+ /**
112
+ * Detect the project structure (router, config, worker entry, package name).
113
+ *
114
+ * `root` must be forward-slash — it is joined with `path.posix.*`, passed to
115
+ * `findDir` / `hasAppDir`, and used as the base of `path.posix.basename`.
116
+ * Callers normalize it at the deploy entry.
117
+ */
110
118
  function detectProject(root) {
111
119
  const hasApp = hasAppDir(root);
112
- const hasPages = findDir(root, "pages", path.join("src", "pages")) !== null;
120
+ const hasPages = findDir(root, "pages", "src/pages") !== null;
113
121
  const isAppRouter = hasApp;
114
122
  const isPagesRouter = !hasApp && hasPages;
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"));
123
+ const hasViteConfig = fs.existsSync(path.posix.join(root, "vite.config.ts")) || fs.existsSync(path.posix.join(root, "vite.config.js")) || fs.existsSync(path.posix.join(root, "vite.config.mjs"));
116
124
  const wranglerConfigExists = hasWranglerConfig(root);
117
- const hasWorkerEntry = fs.existsSync(path.join(root, "worker", "index.ts")) || fs.existsSync(path.join(root, "worker", "index.js"));
125
+ const hasWorkerEntry = fs.existsSync(path.posix.join(root, "worker", "index.ts")) || fs.existsSync(path.posix.join(root, "worker", "index.js"));
118
126
  const hasCloudflarePlugin = findInNodeModules(root, "@cloudflare/vite-plugin") !== null;
119
127
  const hasRscPlugin = findInNodeModules(root, "@vitejs/plugin-rsc") !== null;
120
128
  const hasWrangler = findInNodeModules(root, ".bin/wrangler") !== null;
121
- const pkgPath = path.join(root, "package.json");
129
+ const pkgPath = path.posix.join(root, "package.json");
122
130
  let pkg = null;
123
131
  if (fs.existsSync(pkgPath)) try {
124
132
  pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
125
133
  } catch {}
126
- let projectName = path.basename(root);
134
+ let projectName = path.posix.basename(root);
127
135
  if (pkg?.name && typeof pkg.name === "string") projectName = pkg.name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
128
136
  const hasTypeModule = pkg?.type === "module";
129
137
  let hasISR = false;
@@ -380,6 +388,7 @@ import { handleImageOptimization, DEFAULT_DEVICE_SIZES, DEFAULT_IMAGE_SIZES, isI
380
388
  import type { ImageConfig } from "vinext/server/image-optimization";
381
389
  import { cloneRequestWithHeaders, cloneRequestWithUrl, filterInternalHeaders, isOpenRedirectShaped } from "vinext/server/request-pipeline";
382
390
  import { notFoundStaticAssetResponse } from "vinext/server/http-error-responses";
391
+ import { finalizeMissingStaticAssetResponse } from "vinext/server/worker-utils";
383
392
  import { assetPrefixPathname, isNextStaticPath } from "vinext/utils/asset-prefix";
384
393
  import { hasBasePath, stripBasePath } from "vinext/utils/base-path";
385
394
 
@@ -438,15 +447,11 @@ export default {
438
447
  return new Response("This page could not be found", { status: 404 });
439
448
  }
440
449
 
441
- // Invalid \`_next/static/*\` paths short-circuit with a plain-text 404
442
- // instead of falling through to renderPage (which would render the full
443
- // HTML 404 page with bootstrap scripts + CSS). Valid assets are served
444
- // by Cloudflare's ASSETS binding BEFORE the worker runs; only misses
445
- // reach this code. Matches Next.js (#1337):
446
- // packages/next/src/server/lib/router-server.ts
447
- if (isNextStaticPath(pathname, basePath, assetPathPrefix)) {
448
- return notFoundStaticAssetResponse();
449
- }
450
+ // Valid assets are served by Cloudflare's ASSETS binding before the
451
+ // worker runs. Missing asset-shaped requests still need to reach
452
+ // middleware so it can rewrite or respond; a final 404 is converted
453
+ // back to Next.js's canonical plain-text static-file response below.
454
+ const missingBuildAsset = isNextStaticPath(pathname, basePath, assetPathPrefix);
450
455
 
451
456
  // Strip internal headers from inbound requests so they cannot be
452
457
  // forged to influence routing or impersonate internal state.
@@ -538,10 +543,12 @@ export default {
538
543
 
539
544
  const result = await runPagesRequest(request, deps);
540
545
  if (result.type === "response") {
541
- return result.response;
546
+ return finalizeMissingStaticAssetResponse(result.response, missingBuildAsset);
542
547
  }
543
548
  // Should not reach here for prod/worker (all callbacks supplied).
544
- return new Response("This page could not be found", { status: 404 });
549
+ return missingBuildAsset
550
+ ? notFoundStaticAssetResponse()
551
+ : new Response("This page could not be found", { status: 404 });
545
552
 
546
553
  } catch (error) {
547
554
  console.error("[vinext] Worker error:", error);
@@ -945,7 +952,7 @@ async function deploy(options) {
945
952
  mode: "production"
946
953
  });
947
954
  console.log("\n vinext deploy\n");
948
- const info = detectProject(root);
955
+ const info = detectProject(normalizePathSeparators(root));
949
956
  if (!info.isAppRouter && !info.isPagesRouter) {
950
957
  console.error(" Error: No app/ or pages/ directory found.");
951
958
  console.error(" vinext deploy requires a Next.js project with an app/ or pages/ directory");
@@ -973,7 +980,7 @@ async function deploy(options) {
973
980
  console.log();
974
981
  installDeps(root, missingDeps);
975
982
  const nameOverride = options.name ? info.projectName : void 0;
976
- Object.assign(info, detectProject(root));
983
+ Object.assign(info, detectProject(normalizePathSeparators(root)));
977
984
  if (nameOverride) info.projectName = nameOverride;
978
985
  }
979
986
  if (!info.hasTypeModule) {
@@ -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
  /**