vinext 0.2.1 → 1.0.0-beta.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 (236) hide show
  1. package/README.md +67 -30
  2. package/dist/build/assets-ignore.js +1 -1
  3. package/dist/build/clean-output.js +1 -1
  4. package/dist/build/client-build-config.d.ts +13 -92
  5. package/dist/build/client-build-config.js +23 -99
  6. package/dist/build/css-url-assets.js +3 -3
  7. package/dist/build/google-fonts/fallback-metrics.js +1 -1
  8. package/dist/build/google-fonts/font-metadata.js +1 -1
  9. package/dist/build/inject-pregenerated-paths.js +1 -1
  10. package/dist/build/inline-css.js +2 -5
  11. package/dist/build/module-dependency-cache.d.ts +4 -0
  12. package/dist/build/module-dependency-cache.js +13 -0
  13. package/dist/build/next-client-runtime-manifests.js +1 -1
  14. package/dist/build/pages-client-assets-module.js +1 -1
  15. package/dist/build/precompress.js +7 -7
  16. package/dist/build/prerender-paths.d.ts +23 -0
  17. package/dist/build/prerender-paths.js +291 -0
  18. package/dist/build/prerender-server-pool.js +2 -2
  19. package/dist/build/prerender.d.ts +12 -1
  20. package/dist/build/prerender.js +2 -2
  21. package/dist/build/preview-credentials.d.ts +11 -0
  22. package/dist/build/preview-credentials.js +19 -0
  23. package/dist/build/report.d.ts +0 -3
  24. package/dist/build/report.js +2 -4
  25. package/dist/build/run-prerender.d.ts +2 -11
  26. package/dist/build/run-prerender.js +2 -10
  27. package/dist/build/server-manifest.js +1 -1
  28. package/dist/build/ssr-manifest.d.ts +4 -0
  29. package/dist/build/ssr-manifest.js +10 -6
  30. package/dist/build/standalone.js +37 -37
  31. package/dist/cache/cache-adapters-virtual.d.ts +7 -17
  32. package/dist/cache/cache-adapters-virtual.js +35 -2
  33. package/dist/check.d.ts +0 -16
  34. package/dist/check.js +17 -34
  35. package/dist/cli.js +86 -101
  36. package/dist/client/instrumentation-client-inject.js +1 -1
  37. package/dist/client/pages-router-link-navigation.js +1 -1
  38. package/dist/config/config-matchers.js +2 -2
  39. package/dist/config/dotenv.js +1 -1
  40. package/dist/config/next-config.d.ts +4 -1
  41. package/dist/config/next-config.js +82 -16
  42. package/dist/config/prerender.d.ts +12 -2
  43. package/dist/config/prerender.js +22 -12
  44. package/dist/config/server-external-packages.js +1 -1
  45. package/dist/config/tsconfig-paths.js +7 -3
  46. package/dist/{node_modules/.pnpm/am-i-vibing@0.5.0/node_modules → deps/.pnpm/am-i-vibing@0.5.0/deps}/am-i-vibing/dist/detector-1yx2Hoe0.js +2 -2
  47. package/dist/deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js +50 -0
  48. package/dist/entries/app-rsc-entry.js +12 -5
  49. package/dist/entries/app-rsc-manifest.js +4 -4
  50. package/dist/entries/pages-client-entry.js +6 -5
  51. package/dist/entries/pages-server-entry.js +15 -26
  52. package/dist/entries/runtime-entry-module.js +2 -2
  53. package/dist/image/image-adapters-virtual.js +1 -0
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.js +500 -338
  56. package/dist/init-cloudflare.js +13 -4
  57. package/dist/init-platform.d.ts +10 -1
  58. package/dist/init-platform.js +79 -13
  59. package/dist/init.d.ts +11 -3
  60. package/dist/init.js +133 -35
  61. package/dist/plugins/ast-utils.js +1 -1
  62. package/dist/plugins/dynamic-preload-metadata.js +7 -7
  63. package/dist/plugins/extensionless-dynamic-import.js +1 -1
  64. package/dist/plugins/fonts.js +3 -3
  65. package/dist/plugins/ignore-dynamic-requests.js +13 -8
  66. package/dist/plugins/import-meta-url.js +16 -17
  67. package/dist/plugins/middleware-export-validation.js +1 -1
  68. package/dist/plugins/middleware-server-only.js +2 -2
  69. package/dist/plugins/og-asset-ownership.js +7 -6
  70. package/dist/plugins/og-assets.js +4 -5
  71. package/dist/plugins/optimize-imports.js +12 -13
  72. package/dist/plugins/postcss.js +1 -1
  73. package/dist/plugins/remove-console.js +1 -1
  74. package/dist/plugins/require-context.js +1 -1
  75. package/dist/plugins/sass.js +2 -2
  76. package/dist/plugins/server-externals-manifest.js +1 -1
  77. package/dist/plugins/strip-server-exports.js +1 -1
  78. package/dist/plugins/styled-jsx.js +3 -2
  79. package/dist/plugins/typeof-window.js +1 -1
  80. package/dist/routing/app-route-graph.d.ts +3 -10
  81. package/dist/routing/app-route-graph.js +28 -76
  82. package/dist/routing/app-router.d.ts +0 -5
  83. package/dist/routing/app-router.js +0 -5
  84. package/dist/routing/file-matcher.d.ts +2 -6
  85. package/dist/routing/file-matcher.js +10 -11
  86. package/dist/routing/pages-router.js +3 -3
  87. package/dist/server/api-handler.js +41 -33
  88. package/dist/server/app-browser-entry.js +15 -11
  89. package/dist/server/app-browser-navigation-controller.js +9 -4
  90. package/dist/server/app-browser-server-action-client.js +1 -1
  91. package/dist/server/app-elements-wire.d.ts +2 -1
  92. package/dist/server/app-fallback-renderer.d.ts +8 -0
  93. package/dist/server/app-fallback-renderer.js +13 -1
  94. package/dist/server/app-layout-param-observation.d.ts +1 -1
  95. package/dist/server/app-page-boundary-render.d.ts +14 -1
  96. package/dist/server/app-page-boundary-render.js +107 -54
  97. package/dist/server/app-page-cache-render.d.ts +1 -1
  98. package/dist/server/app-page-dispatch.d.ts +2 -1
  99. package/dist/server/app-page-dispatch.js +29 -40
  100. package/dist/server/app-page-element-builder.d.ts +2 -1
  101. package/dist/server/app-page-element-builder.js +3 -3
  102. package/dist/server/app-page-execution.d.ts +3 -1
  103. package/dist/server/app-page-execution.js +31 -39
  104. package/dist/server/app-page-render.d.ts +1 -1
  105. package/dist/server/app-page-request.js +1 -1
  106. package/dist/server/app-page-route-wiring.js +4 -4
  107. package/dist/server/app-route-handler-execution.d.ts +1 -1
  108. package/dist/server/app-route-tree-prefetch.js +1 -1
  109. package/dist/server/app-rsc-cache-busting.js +1 -2
  110. package/dist/server/app-rsc-handler.js +2 -2
  111. package/dist/server/app-rsc-redirect-flight.d.ts +66 -0
  112. package/dist/server/app-rsc-redirect-flight.js +71 -0
  113. package/dist/server/app-rsc-render-mode.d.ts +2 -5
  114. package/dist/server/app-rsc-render-mode.js +2 -12
  115. package/dist/server/app-segment-config.js +2 -2
  116. package/dist/server/app-server-action-execution.js +3 -3
  117. package/dist/server/app-ssr-entry.js +4 -1
  118. package/dist/server/app-ssr-stream.d.ts +2 -1
  119. package/dist/server/app-ssr-stream.js +4 -1
  120. package/dist/server/cache-proof.js +1 -1
  121. package/dist/server/default-global-error-module.d.ts +3 -1
  122. package/dist/server/default-not-found-module.d.ts +3 -1
  123. package/dist/server/dev-lockfile.js +2 -3
  124. package/dist/server/dev-module-runner.js +1 -1
  125. package/dist/server/dev-origin-check.d.ts +2 -2
  126. package/dist/server/dev-origin-check.js +2 -2
  127. package/dist/server/dev-route-files.js +1 -1
  128. package/dist/server/dev-server.js +256 -164
  129. package/dist/server/dev-stack-sourcemap.js +4 -3
  130. package/dist/server/headers.d.ts +3 -1
  131. package/dist/server/headers.js +3 -1
  132. package/dist/server/image-optimization.d.ts +12 -1
  133. package/dist/server/image-optimization.js +15 -3
  134. package/dist/server/implicit-tags.js +2 -1
  135. package/dist/server/instrumentation.js +2 -2
  136. package/dist/server/isr-cache.d.ts +12 -4
  137. package/dist/server/isr-cache.js +3 -3
  138. package/dist/server/metadata-routes.js +4 -4
  139. package/dist/server/middleware-response-headers.js +1 -1
  140. package/dist/server/middleware-runtime.js +1 -1
  141. package/dist/server/middleware.d.ts +1 -0
  142. package/dist/server/middleware.js +1 -1
  143. package/dist/server/navigation-planner.d.ts +2 -0
  144. package/dist/server/navigation-planner.js +8 -2
  145. package/dist/server/next-error-digest.d.ts +9 -24
  146. package/dist/server/next-error-digest.js +23 -15
  147. package/dist/server/operation-token.js +1 -1
  148. package/dist/server/pages-body-parser-config.js +2 -1
  149. package/dist/server/pages-dev-hydration.d.ts +14 -0
  150. package/dist/server/pages-dev-hydration.js +83 -0
  151. package/dist/server/pages-dev-module-url.js +2 -2
  152. package/dist/server/pages-node-compat.d.ts +13 -6
  153. package/dist/server/pages-node-compat.js +44 -45
  154. package/dist/server/pages-page-data.d.ts +1 -1
  155. package/dist/server/pages-page-data.js +5 -4
  156. package/dist/server/pages-page-handler.js +61 -29
  157. package/dist/server/pages-page-response.d.ts +2 -2
  158. package/dist/server/pages-page-response.js +2 -2
  159. package/dist/server/pages-preview.d.ts +26 -0
  160. package/dist/server/pages-preview.js +165 -0
  161. package/dist/server/pages-readiness.d.ts +2 -1
  162. package/dist/server/pages-readiness.js +4 -2
  163. package/dist/server/pages-request-pipeline.js +1 -1
  164. package/dist/server/prerender-manifest.d.ts +15 -1
  165. package/dist/server/prerender-manifest.js +29 -1
  166. package/dist/server/prod-server.d.ts +6 -5
  167. package/dist/server/prod-server.js +15 -15
  168. package/dist/server/request-pipeline.js +1 -1
  169. package/dist/server/seed-cache.js +1 -1
  170. package/dist/server/socket-error-backstop.js +1 -1
  171. package/dist/server/static-file-cache.js +8 -9
  172. package/dist/server/worker-utils.js +1 -1
  173. package/dist/shims/cache.d.ts +1 -1
  174. package/dist/shims/cache.js +2 -1
  175. package/dist/shims/cdn-cache.d.ts +2 -7
  176. package/dist/shims/cdn-cache.js +2 -14
  177. package/dist/shims/constants.js +1 -1
  178. package/dist/shims/error-boundary.d.ts +4 -4
  179. package/dist/shims/error.d.ts +21 -5
  180. package/dist/shims/error.js +52 -14
  181. package/dist/shims/fetch-cache.js +1 -1
  182. package/dist/shims/font-utils.js +1 -1
  183. package/dist/shims/form.d.ts +1 -1
  184. package/dist/shims/head.js +3 -3
  185. package/dist/shims/headers.js +6 -4
  186. package/dist/shims/internal/pages-data-fetch-dedup.js +5 -3
  187. package/dist/shims/layout-segment-context.d.ts +1 -1
  188. package/dist/shims/link.d.ts +3 -1
  189. package/dist/shims/link.js +6 -3
  190. package/dist/shims/navigation-errors.d.ts +2 -1
  191. package/dist/shims/navigation-errors.js +9 -13
  192. package/dist/shims/navigation.d.ts +1 -1
  193. package/dist/shims/navigation.js +5 -5
  194. package/dist/shims/request-state-types.d.ts +2 -2
  195. package/dist/shims/router-state.d.ts +1 -0
  196. package/dist/shims/router.d.ts +2 -1
  197. package/dist/shims/router.js +18 -11
  198. package/dist/shims/script.js +1 -1
  199. package/dist/shims/server.js +2 -2
  200. package/dist/shims/slot.d.ts +1 -1
  201. package/dist/shims/thenable-params.js +1 -1
  202. package/dist/shims/unified-request-context.d.ts +1 -1
  203. package/dist/typegen.js +9 -11
  204. package/dist/utils/client-build-manifest.js +2 -2
  205. package/dist/utils/client-entry-manifest.js +1 -1
  206. package/dist/utils/client-runtime-metadata.js +1 -1
  207. package/dist/utils/commonjs-loader.js +3 -3
  208. package/dist/utils/mdx-scan.js +1 -1
  209. package/dist/utils/path.d.ts +1 -13
  210. package/dist/utils/path.js +1 -15
  211. package/dist/utils/plugin-options.d.ts +4 -0
  212. package/dist/utils/plugin-options.js +8 -0
  213. package/dist/utils/project.d.ts +1 -7
  214. package/dist/utils/project.js +2 -8
  215. package/dist/utils/public-routes.js +2 -2
  216. package/dist/utils/react-version.js +1 -1
  217. package/dist/utils/redirect-digest.d.ts +9 -0
  218. package/dist/utils/redirect-digest.js +25 -0
  219. package/dist/utils/vinext-root.js +1 -1
  220. package/dist/utils/vite-version.d.ts +6 -13
  221. package/dist/utils/vite-version.js +68 -34
  222. package/package.json +33 -14
  223. package/dist/cloudflare/index.d.ts +0 -3
  224. package/dist/cloudflare/index.js +0 -3
  225. package/dist/packages/cloudflare/src/cache/cdn-adapter.runtime.js +0 -102
  226. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +0 -126
  227. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +0 -442
  228. package/dist/packages/cloudflare/src/deploy-config.js +0 -150
  229. package/dist/packages/cloudflare/src/deploy-help.js +0 -55
  230. package/dist/packages/cloudflare/src/deploy.js +0 -272
  231. package/dist/packages/cloudflare/src/tpr.d.ts +0 -45
  232. package/dist/packages/cloudflare/src/tpr.js +0 -582
  233. package/dist/packages/cloudflare/src/utils/cache-control-metadata.js +0 -20
  234. /package/dist/build/google-fonts/{fallback-metrics-data.js → fallback-metrics-data.json.js} +0 -0
  235. /package/dist/build/google-fonts/{font-data.js → font-data.json.js} +0 -0
  236. /package/dist/{node_modules/.pnpm/process-ancestry@0.1.0/node_modules → deps/.pnpm/process-ancestry@0.1.0/deps}/process-ancestry/dist/index.js +0 -0
