vinext 0.2.0 → 0.2.1

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 (128) hide show
  1. package/dist/build/css-url-assets.d.ts +3 -1
  2. package/dist/build/css-url-assets.js +18 -1
  3. package/dist/build/inject-pregenerated-paths.d.ts +3 -0
  4. package/dist/build/inject-pregenerated-paths.js +4 -1
  5. package/dist/build/prerender-server-entry.d.ts +1 -0
  6. package/dist/build/prerender-server-entry.js +49 -0
  7. package/dist/build/prerender-server-pool.d.ts +44 -0
  8. package/dist/build/prerender-server-pool.js +194 -0
  9. package/dist/build/prerender.js +86 -22
  10. package/dist/build/run-prerender.js +2 -1
  11. package/dist/check.d.ts +4 -6
  12. package/dist/check.js +8 -9
  13. package/dist/cli.js +1 -1
  14. package/dist/client/vinext-next-data.d.ts +4 -1
  15. package/dist/config/config-matchers.js +33 -11
  16. package/dist/config/next-config.d.ts +36 -6
  17. package/dist/config/next-config.js +16 -2
  18. package/dist/entries/app-browser-entry.js +5 -1
  19. package/dist/entries/app-rsc-entry.d.ts +3 -2
  20. package/dist/entries/app-rsc-entry.js +4 -0
  21. package/dist/entries/pages-client-entry.js +14 -3
  22. package/dist/entries/pages-server-entry.js +18 -4
  23. package/dist/index.js +320 -25
  24. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +8 -1
  25. package/dist/packages/cloudflare/src/deploy.js +1 -5
  26. package/dist/packages/cloudflare/src/tpr.js +26 -5
  27. package/dist/plugins/fonts.js +1 -1
  28. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  29. package/dist/plugins/og-assets.js +2 -1
  30. package/dist/plugins/optimize-imports.js +1 -1
  31. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  32. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  33. package/dist/plugins/sass.d.ts +17 -1
  34. package/dist/plugins/sass.js +74 -1
  35. package/dist/plugins/styled-jsx.d.ts +16 -0
  36. package/dist/plugins/styled-jsx.js +149 -0
  37. package/dist/routing/app-route-graph.js +22 -16
  38. package/dist/routing/file-matcher.d.ts +8 -1
  39. package/dist/routing/file-matcher.js +15 -3
  40. package/dist/server/app-browser-entry.js +46 -14
  41. package/dist/server/app-browser-navigation-controller.js +1 -1
  42. package/dist/server/app-browser-server-action-client.js +5 -3
  43. package/dist/server/app-browser-state.d.ts +1 -0
  44. package/dist/server/app-browser-state.js +2 -2
  45. package/dist/server/app-browser-visible-commit.js +1 -1
  46. package/dist/server/app-page-boundary.js +2 -1
  47. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  48. package/dist/server/app-page-cache-finalizer.js +3 -3
  49. package/dist/server/app-page-cache.js +23 -6
  50. package/dist/server/app-page-dispatch.d.ts +1 -0
  51. package/dist/server/app-page-dispatch.js +25 -8
  52. package/dist/server/app-page-element-builder.js +1 -0
  53. package/dist/server/app-page-execution.js +2 -1
  54. package/dist/server/app-page-render-identity.d.ts +1 -0
  55. package/dist/server/app-page-render-identity.js +2 -1
  56. package/dist/server/app-page-render.d.ts +2 -0
  57. package/dist/server/app-page-render.js +67 -16
  58. package/dist/server/app-page-response.d.ts +7 -0
  59. package/dist/server/app-page-response.js +16 -4
  60. package/dist/server/app-page-stream.d.ts +2 -1
  61. package/dist/server/app-page-stream.js +1 -1
  62. package/dist/server/app-route-handler-execution.js +1 -1
  63. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  64. package/dist/server/app-route-tree-prefetch.js +187 -0
  65. package/dist/server/app-router-entry.js +1 -1
  66. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  67. package/dist/server/app-rsc-cache-busting.js +9 -4
  68. package/dist/server/app-rsc-handler.d.ts +6 -0
  69. package/dist/server/app-rsc-handler.js +89 -11
  70. package/dist/server/app-rsc-render-mode.d.ts +3 -2
  71. package/dist/server/app-rsc-render-mode.js +4 -1
  72. package/dist/server/app-rsc-request-normalization.d.ts +3 -4
  73. package/dist/server/app-rsc-request-normalization.js +4 -5
  74. package/dist/server/app-server-action-execution.js +4 -3
  75. package/dist/server/app-ssr-entry.js +3 -3
  76. package/dist/server/cache-control.d.ts +3 -1
  77. package/dist/server/cache-control.js +13 -1
  78. package/dist/server/dev-server.d.ts +11 -1
  79. package/dist/server/dev-server.js +78 -20
  80. package/dist/server/headers.d.ts +6 -2
  81. package/dist/server/headers.js +11 -5
  82. package/dist/server/isr-cache.d.ts +2 -1
  83. package/dist/server/isr-cache.js +7 -3
  84. package/dist/server/pages-data-route.d.ts +4 -2
  85. package/dist/server/pages-data-route.js +18 -4
  86. package/dist/server/pages-dev-module-url.d.ts +2 -1
  87. package/dist/server/pages-dev-module-url.js +6 -3
  88. package/dist/server/pages-node-compat.d.ts +12 -1
  89. package/dist/server/pages-node-compat.js +50 -1
  90. package/dist/server/pages-page-data.d.ts +9 -0
  91. package/dist/server/pages-page-data.js +17 -9
  92. package/dist/server/pages-page-handler.js +19 -7
  93. package/dist/server/pages-page-response.d.ts +1 -0
  94. package/dist/server/pages-page-response.js +3 -2
  95. package/dist/server/pages-request-pipeline.d.ts +8 -6
  96. package/dist/server/pages-request-pipeline.js +30 -6
  97. package/dist/server/pages-router-entry.js +1 -1
  98. package/dist/server/prod-server.d.ts +4 -2
  99. package/dist/server/prod-server.js +38 -22
  100. package/dist/server/request-pipeline.js +1 -1
  101. package/dist/shims/cache-handler.js +8 -1
  102. package/dist/shims/cache.js +3 -0
  103. package/dist/shims/fetch-cache.d.ts +3 -1
  104. package/dist/shims/fetch-cache.js +77 -52
  105. package/dist/shims/form.d.ts +1 -1
  106. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  107. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  108. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
  109. package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
  110. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
  111. package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
  112. package/dist/shims/internal/pages-data-target.d.ts +8 -3
  113. package/dist/shims/internal/pages-data-target.js +9 -4
  114. package/dist/shims/link.d.ts +1 -1
  115. package/dist/shims/link.js +179 -42
  116. package/dist/shims/navigation.d.ts +15 -3
  117. package/dist/shims/navigation.js +202 -39
  118. package/dist/shims/navigation.react-server.js +1 -1
  119. package/dist/shims/request-context.js +18 -0
  120. package/dist/shims/router.js +31 -15
  121. package/dist/shims/unified-request-context.js +1 -0
  122. package/dist/typegen.js +1 -1
  123. package/dist/utils/middleware-request-headers.js +1 -1
  124. package/dist/utils/protocol-headers.d.ts +7 -1
  125. package/dist/utils/protocol-headers.js +7 -1
  126. package/dist/utils/vite-version.d.ts +2 -0
  127. package/dist/utils/vite-version.js +11 -1
  128. package/package.json +2 -2