@@ -1,10 +1,9 @@
1
- import { normalizePathSeparators } from "../utils/path.js";
1
+ import path, { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
3
3
  import { findFileWithExts, scanWithExtensions } from "./file-matcher.js";
4
4
  import { validateRoutePatterns } from "./route-validation.js";
5
5
  import { compareStrings } from "../utils/compare.js";
6
6
  import fs from "node:fs";
7
- import path from "node:path";
8
7
  import { createHash } from "node:crypto";
9
8
  //#region src/routing/app-route-graph.ts
10
9
  /**
@@ -363,10 +362,6 @@ function createRouteManifestGraphVersion(segmentGraph) {
363
362
  }
364
363
  /**
365
364
  * Build the App Router route graph by scanning `appDir`.
366
- *
367
- * `appDir` must be forward-slash. Every path in the graph is derived from it
368
- * with `path.posix.*` and `findFile`, so a native appDir would produce mixed
369
- * separators on Windows. Production callers normalize it at their entry.
370
365
  */
371
366
  async function buildAppRouteGraph(appDir, matcher) {
372
367
  const routes = [];
@@ -384,8 +379,8 @@ async function buildAppRouteGraph(appDir, matcher) {
384
379
  const routePatterns = new Set(routes.map((route) => route.pattern));
385
380
  const ghostParentRoutes = [];
386
381
  for await (const file of scanWithExtensions("**/layout", appDir, scanMatcher.extensions, excludeDir)) {
387
- const dir = path.posix.dirname(file);
388
- const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
382
+ const dir = path.dirname(file);
383
+ const routeDir = dir === "." ? appDir : path.join(appDir, dir);
389
384
  if (!hasParallelSlotDirectory(routeDir)) continue;
390
385
  if (discoverParallelSlots(routeDir, appDir, scanMatcher, true).length === 0) continue;
391
386
  const route = directoryToAppRoute(dir, appDir, scanMatcher, null, null, true);
@@ -440,9 +435,9 @@ function validatePageRouteConflicts(routes, appDir) {
440
435
  }
441
436
  }
442
437
  function formatAppFilePath(filePath, appDir) {
443
- const relativePath = normalizePathSeparators(path.relative(appDir, filePath));
438
+ const relativePath = path.relative(appDir, filePath);
444
439
  const parsedPath = path.parse(relativePath);
445
- const withoutExtension = normalizePathSeparators(path.join(parsedPath.dir, parsedPath.name));
440
+ const withoutExtension = path.join(parsedPath.dir, parsedPath.name);
446
441
  return withoutExtension.startsWith("/") ? withoutExtension : `/${withoutExtension}`;
447
442
  }
448
443
  /**
@@ -704,27 +699,15 @@ function findCatchAllPage(dir, matcher) {
704
699
  }
705
700
  /**
706
701
  * Convert a file path relative to app/ into an AppRoute.
707
- *
708
- * `file` and `appDir` must be forward-slash. `file` comes from
709
- * `scanWithExtensions` (already forward-slash) and is joined onto `appDir` with
710
- * `path.posix.join` to form the page/route path, so a native input would
711
- * produce a mixed separator on Windows.
712
702
  */
713
703
  function fileToAppRoute(file, appDir, type, matcher) {
714
- let dir = path.posix.dirname(file);
715
- if (type === "page" && dir !== "." && path.posix.basename(dir) === "@children") {
716
- const parent = path.posix.dirname(dir);
704
+ let dir = path.dirname(file);
705
+ if (type === "page" && dir !== "." && path.basename(dir) === "@children") {
706
+ const parent = path.dirname(dir);
717
707
  dir = parent === "" || parent === "." ? "." : parent;
718
708
  }
719
- return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.posix.join(appDir, file) : null, type === "route" ? path.posix.join(appDir, file) : null);
709
+ return directoryToAppRoute(dir, appDir, matcher, type === "page" ? path.join(appDir, file) : null, type === "route" ? path.join(appDir, file) : null);
720
710
  }
721
- /**
722
- * `dir`, `appDir`, `pagePath`, and `routePath` must all be forward-slash. `dir`
723
- * is split on `path.posix.sep` and joined onto `appDir` with `path.posix.join`.
724
- * `appDir` is threaded to the layout/slot/boundary discovery below, which builds
725
- * paths the same way. `pagePath` and `routePath` are stored on the route node as
726
- * canonical ids that get compared and re-joined downstream.
727
- */
728
711
  function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeNestedOnlySlots = false) {
729
712
  const segments = dir === "." ? [] : dir.split("/");
730
713
  const params = [];
@@ -743,7 +726,7 @@ function directoryToAppRoute(dir, appDir, matcher, pagePath, routePath, includeN
743
726
  const errorEntries = discoverSegmentErrors(segments, appDir, matcher);
744
727
  const errorPaths = errorEntries.map((entry) => entry.path);
745
728
  const errorTreePositions = errorEntries.map((entry) => entry.treePosition);
746
- const routeDir = dir === "." ? appDir : path.posix.join(appDir, dir);
729
+ const routeDir = dir === "." ? appDir : path.join(appDir, dir);
747
730
  const effectivePagePath = pagePath ?? (routePath ? null : findFile(routeDir, "default", matcher));
748
731
  const loadingPath = findFile(routeDir, "loading", matcher);
749
732
  const errorPath = findFile(routeDir, "error", matcher);
@@ -850,8 +833,9 @@ function convertTreePathToRouteParts(treePath) {
850
833
  function computeLayoutTreePositions(appDir, layouts) {
851
834
  return layouts.map((layoutPath) => {
852
835
  const layoutDir = path.dirname(layoutPath);
853
- if (layoutDir === appDir) return 0;
854
- return path.relative(appDir, layoutDir).split(path.sep).length;
836
+ const relative = path.relative(appDir, layoutDir);
837
+ if (relative === "") return 0;
838
+ return relative.split(path.sep).length;
855
839
  });
856
840
  }
857
841
  /**
@@ -976,10 +960,6 @@ function discoverBoundaryFilePerLayout(layouts, fileName, matcher) {
976
960
  * Walk from appDir through each segment to the route's directory. At each level
977
961
  * that has @slot dirs, collect them. Slots at the route's own directory level
978
962
  * use page.tsx; slots at ancestor levels use default.tsx only.
979
- *
980
- * `appDir` and `routeDir` must be forward-slash — `currentDir` descends from
981
- * `appDir` via `path.posix.join`, and the `dir === routeDir` active-level test
982
- * below only matches when both share the canonical separator.
983
963
  */
984
964
  function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, includeNestedOnlySlots = false) {
985
965
  const slotMap = /* @__PURE__ */ new Map();
@@ -992,7 +972,7 @@ function discoverInheritedParallelSlots(segments, appDir, routeDir, matcher, inc
992
972
  segmentIndex: 0
993
973
  });
994
974
  for (let i = 0; i < segments.length; i++) {
995
- currentDir = path.posix.join(currentDir, segments[i]);
975
+ currentDir = path.join(currentDir, segments[i]);
996
976
  if (findFile(currentDir, "layout", matcher)) layoutIdx++;
997
977
  dirsToCheck.push({
998
978
  dir: currentDir,
@@ -1155,9 +1135,6 @@ function patternsStructurallyEquivalent(a, b) {
1155
1135
  *
1156
1136
  * Returns the absolute page path, or null if no root-level page is found.
1157
1137
  *
1158
- * `slotDir` must be forward-slash: the `path.posix.join` descent stays a
1159
- * canonical id only when the base already is.
1160
- *
1161
1138
  * Only descends into route-group directories (those whose name starts with `(`
1162
1139
  * and ends with `)`). Dynamic segments, regular named dirs, and `@slot` dirs
1163
1140
  * are not transparent and are therefore not searched.
@@ -1169,7 +1146,7 @@ function findSlotRootPage(slotDir, matcher) {
1169
1146
  for (const entry of entries) {
1170
1147
  if (!entry.isDirectory()) continue;
1171
1148
  if (!entry.name.startsWith("(") || !entry.name.endsWith(")")) continue;
1172
- const found = findSlotRootPage(path.posix.join(slotDir, entry.name), matcher);
1149
+ const found = findSlotRootPage(path.join(slotDir, entry.name), matcher);
1173
1150
  if (found) return found;
1174
1151
  }
1175
1152
  return null;
@@ -1178,9 +1155,6 @@ function findSlotRootPage(slotDir, matcher) {
1178
1155
  * Discover parallel route slots (@team, @analytics, etc.) in a directory.
1179
1156
  * Returns a ParallelSlot for each @-prefixed subdirectory that has a page,
1180
1157
  * default component, intercepting route, or nested page-backed sub-route.
1181
- *
1182
- * `dir` and `appDir` must be forward-slash. The slot directory is built from
1183
- * `dir` with `path.posix.join`.
1184
1158
  */
1185
1159
  function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
1186
1160
  const entries = readDirEntriesCached(dir, matcher);
@@ -1189,7 +1163,7 @@ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = fa
1189
1163
  if (!entry.isDirectory() || !entry.name.startsWith("@")) continue;
1190
1164
  if (entry.name === "@children") continue;
1191
1165
  const slotName = entry.name.slice(1);
1192
- const slotDir = path.posix.join(dir, entry.name);
1166
+ const slotDir = path.join(dir, entry.name);
1193
1167
  const pagePath = findSlotRootPage(slotDir, matcher);
1194
1168
  const defaultPath = findFile(slotDir, "default", matcher);
1195
1169
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
@@ -1200,7 +1174,7 @@ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = fa
1200
1174
  const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1201
1175
  slots.push({
1202
1176
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1203
- key: `${slotName}@${normalizePathSeparators(path.relative(appDir, slotDir))}`,
1177
+ key: `${slotName}@${path.relative(appDir, slotDir)}`,
1204
1178
  name: slotName,
1205
1179
  ownerDir: slotDir,
1206
1180
  ownerTreePath,
@@ -1259,9 +1233,6 @@ function isInterceptionMarkerDir(name) {
1259
1233
  * Intercepting routes use conventions like (.)photo, (..)feed, (...), etc.
1260
1234
  * They intercept navigation to another route and render within the slot instead.
1261
1235
  *
1262
- * `slotDir`, `routeDir`, and `appDir` must be forward-slash. They are passed
1263
- * down to `path.posix.join` when building the intercept page paths.
1264
- *
1265
1236
  * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
1266
1237
  * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
1267
1238
  * @param appDir - The root app directory
@@ -1280,17 +1251,13 @@ function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
1280
1251
  * Sibling intercepts use the same conventions and target-computation logic as
1281
1252
  * slot intercepts, but their intercepting page replaces the full page response
1282
1253
  * (not a slot) during soft navigation.
1283
- *
1284
- * `appDir` and each route's `pagePath` / `routePath` must be forward-slash. The
1285
- * owner directories are derived from `pagePath` / `routePath` and matched
1286
- * against `appDir`-relative paths built with `path.posix.*`.
1287
1254
  */
1288
1255
  function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1289
1256
  const routesByDir = /* @__PURE__ */ new Map();
1290
1257
  for (const route of routes) {
1291
1258
  const filePath = route.pagePath ?? route.routePath;
1292
1259
  if (!filePath) continue;
1293
- const routeDir = path.posix.dirname(filePath);
1260
+ const routeDir = path.dirname(filePath);
1294
1261
  if (!routesByDir.has(routeDir)) routesByDir.set(routeDir, route);
1295
1262
  }
1296
1263
  function walk(dir) {
@@ -1304,7 +1271,7 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1304
1271
  if (!entry.isDirectory()) continue;
1305
1272
  if (entry.name.startsWith("_")) continue;
1306
1273
  if (entry.name.startsWith("@")) continue;
1307
- const childDir = path.posix.join(dir, entry.name);
1274
+ const childDir = path.join(dir, entry.name);
1308
1275
  const marker = matchInterceptConvention(entry.name);
1309
1276
  if (marker) {
1310
1277
  const restOfName = entry.name.slice(marker.prefix.length);
@@ -1335,18 +1302,15 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1335
1302
  * no sibling pages at all (e.g. `deep/path/(...)target` with no
1336
1303
  * `deep/path/page.tsx`).
1337
1304
  *
1338
- * All comparisons happen in forward-slash space: `appDir` is forward-slash
1339
- * (normalized once in the config hook), but `dir` and route file paths
1340
- * descend through native `path.join`/`path.dirname`, which reintroduce
1341
- * backslashes on Windows. Without normalizing, the `current === appDir`
1342
- * termination never fires there and the walk overshoots the app root.
1343
- * `routesByDir` keys must be forward-slash dirnames of the route file paths.
1305
+ * Comparisons happen in forward-slash space — inputs go through `toSlash` so
1306
+ * callers still holding native-separator paths hit the `current === appDir`
1307
+ * termination instead of overshooting the app root.
1344
1308
  *
1345
1309
  * Exported for tests.
1346
1310
  */
1347
1311
  function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1348
- const appRoot = normalizePathSeparators(appDir);
1349
- let current = normalizePathSeparators(dir);
1312
+ const appRoot = toSlash(appDir);
1313
+ let current = toSlash(dir);
1350
1314
  while (true) {
1351
1315
  const exact = routesByDir.get(current);
1352
1316
  if (exact) return exact;
@@ -1355,7 +1319,7 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1355
1319
  for (const route of routes) {
1356
1320
  const filePath = route.pagePath ?? route.routePath;
1357
1321
  if (!filePath) continue;
1358
- if (!normalizePathSeparators(filePath).startsWith(currentWithSep)) continue;
1322
+ if (!toSlash(filePath).startsWith(currentWithSep)) continue;
1359
1323
  if (!best || route.patternParts.length < best.patternParts.length) best = route;
1360
1324
  }
1361
1325
  if (best) return best;
@@ -1369,9 +1333,6 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1369
1333
  /**
1370
1334
  * Recursively scan a directory tree for page.tsx files that are inside
1371
1335
  * intercepting route directories.
1372
- *
1373
- * `currentDir`, `routeDir`, and `appDir` must be forward-slash. `currentDir`
1374
- * descends with `path.posix.join` when building the intercept page paths.
1375
1336
  */
1376
1337
  function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1377
1338
  if (!fs.existsSync(currentDir)) return;
@@ -1380,7 +1341,7 @@ function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher
1380
1341
  if (!entry.isDirectory()) continue;
1381
1342
  if (entry.name.startsWith("_")) continue;
1382
1343
  const interceptMatch = matchInterceptConvention(entry.name);
1383
- const interceptDir = path.posix.join(currentDir, entry.name);
1344
+ const interceptDir = path.join(currentDir, entry.name);
1384
1345
  if (interceptMatch) {
1385
1346
  const restOfName = entry.name.slice(interceptMatch.prefix.length);
1386
1347
  collectInterceptingPages(interceptDir, interceptDir, interceptMatch.convention, restOfName, routeDir, appDir, currentDir, results, matcher);
@@ -1397,12 +1358,6 @@ function matchInterceptConvention(name) {
1397
1358
  /**
1398
1359
  * Collect page.tsx files inside an intercepting route directory tree
1399
1360
  * and compute their target URL patterns.
1400
- *
1401
- * `currentDir`, `interceptRoot`, `routeDir`, `appDir`, and `interceptParentDir`
1402
- * must all be forward-slash. `currentDir` descends with `path.posix.join` and
1403
- * its `findFile` results become the stored layout/page paths. The others are
1404
- * relativized against `appDir` (and each other) to derive the intercept page
1405
- * segments and URL patterns.
1406
1361
  */
1407
1362
  function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, parentLayoutPaths = []) {
1408
1363
  const currentLayoutPath = findFile(currentDir, "layout", matcher);
@@ -1422,7 +1377,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1422
1377
  targetPattern: targetPattern.pattern,
1423
1378
  sourceMatchPattern,
1424
1379
  pagePath: page,
1425
- sourcePageSegments: normalizePathSeparators(path.relative(appDir, path.dirname(page))).split("/").filter(Boolean),
1380
+ sourcePageSegments: path.relative(appDir, path.dirname(page)).split("/").filter(Boolean),
1426
1381
  params: targetPattern.params
1427
1382
  });
1428
1383
  }
@@ -1432,7 +1387,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1432
1387
  for (const entry of entries) {
1433
1388
  if (!entry.isDirectory()) continue;
1434
1389
  if (entry.name.startsWith("_")) continue;
1435
- collectInterceptingPages(path.posix.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1390
+ collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1436
1391
  }
1437
1392
  }
1438
1393
  /**
@@ -1552,9 +1507,6 @@ const findFileProbeCache = /* @__PURE__ */ new WeakMap();
1552
1507
  * registered per-scan cache; otherwise falls back to a direct probe (identical
1553
1508
  * result). The `null` "not found" outcome is cached too, so repeated misses on
1554
1509
  * shared ancestors cost a single set of `existsSync` calls per scan.
1555
- *
1556
- * `dir` must be forward-slash. The returned path comes from `findFileWithExts`,
1557
- * so it is forward-slash too.
1558
1510
  */
1559
1511
  function findFile(dir, name, matcher) {
1560
1512
  const cache = findFileProbeCache.get(matcher);
@@ -12,16 +12,11 @@ declare function invalidateAppRouteCache(): void;
12
12
  * TODO(#726): Layer 4 should consume this read model directly once the
13
13
  * navigation planner owns route graph facts.
14
14
  *
15
- * `appDir` must be forward-slash — callers normalize it at their entry, and it
16
- * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
17
- *
18
15
  * @internal
19
16
  */
20
17
  declare function appRouteGraph(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraph>;
21
18
  /**
22
19
  * Scan the app/ directory and return a list of routes.
23
- *
24
- * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
25
20
  */
26
21
  declare function appRouter(appDir: string, pageExtensions?: readonly string[], matcher?: ValidFileMatcher): Promise<AppRouteGraphRoute[]>;
27
22
  /**
@@ -30,9 +30,6 @@ function invalidateAppRouteCache() {
30
30
  * TODO(#726): Layer 4 should consume this read model directly once the
31
31
  * navigation planner owns route graph facts.
32
32
  *
33
- * `appDir` must be forward-slash — callers normalize it at their entry, and it
34
- * flows into `buildAppRouteGraph`, which builds every path with `path.posix.*`.
35
- *
36
33
  * @internal
37
34
  */
38
35
  async function appRouteGraph(appDir, pageExtensions, matcher) {
@@ -47,8 +44,6 @@ async function appRouteGraph(appDir, pageExtensions, matcher) {
47
44
  }
48
45
  /**
49
46
  * Scan the app/ directory and return a list of routes.
50
- *
51
- * `appDir` must be forward-slash — it is forwarded to `appRouteGraph`.
52
47
  */
53
48
  async function appRouter(appDir, pageExtensions, matcher) {
54
49
  return (await appRouteGraph(appDir, pageExtensions, matcher)).routes;
@@ -21,9 +21,6 @@ declare function findFileWithExtensions(basePath: string, matcher: ValidFileMatc
21
21
  /**
22
22
  * Find a file by basename and configured page extension in a directory.
23
23
  * Returns the first matching absolute path, or null if not found.
24
- *
25
- * `dir` must be forward-slash. The returned path is built with `path.posix.join`,
26
- * so it is forward-slash too.
27
24
  */
28
25
  declare function findFileWithExts(dir: string, name: string, matcher: ValidFileMatcher): string | null;
29
26
  /**
@@ -66,9 +63,8 @@ declare function normalizeViteResolveExtensions(extensions: readonly string[]):
66
63
  * Use function-form exclude for Node < 22.14 compatibility.
67
64
  *
68
65
  * Yields forward-slash relative paths: node's glob emits native (backslash)
69
- * separators on Windows, so each match is normalized — this is the entry point
70
- * that lets downstream consumers treat the scanned paths as canonical
71
- * forward-slash ids.
66
+ * separators on Windows, so each match goes through `toSlash` — this is the
67
+ * boundary where external fs output enters the canonical forward-slash space.
72
68
  */
73
69
  declare function scanWithExtensions(stem: string, cwd: string, extensions: readonly string[], exclude?: (name: string) => boolean): AsyncGenerator<string>;
74
70
  //#endregion
@@ -1,7 +1,6 @@
1
- import { normalizePathSeparators } from "../utils/path.js";
1
+ import path, { toSlash } from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
2
2
  import { escapeRegExp } from "../utils/regex.js";
3
3
  import { existsSync } from "node:fs";
4
- import path from "node:path";
5
4
  import { glob } from "node:fs/promises";
6
5
  //#region src/routing/file-matcher.ts
7
6
  const DEFAULT_PAGE_EXTENSIONS = [
@@ -19,6 +18,10 @@ function buildExtensionGlob(stem, extensions) {
19
18
  if (extensions.length === 1) return `${stem}.${extensions[0]}`;
20
19
  return `${stem}.{${extensions.join(",")}}`;
21
20
  }
21
+ function includeDotDirectoryMatches(pattern) {
22
+ if (!pattern.startsWith("**/")) return pattern;
23
+ return `{**,**/.*/**}/${pattern.slice(3)}`;
24
+ }
22
25
  /**
23
26
  * Ported in spirit from Next.js createValidFileMatcher:
24
27
  * packages/next/src/server/lib/find-page-file.ts
@@ -67,13 +70,10 @@ function findFileWithExtensions(basePath, matcher) {
67
70
  /**
68
71
  * Find a file by basename and configured page extension in a directory.
69
72
  * Returns the first matching absolute path, or null if not found.
70
- *
71
- * `dir` must be forward-slash. The returned path is built with `path.posix.join`,
72
- * so it is forward-slash too.
73
73
  */
74
74
  function findFileWithExts(dir, name, matcher) {
75
75
  for (const ext of matcher.dottedExtensions) {
76
- const filePath = path.posix.join(dir, name + ext);
76
+ const filePath = path.join(dir, name + ext);
77
77
  if (existsSync(filePath)) return filePath;
78
78
  }
79
79
  return null;
@@ -153,16 +153,15 @@ function normalizeViteResolveExtensions(extensions) {
153
153
  * Use function-form exclude for Node < 22.14 compatibility.
154
154
  *
155
155
  * Yields forward-slash relative paths: node's glob emits native (backslash)
156
- * separators on Windows, so each match is normalized — this is the entry point
157
- * that lets downstream consumers treat the scanned paths as canonical
158
- * forward-slash ids.
156
+ * separators on Windows, so each match goes through `toSlash` — this is the
157
+ * boundary where external fs output enters the canonical forward-slash space.
159
158
  */
160
159
  async function* scanWithExtensions(stem, cwd, extensions, exclude) {
161
- const pattern = buildExtensionGlob(stem, extensions);
160
+ const pattern = includeDotDirectoryMatches(buildExtensionGlob(stem, extensions));
162
161
  for await (const file of glob(pattern, {
163
162
  cwd,
164
163
  ...exclude ? { exclude } : {}
165
- })) yield normalizePathSeparators(file);
164
+ })) yield toSlash(file);
166
165
  }
167
166
  //#endregion
168
167
  export { buildViteResolveExtensions, createValidFileMatcher, findFileWithExtensions, findFileWithExts, normalizePageExtensions, normalizeViteResolveExtensions, scanWithExtensions };
@@ -1,11 +1,11 @@
1
+ import path from "../deps/.pnpm/pathslash@0.1.0/deps/pathslash/dist/index.js";
1
2
  import { decodeRouteSegment, sortRoutes } from "./utils.js";
2
3
  import { createValidFileMatcher, scanWithExtensions } from "./file-matcher.js";
3
4
  import { patternToNextFormat, validateRoutePatterns } from "./route-validation.js";
4
5
  import { createRouteTrieCache, matchRouteWithTrie } from "./route-matching.js";
5
- import path from "node:path";
6
6
  //#region src/routing/pages-router.ts
7
7
  /** Next.js special pages that should not produce routes. */
8
- const RESERVED_PAGE_NAMES = new Set([
8
+ const RESERVED_PAGE_NAMES = /* @__PURE__ */ new Set([
9
9
  "_app",
10
10
  "_document",
11
11
  "_error"
@@ -152,7 +152,7 @@ async function scanApiRoutes(pagesDir, matcher) {
152
152
  }
153
153
  const routes = [];
154
154
  for (const file of files) {
155
- const route = fileToRoute(path.posix.join("api", file), pagesDir, matcher);
155
+ const route = fileToRoute(path.join("api", file), pagesDir, matcher);
156
156
  if (route) routes.push(route);
157
157
  }
158
158
  validateRoutePatterns(routes.map((route) => route.pattern));
@@ -8,7 +8,7 @@ import { DEFAULT_PAGES_API_BODY_SIZE_LIMIT, resolveBodyParserConfig } from "./pa
8
8
  import { PagesBodyParseError, getMediaType, isJsonMediaType } from "./pages-media-type.js";
9
9
  import { resolveRequestHost, resolveRequestProtocol } from "./proxy-trust.js";
10
10
  import { performOnDemandRevalidate } from "./pages-revalidate.js";
11
- import { attachPagesRequestCookies } from "./pages-node-compat.js";
11
+ import { attachPagesPreviewApi, attachPagesRequestCookies } from "./pages-node-compat.js";
12
12
  import { isEdgeApiRuntime } from "./edge-api-runtime.js";
13
13
  import { decode } from "node:querystring";
14
14
  import { Buffer } from "node:buffer";
@@ -166,41 +166,49 @@ function enhanceApiObjects(req, res, query, body) {
166
166
  query
167
167
  });
168
168
  attachPagesRequestCookies(apiReq);
169
- return {
170
- apiReq,
171
- apiRes: Object.assign(res, {
172
- status(code) {
173
- this.statusCode = code;
174
- return this;
175
- },
176
- json(data) {
169
+ const apiRes = Object.assign(res, {
170
+ status(code) {
171
+ this.statusCode = code;
172
+ return this;
173
+ },
174
+ json(data) {
175
+ this.setHeader("Content-Type", "application/json");
176
+ this.end(JSON.stringify(data));
177
+ },
178
+ send(data) {
179
+ if (Buffer.isBuffer(data)) {
180
+ if (!this.getHeader("Content-Type")) this.setHeader("Content-Type", "application/octet-stream");
181
+ this.setHeader("Content-Length", String(data.length));
182
+ this.end(data);
183
+ return;
184
+ }
185
+ if (typeof data === "object" && data !== null) {
177
186
  this.setHeader("Content-Type", "application/json");
178
187
  this.end(JSON.stringify(data));
179
- },
180
- send(data) {
181
- if (Buffer.isBuffer(data)) {
182
- if (!this.getHeader("Content-Type")) this.setHeader("Content-Type", "application/octet-stream");
183
- this.setHeader("Content-Length", String(data.length));
184
- this.end(data);
185
- return;
186
- }
187
- if (typeof data === "object" && data !== null) {
188
- this.setHeader("Content-Type", "application/json");
189
- this.end(JSON.stringify(data));
190
- } else {
191
- if (!this.getHeader("Content-Type")) this.setHeader("Content-Type", "text/plain");
192
- this.end(String(data));
193
- }
194
- },
195
- redirect(statusOrUrl, url) {
196
- if (typeof statusOrUrl === "string") this.writeHead(307, { Location: statusOrUrl });
197
- else this.writeHead(statusOrUrl, { Location: url ?? "" });
198
- this.end();
199
- },
200
- async revalidate(urlPath, opts) {
201
- await performOnDemandRevalidate(req, urlPath, opts);
188
+ } else {
189
+ if (!this.getHeader("Content-Type")) this.setHeader("Content-Type", "text/plain");
190
+ this.end(String(data));
191
+ }
192
+ },
193
+ redirect(statusOrUrl, url) {
194
+ if (typeof statusOrUrl === "string") {
195
+ url = statusOrUrl;
196
+ statusOrUrl = 307;
202
197
  }
203
- })
198
+ if (typeof statusOrUrl !== "number" || typeof url !== "string") throw new Error("Invalid redirect arguments. Please use a single argument URL, e.g. res.redirect('/destination') or use a status code and URL, e.g. res.redirect(307, '/destination').");
199
+ this.writeHead(statusOrUrl, { Location: url });
200
+ this.write(url);
201
+ this.end();
202
+ return this;
203
+ },
204
+ async revalidate(urlPath, opts) {
205
+ await performOnDemandRevalidate(req, urlPath, opts);
206
+ }
207
+ });
208
+ attachPagesPreviewApi(apiReq, apiRes);
209
+ return {
210
+ apiReq,
211
+ apiRes
204
212
  };
205
213
  }
206
214
  /**
@@ -1,6 +1,5 @@
1
- import { VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RSC_REDIRECT_HEADER } from "./headers.js";
1
+ import { VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RSC_REDIRECT_HEADER, VINEXT_RSC_REDIRECT_TYPE_HEADER } from "./headers.js";
2
2
  import { AppElementsWire } from "./app-elements-wire.js";
3
- import { APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI } from "./app-rsc-render-mode.js";
4
3
  import { getMountedSlotIdsHeader, resolveVisitedResponseInterceptionContext } from "./app-elements.js";
5
4
  import { AppRouterContext } from "../shims/internal/app-router-context.js";
6
5
  import { installWindowNext, setWindowNextInternalSourcePage } from "../client/window-next.js";
@@ -598,9 +597,10 @@ function BrowserRoot({ hydrationCachePublication, initialElements, initialNaviga
598
597
  resetKey: treeState.renderId,
599
598
  onCatch: handleDevRecoveryBoundaryCatch
600
599
  }, innerTree) : innerTree;
600
+ const scrollScopedTree = createElement(AppRouterScrollCommitProvider, { commitId: treeState.renderId }, committedTree);
601
601
  const rootErrorTree = createElement(GlobalErrorBoundary, {
602
602
  fallback: DEFAULT_GLOBAL_ERROR_COMPONENT,
603
- children: createElement(AppRouterScrollCommitProvider, { commitId: treeState.renderId }, committedTree)
603
+ children: scrollScopedTree
604
604
  });
605
605
  const ClientNavigationRenderContext = getClientNavigationRenderContext();
606
606
  if (!ClientNavigationRenderContext) return rootErrorTree;
@@ -764,7 +764,8 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
764
764
  const metadata = AppElementsWire.readMetadata(elements);
765
765
  if (!isCompleteAppPayloadMetadata(metadata)) return;
766
766
  const mountedSlotsHeader = getMountedSlotIdsHeader(elements);
767
- const rscUrl = await createRscRequestUrl(initialPathAndSearch, createRscRequestHeaders({ mountedSlotsHeader }));
767
+ const headers = createRscRequestHeaders({ mountedSlotsHeader });
768
+ const rscUrl = await createRscRequestUrl(initialPathAndSearch, headers);
768
769
  if (cacheGeneration !== clientNavigationCacheGeneration) return;
769
770
  const snapshot = {
770
771
  compatibilityIdHeader: CLIENT_RSC_COMPATIBILITY_ID,
@@ -852,9 +853,9 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
852
853
  let redirectCount = redirectDepth;
853
854
  let detachedNavigationCommits = false;
854
855
  const activeTraversalIntent = navigationKind === "traverse" ? traversalIntent ?? historyController.resolveTraversalIntent(window.history.state) : null;
855
- const performHardNavigationForScrollIntent = (targetHref) => {
856
+ const performHardNavigationForScrollIntent = (targetHref, mode) => {
856
857
  consumeAppRouterScrollIntent(scrollIntent ?? null);
857
- const didNavigate = browserNavigationController.performHardNavigation(targetHref);
858
+ const didNavigate = browserNavigationController.performHardNavigation(targetHref, mode);
858
859
  if (!didNavigate) clearAppNavigationFailureTarget(targetHref);
859
860
  return didNavigate;
860
861
  };
@@ -888,8 +889,7 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
888
889
  const shouldBypassNavigationCache = earlyIntentDecision?.kind === "flightNavigation" && earlyIntentDecision.bypassNavigationCache;
889
890
  const requestHeaders = createRscRequestHeaders({
890
891
  interceptionContext: requestInterceptionContext,
891
- mountedSlotsHeader,
892
- renderMode: navigationKind === "refresh" ? APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI : void 0
892
+ mountedSlotsHeader
893
893
  });
894
894
  const rscUrl = await createRscRequestUrl(url.pathname + url.search, requestHeaders);
895
895
  const rewrittenNavigationHref = navigationKind === "navigate" ? resolveHybridClientRewriteHref(currentHref, __basePath) : null;
@@ -1035,6 +1035,8 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1035
1035
  if (!browserNavigationController.isCurrentNavigation(navId)) return;
1036
1036
  const navContentType = navResponse.headers.get("content-type") ?? "";
1037
1037
  const streamedRedirectTarget = navResponse.headers.get(VINEXT_RSC_REDIRECT_HEADER);
1038
+ const streamedRedirectTypeHeader = navResponse.headers.get(VINEXT_RSC_REDIRECT_TYPE_HEADER);
1039
+ const streamedRedirectType = streamedRedirectTypeHeader === "push" || streamedRedirectTypeHeader === "replace" ? streamedRedirectTypeHeader : null;
1038
1040
  if (blockDangerousStreamedRscRedirect(navResponse, streamedRedirectTarget)) return;
1039
1041
  const liveFetchDecision = navigationPlanner.classifyRscFetchResult({
1040
1042
  clientCompatibilityId: CLIENT_RSC_COMPATIBILITY_ID,
@@ -1049,13 +1051,14 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1049
1051
  responseOk: navResponse.ok,
1050
1052
  responseUrl: navResponseUrl ?? navResponse.url,
1051
1053
  source: "live",
1052
- streamedRedirectTarget
1054
+ streamedRedirectTarget,
1055
+ streamedRedirectType
1053
1056
  });
1054
1057
  if (liveFetchDecision.kind === "hardNavigate") {
1055
1058
  if (liveFetchDecision.discardBody) navResponse.body?.cancel().catch(() => {});
1056
1059
  if (liveFetchDecision.reason === "redirectDepthExhausted") console.error("[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.");
1057
1060
  if (liveFetchDecision.reason === "streamedRedirectLoop") console.error("[vinext] RSC streamed redirect resolved to the current URL — aborting navigation to prevent infinite loop.");
1058
- performHardNavigationForScrollIntent(liveFetchDecision.url);
1061
+ performHardNavigationForScrollIntent(liveFetchDecision.url, liveFetchDecision.hardNavigationMode);
1059
1062
  return;
1060
1063
  }
1061
1064
  if (liveFetchDecision.kind === "followRedirect") {
@@ -1206,7 +1209,8 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1206
1209
  }
1207
1210
  };
1208
1211
  import.meta.hot.on("rsc:update", () => {
1209
- handleRscUpdate(++latestRscHmrUpdateId);
1212
+ const updateId = ++latestRscHmrUpdateId;
1213
+ handleRscUpdate(updateId);
1210
1214
  });
1211
1215
  }
1212
1216
  }
@@ -259,12 +259,14 @@ function createAppBrowserNavigationController(deps = {}) {
259
259
  }
260
260
  if (visibleCommitMode === "synchronous") {
261
261
  flushSync(() => {
262
- setter(captureCandidateState(applyApprovedVisibleCommit(getBrowserRouterState(), commit)));
262
+ const committedState = captureCandidateState(applyApprovedVisibleCommit(getBrowserRouterState(), commit));
263
+ setter(committedState);
263
264
  });
264
265
  return;
265
266
  }
266
267
  startTransition(() => {
267
- setter(captureCandidateState(applyApprovedVisibleCommit(getBrowserRouterState(), commit)));
268
+ const committedState = captureCandidateState(applyApprovedVisibleCommit(getBrowserRouterState(), commit));
269
+ setter(committedState);
268
270
  });
269
271
  }
270
272
  function dispatchSynchronousVisibleCommit(commit) {
@@ -449,8 +451,11 @@ function createAppBrowserNavigationController(deps = {}) {
449
451
  return;
450
452
  }
451
453
  if (latestApproval.approvedCommit) {
452
- dispatchSynchronousVisibleCommit(latestApproval.approvedCommit);
453
- syncHistoryStatePreviousNextUrl(latestApproval.approvedCommit.previousNextUrl, latestApproval.approvedCommit.action.bfcacheIds);
454
+ const approvedRevalidationCommit = latestApproval.approvedCommit;
455
+ startTransition(() => {
456
+ dispatchSynchronousVisibleCommit(approvedRevalidationCommit);
457
+ });
458
+ syncHistoryStatePreviousNextUrl(approvedRevalidationCommit.previousNextUrl, approvedRevalidationCommit.action.bfcacheIds);
454
459
  } else notifyDiscardedServerActionRevalidation(lifecycleOptions);
455
460
  } else if (decision.disposition === "no-commit") notifyDiscardedServerActionRevalidation(lifecycleOptions);
456
461
  if (returnValue) {
@@ -49,7 +49,7 @@ var ServerActionRedirectError = class extends Error {
49
49
  const redirectUrl = new URL(target.href, window.location.href);
50
50
  const redirectHref = redirectUrl.pathname + redirectUrl.search + redirectUrl.hash;
51
51
  const redirectType = target.type === "push" ? "push" : "replace";
52
- this.digest = `NEXT_REDIRECT;${redirectType};${encodeURIComponent(redirectHref)};${target.status};`;
52
+ this.digest = `NEXT_REDIRECT;${redirectType};${redirectHref};${target.status};`;
53
53
  }
54
54
  };
55
55
  async function invokeClientServerAction(id, args, actionInitiation, deps) {