package/dist/check.d.ts CHANGED
@@ -60,9 +60,8 @@ declare function hasFreeCjsGlobal(content: string): boolean;
60
60
  /**
61
61
  * Scan source files for `import ... from 'next/...'` statements.
62
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.
63
+ * `root` must be forward-slash: it is passed to `findSourceFiles`, which
64
+ * requires it.
66
65
  */
67
66
  declare function scanImports(root: string): CheckItem[];
68
67
  /**
@@ -82,9 +81,8 @@ declare function checkLibraries(root: string): CheckItem[];
82
81
  /**
83
82
  * Check file conventions (pages, app directory, middleware, etc.)
84
83
  *
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.
84
+ * `root` must be forward-slash — joined with `path.posix.join` and passed to
85
+ * `findDir`. Only called from `runCheck`, which normalizes it.
88
86
  */
89
87
  declare function checkConventions(root: string): CheckItem[];
90
88
  /**
package/dist/check.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { detectPackageManager, findDir } from "./utils/project.js";
2
+ import { normalizePathSeparators } from "./utils/path.js";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
5
  import { parseAst } from "vite";
@@ -300,7 +301,7 @@ const CONFIG_SUPPORT = {
300
301
  },
301
302
  reactStrictMode: {
302
303
  status: "partial",
303
- detail: "config option recognized but not yet enforced; root is not wrapped in <React.StrictMode>"
304
+ detail: "enforced for the Pages Router (client root wrapped in <React.StrictMode> when true); App Router is not yet wrapped (Next.js defaults App Router strict mode on)"
304
305
  },
305
306
  poweredByHeader: {
306
307
  status: "supported",
@@ -588,9 +589,8 @@ function hasFreeCjsGlobal(content) {
588
589
  /**
589
590
  * Scan source files for `import ... from 'next/...'` statements.
590
591
  *
591
- * `root` must be forward-slash: it is passed to `findSourceFiles` (which
592
- * requires it) and used as the base of `path.posix.relative`, which only yields
593
- * a canonical relative path when both operands are forward-slash.
592
+ * `root` must be forward-slash: it is passed to `findSourceFiles`, which
593
+ * requires it.
594
594
  */
595
595
  function scanImports(root) {
596
596
  const files = findSourceFiles(root);
@@ -608,7 +608,7 @@ function scanImports(root) {
608
608
  if (mod.startsWith("next/") || mod === "next" || mod === "server-only" || mod === "client-only") {
609
609
  const normalized = mod === "next" ? "next" : mod;
610
610
  if (!importUsage.has(normalized)) importUsage.set(normalized, []);
611
- const relFile = path.posix.relative(root, file);
611
+ const relFile = normalizePathSeparators(path.relative(root, file));
612
612
  const usedInFiles = importUsage.get(normalized) ?? [];
613
613
  if (!usedInFiles.includes(relFile)) usedInFiles.push(relFile);
614
614
  }
@@ -830,9 +830,8 @@ function checkLibraries(root) {
830
830
  /**
831
831
  * Check file conventions (pages, app directory, middleware, etc.)
832
832
  *
833
- * `root` must be forward-slash — joined with `path.posix.join`, passed to
834
- * `findDir`, and used as the base of `path.posix.relative`. Only called from
835
- * `runCheck`, which normalizes it.
833
+ * `root` must be forward-slash — joined with `path.posix.join` and passed to
834
+ * `findDir`. Only called from `runCheck`, which normalizes it.
836
835
  */
837
836
  function checkConventions(root) {
838
837
  const items = [];
@@ -931,7 +930,7 @@ function checkConventions(root) {
931
930
  const cjsGlobalFiles = [];
932
931
  for (const file of allSourceFiles) {
933
932
  const content = fs.readFileSync(file, "utf-8");
934
- const rel = path.posix.relative(root, file);
933
+ const rel = normalizePathSeparators(path.relative(root, file));
935
934
  if (viewTransitionRegex.test(content)) viewTransitionFiles.push(rel);
936
935
  if (hasFreeCjsGlobal(content)) cjsGlobalFiles.push(rel);
937
936
  }
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { detectPackageManager, ensureViteConfigCompatibility, hasAppDir, hasViteConfig } from "./utils/project.js";
3
+ import { normalizePathSeparators } from "./utils/path.js";
3
4
  import { formatReport, runCheck } from "./check.js";
4
5
  import { parseArgs } from "./cli-args.js";
5
6
  import { createDevServerConfigPlugin, normalizeDevServerHostname } from "./cli-dev-config.js";
6
- import { normalizePathSeparators } from "./utils/path.js";
7
7
  import { PHASE_PRODUCTION_BUILD } from "./shims/constants.js";
8
8
  import { createRscCompatibilityId, loadNextConfig, resolveNextConfig } from "./config/next-config.js";
9
9
  import { generateRouteTypes } from "./typegen.js";
@@ -6,6 +6,7 @@ type VinextLinkPrefetchRoute = {
6
6
  documentOnly?: boolean;
7
7
  isDynamic: boolean;
8
8
  patternParts: string[];
9
+ requiresDynamicNavigationRequest?: boolean;
9
10
  };
10
11
  /**
11
12
  * Pages Router route pattern exposed to the client so the App Router's
@@ -22,13 +23,15 @@ type VinextPagesLinkPrefetchRoute = {
22
23
  documentOnly?: boolean;
23
24
  isDynamic: boolean;
24
25
  patternParts: string[];
26
+ requiresDynamicNavigationRequest?: boolean;
25
27
  };
26
28
  type VinextNextData = {
27
29
  /** vinext-specific additions (not part of Next.js upstream). */__vinext?: {
28
30
  /** Absolute URL of the page module for dynamic import. */pageModuleUrl?: string; /** Absolute URL of the `_app` module for dynamic import. */
29
31
  appModuleUrl?: string; /** True when the Pages Router server has middleware/proxy configured. */
30
32
  hasMiddleware?: boolean; /** True when build-time rewrites can affect the initial Pages Router ready state. */
31
- hasRewrites?: boolean;
33
+ hasRewrites?: boolean; /** Server-resolved Pages route URL used to hydrate fallback shells behind rewrites. */
34
+ routeUrl?: string;
32
35
  };
33
36
  } & NEXT_DATA;
34
37
  type BrowserVinextNextData = NonNullable<Window["__NEXT_DATA__"]> & VinextNextData;
@@ -133,7 +133,7 @@ function _getRedirectIndex(redirects) {
133
133
  const alternation = m[1];
134
134
  const optional = m[2] === "?";
135
135
  const suffix = "/" + m[3];
136
- const altRe = safeRegExp("^(?:" + alternation + ")$");
136
+ const altRe = safeRegExp("^(?:" + alternation + ")$", "i");
137
137
  if (!altRe) {
138
138
  linear.push([i, redirect]);
139
139
  continue;
@@ -145,9 +145,10 @@ function _getRedirectIndex(redirects) {
145
145
  redirect,
146
146
  originalIndex: i
147
147
  };
148
- const bucket = localeStatic.get(suffix);
148
+ const bucketKey = suffix.toLowerCase();
149
+ const bucket = localeStatic.get(bucketKey);
149
150
  if (bucket) bucket.push(entry);
150
- else localeStatic.set(suffix, [entry]);
151
+ else localeStatic.set(bucketKey, [entry]);
151
152
  } else linear.push([i, redirect]);
152
153
  }
153
154
  index = {
@@ -393,7 +394,7 @@ function normalizeHost(hostHeader, fallbackHostname) {
393
394
  */
394
395
  function applyMiddlewareRequestHeaders(middlewareHeaders, request, options = {}) {
395
396
  const nextHeaders = buildRequestHeadersFromMiddlewareResponse(request.headers, middlewareHeaders, options);
396
- for (const key of Object.keys(middlewareHeaders)) if (key.startsWith("x-middleware-")) delete middlewareHeaders[key];
397
+ for (const key of Object.keys(middlewareHeaders)) if (key.startsWith("x-middleware-") && key !== "x-middleware-cache") delete middlewareHeaders[key];
397
398
  if (nextHeaders) request = new Request(request.url, {
398
399
  method: request.method,
399
400
  headers: nextHeaders,
@@ -529,6 +530,12 @@ function extractConstraint(str, re) {
529
530
  function stripTrailingSlashForConfigMatch(value) {
530
531
  return value.length > 1 && value.endsWith("/") ? value.slice(0, -1) : value;
531
532
  }
533
+ function configPathEquals(a, b) {
534
+ return a.toLowerCase() === b.toLowerCase();
535
+ }
536
+ function configPathStartsWith(pathname, prefix) {
537
+ return pathname.slice(0, prefix.length).toLowerCase() === prefix.toLowerCase();
538
+ }
532
539
  function matchConfigPattern(pathname, pattern) {
533
540
  const pathnameHadTrailingSlash = pathname.length > 1 && pathname.endsWith("/");
534
541
  pathname = stripTrailingSlashForConfigMatch(pathname);
@@ -558,7 +565,7 @@ function matchConfigPattern(pathname, pattern) {
558
565
  }
559
566
  } else if (tok[0] === ".") regexStr += "\\.";
560
567
  else regexStr += tok[0];
561
- const re = safeRegExp("^" + regexStr + "$");
568
+ const re = safeRegExp("^" + regexStr + "$", "i");
562
569
  return re ? {
563
570
  re,
564
571
  paramNames
@@ -577,7 +584,7 @@ function matchConfigPattern(pathname, pattern) {
577
584
  const paramName = catchAllMatch[1];
578
585
  const isPlus = catchAllMatch[2] === "+";
579
586
  const prefixNoSlash = prefix.replace(/\/$/, "");
580
- if (!pathname.startsWith(prefixNoSlash)) return null;
587
+ if (!configPathStartsWith(pathname, prefixNoSlash)) return null;
581
588
  const charAfter = pathname[prefixNoSlash.length];
582
589
  if (charAfter !== void 0 && charAfter !== "/") return null;
583
590
  const rest = pathname.slice(prefixNoSlash.length);
@@ -590,7 +597,7 @@ function matchConfigPattern(pathname, pattern) {
590
597
  if (parts.length !== pathParts.length) return null;
591
598
  const params = Object.create(null);
592
599
  for (let i = 0; i < parts.length; i++) if (parts[i].startsWith(":")) params[parts[i].slice(1)] = pathParts[i];
593
- else if (parts[i] !== pathParts[i]) return null;
600
+ else if (!configPathEquals(parts[i], pathParts[i])) return null;
594
601
  return params;
595
602
  }
596
603
  /**
@@ -635,7 +642,7 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
635
642
  let localeMatch = null;
636
643
  let localeMatchIndex = Infinity;
637
644
  if (index.localeStatic.size > 0) {
638
- const noLocaleBucket = index.localeStatic.get(normalizedPathname);
645
+ const noLocaleBucket = index.localeStatic.get(normalizedPathname.toLowerCase());
639
646
  if (noLocaleBucket) for (const entry of noLocaleBucket) {
640
647
  if (!entry.optional) continue;
641
648
  if (entry.originalIndex >= localeMatchIndex) continue;
@@ -657,7 +664,7 @@ function matchRedirect(pathname, redirects, ctx, basePathState = _BASEPATH_DEFAU
657
664
  if (slashTwo !== -1) {
658
665
  const suffix = normalizedPathname.slice(slashTwo);
659
666
  const localePart = normalizedPathname.slice(1, slashTwo);
660
- const localeBucket = index.localeStatic.get(suffix);
667
+ const localeBucket = index.localeStatic.get(suffix.toLowerCase());
661
668
  if (localeBucket) for (const entry of localeBucket) {
662
669
  if (entry.originalIndex >= localeMatchIndex) continue;
663
670
  if (!entry.altRe.test(localePart)) continue;
@@ -748,7 +755,22 @@ function substituteDestinationParams(destination, params) {
748
755
  paramRe = new RegExp(`:(${paramAlternation})([+*])?(?![A-Za-z0-9_])`, "g");
749
756
  _compiledDestinationParamCache.set(cacheKey, paramRe);
750
757
  }
751
- return destination.replace(paramRe, (_token, key) => params[key]);
758
+ const replaceParams = (value, encodeParam) => value.replace(paramRe, (_token, key) => encodeParam(params[key]));
759
+ const hashIndex = destination.indexOf("#");
760
+ const beforeHash = hashIndex === -1 ? destination : destination.slice(0, hashIndex);
761
+ const hash = hashIndex === -1 ? "" : destination.slice(hashIndex);
762
+ const queryIndex = beforeHash.indexOf("?");
763
+ if (queryIndex !== -1) {
764
+ const beforeQuery = beforeHash.slice(0, queryIndex);
765
+ const query = beforeHash.slice(queryIndex + 1);
766
+ return `${replaceParams(beforeQuery, (value) => value)}?${replaceParams(query, encodeDestinationQueryParamValue)}${replaceParams(hash, (value) => value)}`;
767
+ }
768
+ return replaceParams(destination, (value) => value);
769
+ }
770
+ function encodeDestinationQueryParamValue(value) {
771
+ const params = new URLSearchParams();
772
+ params.set("", value);
773
+ return params.toString().slice(1);
752
774
  }
753
775
  /**
754
776
  * Substitute params into a redirect/rewrite destination and sanitize the
@@ -964,7 +986,7 @@ function matchHeaders(pathname, headers, ctx, basePathState = _BASEPATH_DEFAULT)
964
986
  for (const rule of headers) {
965
987
  if (!shouldEvaluateRule(rule.basePath, basePathState)) continue;
966
988
  const source = pathnameHadTrailingSlash ? stripTrailingSlashForConfigMatch(rule.source) : rule.source;
967
- const sourceRegex = getCachedRegex(_compiledHeaderSourceCache, source, () => safeRegExp("^" + escapeHeaderSource(source) + "$"));
989
+ const sourceRegex = getCachedRegex(_compiledHeaderSourceCache, source, () => safeRegExp("^" + escapeHeaderSource(source) + "$", "i"));
968
990
  if (sourceRegex && sourceRegex.test(pathname)) {
969
991
  if (rule.has || rule.missing) {
970
992
  if (!checkHasConditions(rule.has, rule.missing, ctx)) continue;
@@ -82,6 +82,10 @@ type MdxOptions = {
82
82
  rehypePlugins?: unknown[];
83
83
  recmaPlugins?: unknown[];
84
84
  };
85
+ type PrefetchInliningConfig = false | {
86
+ maxBundleSize: number;
87
+ maxSize: number;
88
+ };
85
89
  type NextConfig = {
86
90
  /** Additional env variables */env?: Record<string, string>; /** Base URL path prefix */
87
91
  basePath?: string;
@@ -123,7 +127,15 @@ type NextConfig = {
123
127
  dangerouslyAllowLocalIP?: boolean; /** Content-Disposition header for image responses. Defaults to "inline". */
124
128
  contentDispositionType?: "inline" | "attachment"; /** Content-Security-Policy header for image responses. Defaults to "script-src 'none'; frame-src 'none'; sandbox;" */
125
129
  contentSecurityPolicy?: string;
126
- }; /** Build output mode: 'export' for full static export, 'standalone' for single server */
130
+ };
131
+ /**
132
+ * Enable React Strict Mode. When `true`, the client root is wrapped in
133
+ * `<React.StrictMode>` so React runs its dev-only strict checks (double-
134
+ * invoked effects/render, deprecation warnings). `null`/unset resolves per
135
+ * router: OFF for the Pages Router, ON for the App Router — matching Next.js.
136
+ * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/reactStrictMode
137
+ */
138
+ reactStrictMode?: boolean | null; /** Build output mode: 'export' for full static export, 'standalone' for single server */
127
139
  output?: "export" | "standalone"; /** File extensions treated as routable pages/routes (Next.js pageExtensions) */
128
140
  pageExtensions?: string[]; /** Turbopack-compatible module resolution options. */
129
141
  turbopack?: {
@@ -194,6 +206,15 @@ type NextConfig = {
194
206
  * `useRouter().experimental_gesturePush()`.
195
207
  */
196
208
  gestureTransition?: boolean;
209
+ /**
210
+ * Enables App Router Segment Cache prefetch inlining. When provided as an
211
+ * object, thresholds are resolved with Next.js defaults and non-finite
212
+ * values are clamped to Number.MAX_SAFE_INTEGER.
213
+ */
214
+ prefetchInlining?: boolean | {
215
+ maxBundleSize?: number;
216
+ maxSize?: number;
217
+ };
197
218
  [key: string]: unknown;
198
219
  };
199
220
  /**
@@ -254,11 +275,10 @@ type ResolvedNextConfig = {
254
275
  */
255
276
  gestureTransition: boolean;
256
277
  /**
257
- * Whether `experimental.prefetchInlining` is configured. Next.js uses this
258
- * with the Segment Cache to fetch the route tree before the bundled inlined
259
- * segment payload.
278
+ * Resolved `experimental.prefetchInlining` config. Next.js normalizes `true`
279
+ * and partial object config into concrete thresholds.
260
280
  */
261
- prefetchInlining: boolean;
281
+ prefetchInlining: PrefetchInliningConfig;
262
282
  redirects: NextRedirect[];
263
283
  rewrites: {
264
284
  beforeFiles: NextRewrite[];
@@ -350,6 +370,16 @@ type ResolvedNextConfig = {
350
370
  * `test/e2e/optimized-loading` test fixture.
351
371
  */
352
372
  disableOptimizedLoading: boolean;
373
+ /**
374
+ * Resolved `reactStrictMode` from next.config, preserved as `boolean | null`
375
+ * so each router can apply its own default (Next.js resolves `null` to OFF
376
+ * for the Pages Router and ON for the App Router). When the effective value
377
+ * is `true`, the client root is wrapped in `<React.StrictMode>`.
378
+ *
379
+ * See `.nextjs-ref/packages/next/src/build/define-env.ts`
380
+ * (`__NEXT_STRICT_MODE` / `__NEXT_STRICT_MODE_APP`).
381
+ */
382
+ reactStrictMode: boolean | null;
353
383
  /**
354
384
  * Mirrors Next.js `experimental.scrollRestoration`. When true, the Pages
355
385
  * Router client takes ownership of browser history scroll restoration by
@@ -534,4 +564,4 @@ declare function extractMdxOptions(config: NextConfig, root?: string): Promise<M
534
564
  */
535
565
  declare function detectNextIntlConfig(root: string, resolved: ResolvedNextConfig): void;
536
566
  //#endregion
537
- export { HasCondition, MdxOptions, NextConfig, NextConfigInput, NextHeader, NextI18nConfig, NextRedirect, NextRewrite, PHASE_PRODUCTION_BUILD, ResolvedNextConfig, createRscCompatibilityId, detectNextIntlConfig, extractMdxOptions, findNextConfigPath, lightningCssFeatureNamesToMask, loadNextConfig, normalizeAssetPrefix, parseBodySizeLimit, reassignsModuleExports, referencesCjsGlobals, resolveNextConfig, resolveNextConfigInput };
567
+ export { HasCondition, MdxOptions, NextConfig, NextConfigInput, NextHeader, NextI18nConfig, NextRedirect, NextRewrite, PHASE_PRODUCTION_BUILD, PrefetchInliningConfig, ResolvedNextConfig, createRscCompatibilityId, detectNextIntlConfig, extractMdxOptions, findNextConfigPath, lightningCssFeatureNamesToMask, loadNextConfig, normalizeAssetPrefix, parseBodySizeLimit, reassignsModuleExports, referencesCjsGlobals, resolveNextConfig, resolveNextConfigInput };
@@ -560,6 +560,18 @@ function resolveStaleTimes(experimental) {
560
560
  static: Number.isFinite(staticRaw) && staticRaw >= 0 ? staticRaw : DEFAULT_STALE_TIMES.static
561
561
  };
562
562
  }
563
+ function normalizePrefetchInliningConfig(value) {
564
+ if (!value) return false;
565
+ const raw = isUnknownRecord(value) ? value : null;
566
+ const maxSize = raw ? raw.maxSize ?? 2048 : 2048;
567
+ const maxBundleSize = raw ? raw.maxBundleSize ?? 10240 : 10240;
568
+ const normalizedMaxSize = Number(maxSize);
569
+ const normalizedMaxBundleSize = Number(maxBundleSize);
570
+ return {
571
+ maxBundleSize: Number.isFinite(normalizedMaxBundleSize) ? normalizedMaxBundleSize : Number.MAX_SAFE_INTEGER,
572
+ maxSize: Number.isFinite(normalizedMaxSize) ? normalizedMaxSize : Number.MAX_SAFE_INTEGER
573
+ };
574
+ }
563
575
  /**
564
576
  * Resolve a NextConfig into a fully-resolved ResolvedNextConfig.
565
577
  * Awaits async functions for redirects/rewrites/headers.
@@ -615,6 +627,7 @@ async function resolveNextConfig(config, root = process.cwd(), options = {}) {
615
627
  sassOptions: null,
616
628
  removeConsole: false,
617
629
  disableOptimizedLoading: false,
630
+ reactStrictMode: null,
618
631
  scrollRestoration: false,
619
632
  compilerDefine: {},
620
633
  compilerDefineServer: {},
@@ -683,12 +696,12 @@ async function resolveNextConfig(config, root = process.cwd(), options = {}) {
683
696
  const optimizePackageImports = Array.isArray(rawOptimize) ? rawOptimize.filter((x) => typeof x === "string") : [];
684
697
  const inlineCss = experimental?.inlineCss === true;
685
698
  const globalNotFound = experimental?.globalNotFound === true;
686
- const prefetchInlining = experimental?.prefetchInlining === true || isUnknownRecord(experimental?.prefetchInlining);
699
+ const prefetchInlining = normalizePrefetchInliningConfig(experimental?.prefetchInlining);
687
700
  const appShells = experimental?.appShells === true;
688
701
  if (appShells) {
689
702
  const missingCoFlags = [];
690
703
  if (!config.cacheComponents) missingCoFlags.push("cacheComponents");
691
- if (experimental?.prefetchInlining !== true) missingCoFlags.push("experimental.prefetchInlining");
704
+ if (!prefetchInlining) missingCoFlags.push("experimental.prefetchInlining");
692
705
  if (experimental?.varyParams !== true) missingCoFlags.push("experimental.varyParams");
693
706
  if (experimental?.optimisticRouting !== true) missingCoFlags.push("experimental.optimisticRouting");
694
707
  if (experimental?.cachedNavigations !== true) missingCoFlags.push("experimental.cachedNavigations");
@@ -791,6 +804,7 @@ async function resolveNextConfig(config, root = process.cwd(), options = {}) {
791
804
  sassOptions: readOptionalRecord(config.sassOptions) ?? null,
792
805
  removeConsole: config.compiler?.removeConsole === true ? true : isUnknownRecord(config.compiler?.removeConsole) ? { exclude: readStringArray(config.compiler.removeConsole.exclude) } : false,
793
806
  disableOptimizedLoading: experimental?.disableOptimizedLoading === true,
807
+ reactStrictMode: typeof config.reactStrictMode === "boolean" ? config.reactStrictMode : null,
794
808
  scrollRestoration: experimental?.scrollRestoration === true,
795
809
  compilerDefine: serializeCompilerDefine(config.compiler?.define),
796
810
  compilerDefineServer: serializeCompilerDefine(config.compiler?.defineServer),
@@ -47,12 +47,16 @@ function toDocumentOnlyAppRoute(route) {
47
47
  isDynamic: route.isDynamic
48
48
  };
49
49
  }
50
+ function requiresDynamicNavigationRequest(route) {
51
+ return route.isDynamic && route.parallelSlots.length > 0;
52
+ }
50
53
  /** Project an `AppRoute` down to the public `VinextLinkPrefetchRoute` shape. */
51
54
  function toLinkPrefetchRoute(route) {
52
55
  return {
53
56
  canPrefetchLoadingShell: route.loadingPath !== null,
54
57
  patternParts: [...route.patternParts],
55
- isDynamic: route.isDynamic
58
+ isDynamic: route.isDynamic,
59
+ ...requiresDynamicNavigationRequest(route) ? { requiresDynamicNavigationRequest: true } : {}
56
60
  };
57
61
  }
58
62
  function buildRouteManifestExpression(routeManifest) {
@@ -1,4 +1,4 @@
1
- import { NextHeader, NextI18nConfig, NextRedirect, NextRewrite } from "../config/next-config.js";
1
+ import { NextHeader, NextI18nConfig, NextRedirect, NextRewrite, PrefetchInliningConfig } from "../config/next-config.js";
2
2
  import { AppRoute } from "../routing/app-route-graph.js";
3
3
  import { MetadataFileRoute } from "../server/metadata-routes.js";
4
4
  import { ImageConfig } from "../server/image-optimization.js";
@@ -45,7 +45,8 @@ type AppRouterConfig = {
45
45
  cacheMaxMemorySize?: number; /** Inline app CSS into production HTML (from experimental.inlineCss). */
46
46
  inlineCss?: boolean; /** Enable standalone route-miss 404 handling (from experimental.globalNotFound). */
47
47
  globalNotFound?: boolean; /** Enables Next.js Cache Components semantics for App Router document HTML. */
48
- cacheComponents?: boolean; /** Whether the RSC build discovered any server references. Defaults to true. */
48
+ cacheComponents?: boolean; /** Resolved `experimental.prefetchInlining` thresholds. */
49
+ prefetchInlining?: PrefetchInliningConfig; /** Whether the RSC build discovered any server references. Defaults to true. */
49
50
  hasServerActions?: boolean; /** Internationalization routing config for middleware matcher locale handling. */
50
51
  i18n?: NextI18nConfig | null;
51
52
  imageConfig?: ImageConfig;
@@ -78,6 +78,7 @@ function generateRscEntry(appDir, routes, middlewarePath, metadataRoutes, global
78
78
  const cacheMaxMemorySize = config?.cacheMaxMemorySize;
79
79
  const inlineCss = config?.inlineCss === true;
80
80
  const cacheComponents = config?.cacheComponents === true;
81
+ const prefetchInlining = config?.prefetchInlining ?? false;
81
82
  const hasServerActions = config?.hasServerActions !== false;
82
83
  const i18nConfig = config?.i18n ?? null;
83
84
  const hasPagesDir = config?.hasPagesDir ?? false;
@@ -493,6 +494,7 @@ export default createAppRscHandler({
493
494
  basePath: __basePath,
494
495
  buildId: process.env.__VINEXT_BUILD_ID ?? null,
495
496
  ensureRouteLoaded: __ensureRouteLoaded,
497
+ prefetchInlining: ${JSON.stringify(prefetchInlining)},
496
498
  clearRequestContext() {
497
499
  __clearRequestContext();
498
500
  },
@@ -527,6 +529,7 @@ export default createAppRscHandler({
527
529
  staticParamsValidationParams,
528
530
  rootParams,
529
531
  request,
532
+ renderedPathAndSearch,
530
533
  route,
531
534
  scriptNonce,
532
535
  searchParams,
@@ -693,6 +696,7 @@ export default createAppRscHandler({
693
696
  prerenderToReadableStream,
694
697
  request,
695
698
  revalidateSeconds: __segmentConfig.revalidateSeconds,
699
+ renderedPathAndSearch,
696
700
  resolveRouteFetchCacheMode(targetRoute) {
697
701
  return __resolveRouteFetchCacheMode(targetRoute);
698
702
  },
@@ -57,7 +57,10 @@ async function generateClientEntry(pagesDir, nextConfig, fileMatcher, options =
57
57
  return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;
58
58
  });
59
59
  const appFileBase = appFilePath ? normalizePathSeparators(appFilePath) : void 0;
60
- return `${instrumentationClientPath ? `import ${JSON.stringify(normalizePathSeparators(instrumentationClientPath))};\n` : ""}${options.reactPreamble === false ? "" : "import \"@vitejs/plugin-react/preamble\";\n"}
60
+ const userInstrumentationImport = instrumentationClientPath ? `import ${JSON.stringify(normalizePathSeparators(instrumentationClientPath))};\n` : "";
61
+ const reactPreambleImport = options.reactPreamble === false ? "" : "import \"@vitejs/plugin-react/preamble\";\n";
62
+ const reactStrictModeEnabled = nextConfig.reactStrictMode === true;
63
+ return `${userInstrumentationImport}${reactPreambleImport}
61
64
  import "vinext/instrumentation-client";
62
65
  import React from "react";
63
66
  import { hydrateRoot } from "react-dom/client";
@@ -89,6 +92,9 @@ const appLoader = undefined;
89
92
  // can iterate in order and trust the first match.
90
93
  window.__VINEXT_PAGE_LOADERS__ = pageLoaders;
91
94
  window.__VINEXT_PAGE_PATTERNS__ = Object.keys(pageLoaders);
95
+ // reactStrictMode flag — read by wrapWithRouterContext (next/router) so the
96
+ // <React.StrictMode> wrap is applied on initial hydration and every navigation.
97
+ window.__VINEXT_REACT_STRICT_MODE__ = ${JSON.stringify(reactStrictModeEnabled)};
92
98
  window.__VINEXT_PAGES_SSG_PATTERNS__ = ${JSON.stringify(pagesSsgPatterns)};
93
99
  window.__VINEXT_PAGES_SSP_PATTERNS__ = ${JSON.stringify(pagesSspPatterns)};
94
100
  window.__VINEXT_MIDDLEWARE_MATCHER__ = ${JSON.stringify(options.middlewareMatcher)};
@@ -186,6 +192,9 @@ async function hydrate() {
186
192
  });
187
193
 
188
194
  // Wrap with RouterContext.Provider so next/router and next/compat/router work during hydration.
195
+ // When reactStrictMode is enabled, wrapWithRouterContext also wraps the tree
196
+ // in <React.StrictMode> (see next/router) — applied here and on every
197
+ // navigation render, matching Next.js.
189
198
  element = wrapWithRouterContext(element, resolveHydrationCommit);
190
199
 
191
200
  const container = document.getElementById("__next");
@@ -204,9 +213,11 @@ async function hydrate() {
204
213
  window.__NEXT_HYDRATED_CB?.();
205
214
 
206
215
  if (nextData.isFallback) {
216
+ const currentUrl = window.location.pathname + window.location.search + window.location.hash;
217
+ const routeUrl = nextData.__vinext?.routeUrl;
207
218
  await Router.replace(
208
- window.location.pathname + window.location.search + window.location.hash,
209
- undefined,
219
+ routeUrl || currentUrl,
220
+ routeUrl ? currentUrl : undefined,
210
221
  { _h: 1, scroll: false },
211
222
  );
212
223
  }
@@ -4,6 +4,8 @@ import { apiRouter, pagesRouter } from "../routing/pages-router.js";
4
4
  import { resolveEntryPath } from "./runtime-entry-module.js";
5
5
  import { isProxyFile } from "../server/middleware.js";
6
6
  import "./pages-entry-helpers.js";
7
+ import { hasExportedName } from "../build/report.js";
8
+ import { readFile } from "node:fs/promises";
7
9
  //#region src/entries/pages-server-entry.ts
8
10
  /**
9
11
  * Pages Router server entry generator.
@@ -24,6 +26,12 @@ const _pagesApiRoutePath = resolveEntryPath("../server/pages-api-route.js", impo
24
26
  const _serverGlobalsPath = resolveEntryPath("../server/server-globals.js", import.meta.url);
25
27
  const _queryUtilsPath = resolveEntryPath("../utils/query.js", import.meta.url);
26
28
  const _pagesPageHandlerPath = resolveEntryPath("../server/pages-page-handler.js", import.meta.url);
29
+ async function getPagesDataKind(filePath) {
30
+ const source = await readFile(filePath, "utf8");
31
+ if (hasExportedName(source, "getStaticProps")) return "static";
32
+ if (hasExportedName(source, "getServerSideProps")) return "server";
33
+ return "none";
34
+ }
27
35
  /**
28
36
  * Generate the virtual SSR server entry module.
29
37
  * This is the entry point for `vite build --ssr`.
@@ -39,10 +47,11 @@ async function generateServerEntry(pagesDir, nextConfig, fileMatcher, middleware
39
47
  const absPath = normalizePathSeparators(r.filePath);
40
48
  return `import * as api_${i} from ${JSON.stringify(absPath)};`;
41
49
  });
42
- const pageRouteEntries = pageRoutes.map((r, i) => {
50
+ const pageRouteEntries = await Promise.all(pageRoutes.map(async (r, i) => {
43
51
  const absPath = normalizePathSeparators(r.filePath);
44
- return ` { pattern: ${JSON.stringify(r.pattern)}, patternParts: ${JSON.stringify(r.patternParts)}, isDynamic: ${r.isDynamic}, params: ${JSON.stringify(r.params)}, module: page_${i}, filePath: ${JSON.stringify(absPath)} }`;
45
- });
52
+ const dataKind = await getPagesDataKind(r.filePath);
53
+ return ` { pattern: ${JSON.stringify(r.pattern)}, patternParts: ${JSON.stringify(r.patternParts)}, isDynamic: ${r.isDynamic}, params: ${JSON.stringify(r.params)}, module: page_${i}, filePath: ${JSON.stringify(absPath)}, dataKind: ${JSON.stringify(dataKind)} }`;
54
+ }));
46
55
  const apiRouteEntries = apiRoutes.map((r, i) => ` { pattern: ${JSON.stringify(r.pattern)}, patternParts: ${JSON.stringify(r.patternParts)}, isDynamic: ${r.isDynamic}, params: ${JSON.stringify(r.params)}, module: api_${i} }`);
47
56
  const appFilePath = findFileWithExts(pagesDir, "_app", fileMatcher);
48
57
  const docFilePath = findFileWithExts(pagesDir, "_document", fileMatcher);
@@ -164,7 +173,12 @@ const i18nConfig = ${i18nConfigJson};
164
173
  // to load next.config.js at runtime.
165
174
  export const buildId = ${buildIdJson};
166
175
  export function normalizeDataRequest(request) {
167
- return __normalizePagesDataRequest(request, buildId);
176
+ return __normalizePagesDataRequest(
177
+ request,
178
+ buildId,
179
+ vinextConfig.basePath,
180
+ hasMiddleware && vinextConfig.trailingSlash,
181
+ );
168
182
  }
169
183
  export const hasMiddleware = ${JSON.stringify(Boolean(middlewarePath))};
170
184