vinext 0.2.0 → 1.0.0-beta.0

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 (168) hide show
  1. package/README.md +50 -29
  2. package/dist/build/client-build-config.d.ts +13 -92
  3. package/dist/build/client-build-config.js +17 -95
  4. package/dist/build/css-url-assets.d.ts +3 -1
  5. package/dist/build/css-url-assets.js +18 -1
  6. package/dist/build/inject-pregenerated-paths.d.ts +3 -0
  7. package/dist/build/inject-pregenerated-paths.js +4 -1
  8. package/dist/build/prerender-paths.d.ts +23 -0
  9. package/dist/build/prerender-paths.js +296 -0
  10. package/dist/build/prerender-server-entry.d.ts +1 -0
  11. package/dist/build/prerender-server-entry.js +49 -0
  12. package/dist/build/prerender-server-pool.d.ts +44 -0
  13. package/dist/build/prerender-server-pool.js +194 -0
  14. package/dist/build/prerender.d.ts +12 -1
  15. package/dist/build/prerender.js +87 -23
  16. package/dist/build/run-prerender.js +2 -1
  17. package/dist/cache/cache-adapters-virtual.d.ts +7 -1
  18. package/dist/cache/cache-adapters-virtual.js +26 -2
  19. package/dist/check.d.ts +4 -6
  20. package/dist/check.js +13 -9
  21. package/dist/cli.js +34 -54
  22. package/dist/client/vinext-next-data.d.ts +4 -1
  23. package/dist/config/config-matchers.js +33 -11
  24. package/dist/config/next-config.d.ts +36 -6
  25. package/dist/config/next-config.js +17 -4
  26. package/dist/config/prerender.d.ts +11 -1
  27. package/dist/config/prerender.js +19 -1
  28. package/dist/config/tsconfig-paths.js +5 -1
  29. package/dist/entries/app-browser-entry.js +5 -1
  30. package/dist/entries/app-rsc-entry.d.ts +3 -2
  31. package/dist/entries/app-rsc-entry.js +4 -0
  32. package/dist/entries/pages-client-entry.js +14 -3
  33. package/dist/entries/pages-server-entry.js +18 -4
  34. package/dist/image/image-adapters-virtual.js +1 -0
  35. package/dist/index.d.ts +1 -1
  36. package/dist/index.js +549 -152
  37. package/dist/init-cloudflare.js +12 -3
  38. package/dist/init-platform.d.ts +10 -1
  39. package/dist/init-platform.js +78 -12
  40. package/dist/init.d.ts +11 -3
  41. package/dist/init.js +129 -31
  42. package/dist/plugins/fonts.js +1 -1
  43. package/dist/plugins/ignore-dynamic-requests.js +1 -1
  44. package/dist/plugins/og-assets.js +2 -1
  45. package/dist/plugins/optimize-imports.js +1 -1
  46. package/dist/plugins/rsc-reference-validation-normalizer.d.ts +12 -0
  47. package/dist/plugins/rsc-reference-validation-normalizer.js +48 -0
  48. package/dist/plugins/sass.d.ts +17 -1
  49. package/dist/plugins/sass.js +74 -1
  50. package/dist/plugins/styled-jsx.d.ts +16 -0
  51. package/dist/plugins/styled-jsx.js +149 -0
  52. package/dist/routing/app-route-graph.js +22 -16
  53. package/dist/routing/file-matcher.d.ts +8 -1
  54. package/dist/routing/file-matcher.js +15 -3
  55. package/dist/server/app-browser-entry.js +44 -14
  56. package/dist/server/app-browser-navigation-controller.js +5 -2
  57. package/dist/server/app-browser-server-action-client.js +5 -3
  58. package/dist/server/app-browser-state.d.ts +1 -0
  59. package/dist/server/app-browser-state.js +1 -1
  60. package/dist/server/app-layout-param-observation.d.ts +1 -1
  61. package/dist/server/app-page-boundary.js +2 -1
  62. package/dist/server/app-page-cache-finalizer.d.ts +1 -0
  63. package/dist/server/app-page-cache-finalizer.js +3 -3
  64. package/dist/server/app-page-cache-render.d.ts +1 -1
  65. package/dist/server/app-page-cache.js +23 -6
  66. package/dist/server/app-page-dispatch.d.ts +2 -1
  67. package/dist/server/app-page-dispatch.js +27 -10
  68. package/dist/server/app-page-element-builder.js +1 -0
  69. package/dist/server/app-page-execution.js +2 -1
  70. package/dist/server/app-page-render-identity.d.ts +1 -0
  71. package/dist/server/app-page-render-identity.js +2 -1
  72. package/dist/server/app-page-render.d.ts +3 -1
  73. package/dist/server/app-page-render.js +67 -16
  74. package/dist/server/app-page-response.d.ts +7 -0
  75. package/dist/server/app-page-response.js +16 -4
  76. package/dist/server/app-page-route-wiring.js +3 -3
  77. package/dist/server/app-page-stream.d.ts +2 -1
  78. package/dist/server/app-page-stream.js +1 -1
  79. package/dist/server/app-route-handler-execution.d.ts +1 -1
  80. package/dist/server/app-route-tree-prefetch.d.ts +43 -0
  81. package/dist/server/app-route-tree-prefetch.js +187 -0
  82. package/dist/server/app-router-entry.js +1 -1
  83. package/dist/server/app-rsc-cache-busting.d.ts +2 -1
  84. package/dist/server/app-rsc-cache-busting.js +9 -5
  85. package/dist/server/app-rsc-handler.d.ts +6 -0
  86. package/dist/server/app-rsc-handler.js +88 -10
  87. package/dist/server/app-rsc-render-mode.d.ts +3 -5
  88. package/dist/server/app-rsc-render-mode.js +5 -12
  89. package/dist/server/app-rsc-request-normalization.d.ts +3 -4
  90. package/dist/server/app-rsc-request-normalization.js +4 -5
  91. package/dist/server/app-server-action-execution.js +6 -5
  92. package/dist/server/app-ssr-entry.js +1 -1
  93. package/dist/server/cache-control.d.ts +3 -1
  94. package/dist/server/cache-control.js +13 -1
  95. package/dist/server/dev-module-runner.js +1 -1
  96. package/dist/server/dev-origin-check.d.ts +2 -2
  97. package/dist/server/dev-origin-check.js +2 -2
  98. package/dist/server/dev-server.d.ts +11 -1
  99. package/dist/server/dev-server.js +82 -24
  100. package/dist/server/headers.d.ts +6 -2
  101. package/dist/server/headers.js +11 -5
  102. package/dist/server/image-optimization.d.ts +12 -1
  103. package/dist/server/image-optimization.js +13 -1
  104. package/dist/server/isr-cache.d.ts +13 -4
  105. package/dist/server/isr-cache.js +8 -4
  106. package/dist/server/pages-data-route.d.ts +4 -2
  107. package/dist/server/pages-data-route.js +18 -4
  108. package/dist/server/pages-dev-module-url.d.ts +2 -1
  109. package/dist/server/pages-dev-module-url.js +6 -3
  110. package/dist/server/pages-node-compat.d.ts +12 -1
  111. package/dist/server/pages-node-compat.js +50 -1
  112. package/dist/server/pages-page-data.d.ts +9 -0
  113. package/dist/server/pages-page-data.js +18 -10
  114. package/dist/server/pages-page-handler.js +19 -7
  115. package/dist/server/pages-page-response.d.ts +1 -0
  116. package/dist/server/pages-page-response.js +5 -4
  117. package/dist/server/pages-request-pipeline.d.ts +8 -6
  118. package/dist/server/pages-request-pipeline.js +31 -7
  119. package/dist/server/pages-router-entry.js +1 -1
  120. package/dist/server/prerender-manifest.d.ts +15 -1
  121. package/dist/server/prerender-manifest.js +29 -1
  122. package/dist/server/prod-server.d.ts +10 -7
  123. package/dist/server/prod-server.js +43 -28
  124. package/dist/server/request-pipeline.js +1 -1
  125. package/dist/server/static-file-cache.js +1 -1
  126. package/dist/shims/cache-handler.js +8 -1
  127. package/dist/shims/cache.d.ts +1 -1
  128. package/dist/shims/cache.js +3 -0
  129. package/dist/shims/cdn-cache.d.ts +2 -7
  130. package/dist/shims/cdn-cache.js +2 -14
  131. package/dist/shims/fetch-cache.d.ts +3 -1
  132. package/dist/shims/fetch-cache.js +77 -52
  133. package/dist/shims/internal/app-prefetch-fetch-queue.d.ts +10 -0
  134. package/dist/shims/internal/app-prefetch-fetch-queue.js +61 -0
  135. package/dist/shims/internal/hybrid-client-route-owner.d.ts +2 -1
  136. package/dist/shims/internal/hybrid-client-route-owner.js +34 -1
  137. package/dist/shims/internal/pages-data-fetch-dedup.d.ts +2 -1
  138. package/dist/shims/internal/pages-data-fetch-dedup.js +13 -2
  139. package/dist/shims/internal/pages-data-target.d.ts +8 -3
  140. package/dist/shims/internal/pages-data-target.js +9 -4
  141. package/dist/shims/link.d.ts +1 -1
  142. package/dist/shims/link.js +179 -42
  143. package/dist/shims/navigation.d.ts +15 -3
  144. package/dist/shims/navigation.js +202 -39
  145. package/dist/shims/request-context.js +18 -0
  146. package/dist/shims/request-state-types.d.ts +2 -2
  147. package/dist/shims/router.js +31 -15
  148. package/dist/shims/unified-request-context.d.ts +1 -1
  149. package/dist/shims/unified-request-context.js +1 -0
  150. package/dist/typegen.js +1 -1
  151. package/dist/utils/middleware-request-headers.js +1 -1
  152. package/dist/utils/project.d.ts +1 -1
  153. package/dist/utils/protocol-headers.d.ts +7 -1
  154. package/dist/utils/protocol-headers.js +7 -1
  155. package/dist/utils/vite-version.d.ts +4 -12
  156. package/dist/utils/vite-version.js +39 -21
  157. package/package.json +31 -13
  158. package/dist/cloudflare/index.d.ts +0 -3
  159. package/dist/cloudflare/index.js +0 -3
  160. package/dist/packages/cloudflare/src/cache/cdn-adapter.runtime.js +0 -102
  161. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.d.ts +0 -126
  162. package/dist/packages/cloudflare/src/cache/kv-data-adapter.runtime.js +0 -435
  163. package/dist/packages/cloudflare/src/deploy-config.js +0 -150
  164. package/dist/packages/cloudflare/src/deploy-help.js +0 -55
  165. package/dist/packages/cloudflare/src/deploy.js +0 -276
  166. package/dist/packages/cloudflare/src/tpr.d.ts +0 -45
  167. package/dist/packages/cloudflare/src/tpr.js +0 -561
  168. package/dist/packages/cloudflare/src/utils/cache-control-metadata.js +0 -20
@@ -1,3 +1,4 @@
1
+ import { markCssUrlAssetReferences, rebaseCssUrlAssetReferences } from "../build/css-url-assets.js";
1
2
  import { createRequire } from "node:module";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
@@ -75,6 +76,78 @@ function createSassTildeImporter(root) {
75
76
  }
76
77
  } };
77
78
  }
79
+ function sassStylesheetCandidates(importPath) {
80
+ const extension = path.extname(importPath);
81
+ const directory = path.dirname(importPath);
82
+ const basename = path.basename(importPath, extension);
83
+ const candidates = [];
84
+ const extensions = extension ? [extension] : [
85
+ ".scss",
86
+ ".sass",
87
+ ".css"
88
+ ];
89
+ for (const candidateExtension of extensions) {
90
+ const basePath = extension ? importPath : `${importPath}${candidateExtension}`;
91
+ candidates.push(basePath);
92
+ candidates.push(path.join(directory, `_${basename}${candidateExtension}`));
93
+ }
94
+ for (const candidateExtension of extensions) {
95
+ candidates.push(path.join(importPath, `index${candidateExtension}`));
96
+ candidates.push(path.join(importPath, `_index${candidateExtension}`));
97
+ }
98
+ return candidates;
99
+ }
100
+ function deriveSassNamespace(importUrl) {
101
+ const parts = importUrl.replaceAll("\\", "/").replace(/\/$/, "").split("/");
102
+ let basename = parts.pop() ?? "stylesheet";
103
+ basename = basename.replace(/^_/, "").replace(/\..*$/, "");
104
+ if (basename === "index" && parts.length > 0) basename = (parts.pop() ?? basename).replace(/^_/, "").replace(/\..*$/, "");
105
+ return basename;
106
+ }
107
+ /**
108
+ * Preserve source-asset provenance for `url()` references inside imported Sass
109
+ * partials. Vite's transform hook only sees the entry stylesheet after Sass has
110
+ * flattened its imports, at which point relative URLs from a partial have lost
111
+ * the partial's source filename. Marking the partial before compilation keeps
112
+ * byte-identical assets associated with their original basenames.
113
+ */
114
+ function createSassCssUrlAssetImporter() {
115
+ const markedStylesheets = /* @__PURE__ */ new Map();
116
+ const importUrlPrefix = "vinext-css-url-asset:";
117
+ const prepareStylesheet = (filename, entryDirectory) => {
118
+ const contents = fs.readFileSync(filename, "utf8");
119
+ const marked = markCssUrlAssetReferences(rebaseCssUrlAssetReferences(contents, path.dirname(filename), entryDirectory) ?? contents, filename);
120
+ if (marked === null) return null;
121
+ const extension = path.extname(filename).toLowerCase();
122
+ return {
123
+ contents: rewriteImports(marked, filename, entryDirectory),
124
+ syntax: extension === ".sass" ? "indented" : extension === ".css" ? "css" : "scss"
125
+ };
126
+ };
127
+ const rewriteImports = (source, filename, entryDirectory = path.dirname(filename)) => source.replace(/(@(import|use|forward)\s+)(["'])(\.[^"']+)\3([^;]*;?)/g, (match, prefix, rule, quote, importUrl, suffix) => {
128
+ const importPath = path.resolve(path.dirname(filename), importUrl);
129
+ for (const candidate of sassStylesheetCandidates(importPath)) {
130
+ if (!fs.statSync(candidate, { throwIfNoEntry: false })?.isFile()) continue;
131
+ const prepared = prepareStylesheet(candidate, entryDirectory);
132
+ if (prepared === null) return match;
133
+ const canonicalUrl = pathToFileURL(candidate);
134
+ markedStylesheets.set(canonicalUrl.href, prepared);
135
+ const namespace = rule === "use" && !/\bas\s+(?:\*|[-\w]+)/.test(suffix) ? ` as ${deriveSassNamespace(importUrl)}` : "";
136
+ return `${prefix}${quote}${importUrlPrefix}${encodeURIComponent(candidate)}${quote}${namespace}${suffix}`;
137
+ }
138
+ return match;
139
+ });
140
+ return {
141
+ canonicalize(url) {
142
+ if (!url.startsWith(importUrlPrefix)) return null;
143
+ return pathToFileURL(decodeURIComponent(url.slice(21)));
144
+ },
145
+ load(canonicalUrl) {
146
+ return markedStylesheets.get(canonicalUrl.href) ?? null;
147
+ },
148
+ rewriteImports
149
+ };
150
+ }
78
151
  function buildSassPreprocessorOptions(sassOptions) {
79
152
  if (!sassOptions || typeof sassOptions !== "object") return void 0;
80
153
  const { prependData, additionalData, includePaths, loadPaths, ...rest } = sassOptions;
@@ -265,4 +338,4 @@ function createSassAwareFileSystemLoader() {
265
338
  };
266
339
  }
267
340
  //#endregion
268
- export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassTildeImporter };
341
+ export { buildSassPreprocessorOptions, createSassAwareFileSystemLoader, createSassCssUrlAssetImporter, createSassTildeImporter };
@@ -0,0 +1,16 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region src/plugins/styled-jsx.d.ts
4
+ type NextSwcModule = {
5
+ loadBindings(): Promise<unknown>;
6
+ transform(source: string, options: Record<string, unknown>): Promise<{
7
+ code: string;
8
+ map?: string;
9
+ }>;
10
+ };
11
+ type StyledJsxPluginOptions = {
12
+ importModule?: (url: string) => Promise<NextSwcModule>;
13
+ };
14
+ declare function createStyledJsxPlugin(initialProjectRoot: string, options?: StyledJsxPluginOptions): Plugin;
15
+ //#endregion
16
+ export { createStyledJsxPlugin };
@@ -0,0 +1,149 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { parseAst } from "vite";
4
+ import { pathToFileURL } from "node:url";
5
+ //#region src/plugins/styled-jsx.ts
6
+ const STYLED_JSX_IMPORT_RE = /^styled-jsx(?:\/.*)?$/;
7
+ const NODE_MODULES_RE = /[\\/]node_modules[\\/]/;
8
+ const STYLED_JSX_SOURCE_RE = /(?:<style\b|from\s+["']styled-jsx\/css["']|require\s*\(\s*["']styled-jsx\/css["']\s*\))/;
9
+ const STYLED_JSX_CSS_RE = /(?:from\s+["']styled-jsx\/css["']|require\s*\(\s*["']styled-jsx\/css["']\s*\))/;
10
+ function hasStyledJsxTag(source, id) {
11
+ const cleanId = id.split("?")[0];
12
+ const extension = path.extname(cleanId);
13
+ const lang = extension === ".ts" || extension === ".mts" || extension === ".cts" ? "ts" : "tsx";
14
+ let ast;
15
+ try {
16
+ ast = parseAst(source, { lang });
17
+ } catch {
18
+ return false;
19
+ }
20
+ const pending = [ast];
21
+ const visited = /* @__PURE__ */ new Set();
22
+ while (pending.length > 0) {
23
+ const value = pending.pop();
24
+ if (!value || typeof value !== "object" || visited.has(value)) continue;
25
+ visited.add(value);
26
+ const node = value;
27
+ if (node.type === "JSXOpeningElement") {
28
+ const name = node.name;
29
+ if (name?.type === "JSXIdentifier" && name.name === "style") {
30
+ if (node.attributes?.some((attribute) => {
31
+ if (attribute.type !== "JSXAttribute") return false;
32
+ const attributeName = attribute.name;
33
+ return attributeName?.type === "JSXIdentifier" && attributeName.name === "jsx";
34
+ })) return true;
35
+ }
36
+ }
37
+ for (const child of Object.values(node)) if (Array.isArray(child)) pending.push(...child);
38
+ else if (child && typeof child === "object") pending.push(child);
39
+ }
40
+ return false;
41
+ }
42
+ function createProjectRequire(projectRoot) {
43
+ return createRequire(path.join(projectRoot, "package.json"));
44
+ }
45
+ function resolveNextRequire(projectRoot) {
46
+ try {
47
+ return createRequire(createProjectRequire(projectRoot).resolve("next/package.json"));
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ function parserOptions(id) {
53
+ const extension = path.extname(id.split("?")[0]);
54
+ if (extension === ".ts" || extension === ".tsx") return {
55
+ syntax: "typescript",
56
+ tsx: extension === ".tsx",
57
+ decorators: true
58
+ };
59
+ return {
60
+ syntax: "ecmascript",
61
+ jsx: true
62
+ };
63
+ }
64
+ function createStyledJsxPlugin(initialProjectRoot, options = {}) {
65
+ let projectRoot = initialProjectRoot;
66
+ let development = false;
67
+ let nextRequire;
68
+ let compilerPromise = null;
69
+ const importModule = options.importModule ?? ((url) => import(url));
70
+ function getNextRequire() {
71
+ nextRequire ??= resolveNextRequire(projectRoot);
72
+ return nextRequire;
73
+ }
74
+ async function getCompiler() {
75
+ if (!compilerPromise) {
76
+ const requireFromNext = getNextRequire();
77
+ if (!requireFromNext) throw new Error("[vinext] styled-jsx requires an installed next package so vinext can use its matching compiler.");
78
+ compilerPromise = importModule(pathToFileURL(requireFromNext.resolve("next/dist/build/swc")).href).then(async (compiler) => {
79
+ await compiler.loadBindings();
80
+ return compiler;
81
+ });
82
+ }
83
+ return compilerPromise;
84
+ }
85
+ return {
86
+ name: "vinext:styled-jsx",
87
+ enforce: "pre",
88
+ configResolved(config) {
89
+ development = config.command === "serve";
90
+ if (config.root !== projectRoot) {
91
+ projectRoot = config.root;
92
+ nextRequire = void 0;
93
+ compilerPromise = null;
94
+ }
95
+ },
96
+ resolveId: {
97
+ filter: { id: STYLED_JSX_IMPORT_RE },
98
+ handler(source) {
99
+ try {
100
+ return getNextRequire()?.resolve(source) ?? null;
101
+ } catch {}
102
+ try {
103
+ return createProjectRequire(projectRoot).resolve(source);
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+ },
109
+ transform: {
110
+ filter: {
111
+ id: {
112
+ include: /\.[cm]?[jt]sx?(?:\?.*)?$/,
113
+ exclude: NODE_MODULES_RE
114
+ },
115
+ code: STYLED_JSX_SOURCE_RE
116
+ },
117
+ async handler(source, id) {
118
+ if (NODE_MODULES_RE.test(id.split("?")[0])) return null;
119
+ const hasStyledJsxCss = STYLED_JSX_CSS_RE.test(source);
120
+ const hasStyledJsxElement = !hasStyledJsxCss && hasStyledJsxTag(source, id);
121
+ if (!hasStyledJsxCss && !hasStyledJsxElement) return null;
122
+ if (!getNextRequire()) throw new Error("[vinext] styled-jsx requires an installed next package so vinext can use its matching compiler.");
123
+ const result = await (await getCompiler()).transform(source, {
124
+ filename: id.split("?")[0],
125
+ sourceMaps: true,
126
+ module: { type: "es6" },
127
+ styledJsx: { useLightningcss: false },
128
+ jsc: {
129
+ parser: parserOptions(id),
130
+ transform: {
131
+ react: {
132
+ runtime: "automatic",
133
+ development,
134
+ useBuiltins: true
135
+ },
136
+ optimizer: { simplify: false }
137
+ }
138
+ }
139
+ });
140
+ return {
141
+ code: result.code,
142
+ map: result.map ?? null
143
+ };
144
+ }
145
+ }
146
+ };
147
+ }
148
+ //#endregion
149
+ export { createStyledJsxPlugin };
@@ -1,5 +1,5 @@
1
- import { decodeRouteSegment, isInvisibleSegment, sortRoutes } from "./utils.js";
2
1
  import { normalizePathSeparators } from "../utils/path.js";
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";
@@ -440,9 +440,9 @@ function validatePageRouteConflicts(routes, appDir) {
440
440
  }
441
441
  }
442
442
  function formatAppFilePath(filePath, appDir) {
443
- const relativePath = path.relative(appDir, filePath).replace(/\\/g, "/");
443
+ const relativePath = normalizePathSeparators(path.relative(appDir, filePath));
444
444
  const parsedPath = path.parse(relativePath);
445
- const withoutExtension = path.join(parsedPath.dir, parsedPath.name).replace(/\\/g, "/");
445
+ const withoutExtension = normalizePathSeparators(path.join(parsedPath.dir, parsedPath.name));
446
446
  return withoutExtension.startsWith("/") ? withoutExtension : `/${withoutExtension}`;
447
447
  }
448
448
  /**
@@ -1180,9 +1180,7 @@ function findSlotRootPage(slotDir, matcher) {
1180
1180
  * default component, intercepting route, or nested page-backed sub-route.
1181
1181
  *
1182
1182
  * `dir` and `appDir` must be forward-slash. The slot directory is built from
1183
- * `dir` with `path.posix.join`, and the owner segments and slot key come from
1184
- * `path.posix.relative(appDir, …)`, which only yields a forward-slash relative
1185
- * path when both operands already are.
1183
+ * `dir` with `path.posix.join`.
1186
1184
  */
1187
1185
  function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = false) {
1188
1186
  const entries = readDirEntriesCached(dir, matcher);
@@ -1197,12 +1195,12 @@ function discoverParallelSlots(dir, appDir, matcher, includeNestedOnlySlots = fa
1197
1195
  const interceptingRoutes = discoverInterceptingRoutes(slotDir, dir, appDir, matcher);
1198
1196
  const hasNestedPages = includeNestedOnlySlots && findSlotSubPages(slotDir, matcher).length > 0;
1199
1197
  if (!pagePath && !defaultPath && interceptingRoutes.length === 0 && !hasNestedPages) continue;
1200
- const ownerSegments = path.posix.relative(appDir, dir).split("/").filter((segment) => segment.length > 0);
1198
+ const ownerSegments = path.relative(appDir, dir).split(path.sep).filter((segment) => segment.length > 0);
1201
1199
  const ownerTreePath = createAppRouteGraphTreePath(ownerSegments, ownerSegments.length);
1202
1200
  const configLayoutPaths = findSlotConfigLayoutPaths(slotDir, pagePath, matcher);
1203
1201
  slots.push({
1204
1202
  id: createAppRouteGraphSlotId(slotName, ownerTreePath),
1205
- key: `${slotName}@${path.posix.relative(appDir, slotDir)}`,
1203
+ key: `${slotName}@${normalizePathSeparators(path.relative(appDir, slotDir))}`,
1206
1204
  name: slotName,
1207
1205
  ownerDir: slotDir,
1208
1206
  ownerTreePath,
@@ -1262,8 +1260,7 @@ function isInterceptionMarkerDir(name) {
1262
1260
  * They intercept navigation to another route and render within the slot instead.
1263
1261
  *
1264
1262
  * `slotDir`, `routeDir`, and `appDir` must be forward-slash. They are passed
1265
- * down to `path.posix.join` and `path.posix.relative` when building the
1266
- * intercept page paths and target patterns.
1263
+ * down to `path.posix.join` when building the intercept page paths.
1267
1264
  *
1268
1265
  * @param slotDir - The parallel slot directory (e.g. app/feed/@modal)
1269
1266
  * @param routeDir - The directory of the route that owns this slot (e.g. app/feed)
@@ -1283,13 +1280,17 @@ function discoverInterceptingRoutes(slotDir, routeDir, appDir, matcher) {
1283
1280
  * Sibling intercepts use the same conventions and target-computation logic as
1284
1281
  * slot intercepts, but their intercepting page replaces the full page response
1285
1282
  * (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.*`.
1286
1287
  */
1287
1288
  function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1288
1289
  const routesByDir = /* @__PURE__ */ new Map();
1289
1290
  for (const route of routes) {
1290
1291
  const filePath = route.pagePath ?? route.routePath;
1291
1292
  if (!filePath) continue;
1292
- const routeDir = normalizePathSeparators(path.dirname(filePath));
1293
+ const routeDir = path.posix.dirname(filePath);
1293
1294
  if (!routesByDir.has(routeDir)) routesByDir.set(routeDir, route);
1294
1295
  }
1295
1296
  function walk(dir) {
@@ -1303,7 +1304,7 @@ function discoverSiblingInterceptingRoutes(routes, appDir, matcher) {
1303
1304
  if (!entry.isDirectory()) continue;
1304
1305
  if (entry.name.startsWith("_")) continue;
1305
1306
  if (entry.name.startsWith("@")) continue;
1306
- const childDir = path.join(dir, entry.name);
1307
+ const childDir = path.posix.join(dir, entry.name);
1307
1308
  const marker = matchInterceptConvention(entry.name);
1308
1309
  if (marker) {
1309
1310
  const restOfName = entry.name.slice(marker.prefix.length);
@@ -1370,8 +1371,7 @@ function findOwnerRouteForDir(dir, appDir, routes, routesByDir) {
1370
1371
  * intercepting route directories.
1371
1372
  *
1372
1373
  * `currentDir`, `routeDir`, and `appDir` must be forward-slash. `currentDir`
1373
- * descends with `path.posix.join` and all three reach the `path.posix.join` /
1374
- * `path.posix.relative` calls that build the intercept page paths and patterns.
1374
+ * descends with `path.posix.join` when building the intercept page paths.
1375
1375
  */
1376
1376
  function scanForInterceptingPages(currentDir, routeDir, appDir, results, matcher) {
1377
1377
  if (!fs.existsSync(currentDir)) return;
@@ -1397,6 +1397,12 @@ function matchInterceptConvention(name) {
1397
1397
  /**
1398
1398
  * Collect page.tsx files inside an intercepting route directory tree
1399
1399
  * 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.
1400
1406
  */
1401
1407
  function collectInterceptingPages(currentDir, interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, parentLayoutPaths = []) {
1402
1408
  const currentLayoutPath = findFile(currentDir, "layout", matcher);
@@ -1407,7 +1413,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1407
1413
  if (targetPattern) {
1408
1414
  const sourceMatchPattern = computeInterceptSourceMatchPattern(interceptParentDir, appDir);
1409
1415
  results.push({
1410
- branchSegments: [interceptSegment, ...normalizePathSeparators(path.relative(interceptRoot, path.dirname(page))).split("/").filter(Boolean)],
1416
+ branchSegments: [interceptSegment, ...path.relative(interceptRoot, path.dirname(page)).split(path.sep).filter(Boolean)],
1411
1417
  convention,
1412
1418
  layoutPaths: [...layoutPaths],
1413
1419
  layoutSegments: layoutPaths.map((layoutPath) => {
@@ -1426,7 +1432,7 @@ function collectInterceptingPages(currentDir, interceptRoot, convention, interce
1426
1432
  for (const entry of entries) {
1427
1433
  if (!entry.isDirectory()) continue;
1428
1434
  if (entry.name.startsWith("_")) continue;
1429
- collectInterceptingPages(path.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1435
+ collectInterceptingPages(path.posix.join(currentDir, entry.name), interceptRoot, convention, interceptSegment, routeDir, appDir, interceptParentDir, results, matcher, layoutPaths);
1430
1436
  }
1431
1437
  }
1432
1438
  /**
@@ -39,11 +39,18 @@ declare function findFileWithExts(dir: string, name: string, matcher: ValidFileM
39
39
  * 1. User-configured pageExtensions go first (each prefixed with `.`) so
40
40
  * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
41
41
  * 2. Vite's defaults follow, with duplicates removed.
42
+ * 3. `.cjs`/`.cts` go last (lowest priority). Neither Vite's defaults nor the
43
+ * user's pageExtensions include them, but `vinext init` renames CJS config
44
+ * files (e.g. `tailwind.config.js` → `tailwind.config.cjs`) when it adds
45
+ * `"type": "module"`, and app code imports those extensionlessly
46
+ * (`import cfg from "../tailwind.config"`). Without these, the bundle fails
47
+ * with "[UNRESOLVED_IMPORT] Could not resolve '../tailwind.config'".
42
48
  *
43
49
  * The user's pageExtensions retain their relative order, which is what
44
50
  * Next.js / Turbopack do via the `resolveExtensions` config option.
45
51
  *
46
- * See: cloudflare/vinext#1502
52
+ * See: cloudflare/vinext#1502 for page-extension ordering, and
53
+ * cloudflare/vinext#2435 for extensionless `.cjs` config imports.
47
54
  */
48
55
  declare function buildViteResolveExtensions(pageExtensions?: readonly string[] | null, viteDefaults?: readonly string[]): string[];
49
56
  /**
@@ -1,5 +1,5 @@
1
- import { escapeRegExp } from "../utils/regex.js";
2
1
  import { normalizePathSeparators } from "../utils/path.js";
2
+ import { escapeRegExp } from "../utils/regex.js";
3
3
  import { existsSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import { glob } from "node:fs/promises";
@@ -91,11 +91,18 @@ function findFileWithExts(dir, name, matcher) {
91
91
  * 1. User-configured pageExtensions go first (each prefixed with `.`) so
92
92
  * the user's priority wins. e.g. `.platform.tsx` resolves before `.tsx`.
93
93
  * 2. Vite's defaults follow, with duplicates removed.
94
+ * 3. `.cjs`/`.cts` go last (lowest priority). Neither Vite's defaults nor the
95
+ * user's pageExtensions include them, but `vinext init` renames CJS config
96
+ * files (e.g. `tailwind.config.js` → `tailwind.config.cjs`) when it adds
97
+ * `"type": "module"`, and app code imports those extensionlessly
98
+ * (`import cfg from "../tailwind.config"`). Without these, the bundle fails
99
+ * with "[UNRESOLVED_IMPORT] Could not resolve '../tailwind.config'".
94
100
  *
95
101
  * The user's pageExtensions retain their relative order, which is what
96
102
  * Next.js / Turbopack do via the `resolveExtensions` config option.
97
103
  *
98
- * See: cloudflare/vinext#1502
104
+ * See: cloudflare/vinext#1502 for page-extension ordering, and
105
+ * cloudflare/vinext#2435 for extensionless `.cjs` config imports.
99
106
  */
100
107
  function buildViteResolveExtensions(pageExtensions, viteDefaults = [
101
108
  ".mjs",
@@ -109,7 +116,12 @@ function buildViteResolveExtensions(pageExtensions, viteDefaults = [
109
116
  const dotted = normalizePageExtensions(pageExtensions).map((ext) => `.${ext}`);
110
117
  const seen = /* @__PURE__ */ new Set();
111
118
  const result = [];
112
- for (const ext of [...dotted, ...viteDefaults]) {
119
+ for (const ext of [
120
+ ...dotted,
121
+ ...viteDefaults,
122
+ ".cjs",
123
+ ".cts"
124
+ ]) {
113
125
  if (seen.has(ext)) continue;
114
126
  seen.add(ext);
115
127
  result.push(ext);
@@ -1,6 +1,5 @@
1
1
  import { VINEXT_CLIENT_REUSE_MANIFEST_HEADER, VINEXT_PARAMS_HEADER, VINEXT_RSC_REDIRECT_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";
@@ -13,7 +12,7 @@ import { DevRecoveryBoundary, GlobalErrorBoundary, RedirectBoundary } from "../s
13
12
  import { beginAppRouterScrollIntent, consumeAppRouterScrollIntent } from "../shims/app-router-scroll-state.js";
14
13
  import { AppRouterScrollCommitProvider } from "../shims/app-router-scroll.js";
15
14
  import { BfcacheStateKeyMapContext, ElementsContext, Slot } from "../shims/slot.js";
16
- import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, getVinextRscCompatibilityId } from "./app-rsc-cache-busting.js";
15
+ import { VINEXT_RSC_COMPATIBILITY_ID_HEADER, VINEXT_RSC_CONTENT_TYPE, createRscRequestHeaders, createRscRequestUrl, getVinextRscCompatibilityId, stripRscCacheBustingSearchParam } from "./app-rsc-cache-busting.js";
17
16
  import { getNavigationRuntime, registerNavigationRuntimeBootstrap, registerNavigationRuntimeFunctions } from "../client/navigation-runtime.js";
18
17
  import { notifyAppRouterTransitionStart } from "../client/instrumentation-client-state.js";
19
18
  import "../client/instrumentation-client.js";
@@ -25,7 +24,8 @@ import { readHistoryStatePreviousNextUrl } from "./app-history-state.js";
25
24
  import { AppBrowserMpaNavigationScheduler } from "./app-browser-mpa-navigation.js";
26
25
  import { blockDangerousStreamedRscRedirect } from "./app-browser-rsc-redirect.js";
27
26
  import { navigationPlanner } from "./navigation-planner.js";
28
- import { DYNAMIC_NAVIGATION_CACHE_TTL, PREFETCH_CACHE_TTL, __basePath, appRouterInstance, commitClientNavigationState, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, deletePrefetchResponseSnapshot, getClientNavigationRenderContext, getPrefetchCache, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, seedPrefetchResponseSnapshot, setClientParams, setMountedSlotsHeader, setPendingPathname, useRouter } from "../shims/navigation.js";
27
+ import { resolveHybridClientRewriteHref } from "../shims/internal/hybrid-client-route-owner.js";
28
+ import { DYNAMIC_NAVIGATION_CACHE_TTL, PREFETCH_CACHE_TTL, __basePath, appRouterInstance, commitClientNavigationState, consumePrefetchResponseForNavigation, createCachedRscResponseSnapshot, createClientNavigationRenderSnapshot, createSnapshotPathAndSearch, deletePrefetchResponseSnapshot, getClientNavigationRenderContext, getMountedSlotsHeader, getPrefetchCache, hasPrefetchCacheEntryForNavigation, invalidatePrefetchCache, pushHistoryStateWithoutNotify, replaceClientParamsWithoutNotify, replaceHistoryStateWithoutNotify, resolvePrefetchCacheEntryMountedSlotsHeader, restoreRscResponse, saveScrollPosition, seedPrefetchResponseSnapshot, setClientParams, setMountedSlotsHeader, setPendingPathname, useRouter } from "../shims/navigation.js";
29
29
  import { chunksToReadableStream, createProgressiveRscStream, getVinextBrowserGlobal } from "./app-browser-stream.js";
30
30
  import { COMMITTED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, FRESH_APP_NAVIGATION_PAYLOAD_ORIGIN, VISITED_CACHE_APP_NAVIGATION_PAYLOAD_ORIGIN, isCacheRestorableAppPayloadMetadata, isCompleteAppPayloadMetadata, resolveInterceptionContextFromPreviousNextUrl } from "./app-browser-state.js";
31
31
  import { clearHardNavigationLoopGuard, createAppBrowserNavigationController, createBasePathStrippedPathAndSearch } from "./app-browser-navigation-controller.js";
@@ -193,6 +193,21 @@ function clearClientNavigationCaches() {
193
193
  clearPrefetchState();
194
194
  historyController.invalidateRestorableClientState();
195
195
  }
196
+ function normalizeBrowserRscUrlForReuse(url) {
197
+ if (!url) return null;
198
+ try {
199
+ const parsed = new URL(url, window.location.origin);
200
+ stripRscCacheBustingSearchParam(parsed);
201
+ return `${parsed.pathname}${parsed.search}`;
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ function isAlternatePrefetchResponseUrl(responseUrl, additionalRscUrls) {
207
+ const normalizedResponseUrl = normalizeBrowserRscUrlForReuse(responseUrl);
208
+ if (normalizedResponseUrl === null) return false;
209
+ return additionalRscUrls.some((additionalRscUrl) => normalizeBrowserRscUrlForReuse(additionalRscUrl) === normalizedResponseUrl);
210
+ }
196
211
  function isSettledPrefetchCacheEntry(entry) {
197
212
  return entry.outcome === "cache-seeded" && entry.pending === void 0 && entry.snapshot !== void 0;
198
213
  }
@@ -242,6 +257,7 @@ async function learnOptimisticRouteTemplatesFromPrefetchCache(options) {
242
257
  if (optimisticRouteTemplateSources.has(sourceKey)) continue;
243
258
  if (optimisticRouteTemplateLearning.has(sourceKey)) continue;
244
259
  if (!isSettledPrefetchCacheEntry(entry)) continue;
260
+ if (entry.prefetchKind === "route-tree") continue;
245
261
  const promise = learnOptimisticRouteTemplateFromPrefetch({
246
262
  cacheKey,
247
263
  entry,
@@ -545,9 +561,17 @@ function BrowserRoot({ hydrationCachePublication, initialElements, initialNaviga
545
561
  setWindowNextInternalSourcePage(AppElementsWire.readMetadata(treeState.elements).sourcePage);
546
562
  }, [treeState.elements]);
547
563
  useLayoutEffect(() => {
548
- setMountedSlotsHeader(getMountedSlotIdsHeader(stateRef.current.elements));
564
+ const previousMountedSlotsHeader = getMountedSlotsHeader();
565
+ const nextMountedSlotsHeader = getMountedSlotIdsHeader(stateRef.current.elements);
566
+ setMountedSlotsHeader(nextMountedSlotsHeader);
549
567
  removeStylesheetLinksCoveredByInlineCss();
550
- getNavigationRuntime()?.functions.pingVisibleLinks?.();
568
+ if (previousMountedSlotsHeader === nextMountedSlotsHeader) return;
569
+ const pingTimer = window.setTimeout(() => {
570
+ getNavigationRuntime()?.functions.pingVisibleLinks?.();
571
+ }, 0);
572
+ return () => {
573
+ window.clearTimeout(pingTimer);
574
+ };
551
575
  }, [treeState.elements]);
552
576
  useLayoutEffect(() => {
553
577
  if (treeState.renderId !== 0) return;
@@ -748,14 +772,14 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
748
772
  ...initialRscBootstrap?.dynamicStaleTimeSeconds !== void 0 ? { dynamicStaleTimeSeconds: initialRscBootstrap.dynamicStaleTimeSeconds } : {},
749
773
  mountedSlotsHeader,
750
774
  paramsHeader: encodeURIComponent(JSON.stringify(initialParams)),
775
+ renderedPathAndSearch: null,
751
776
  url: rscUrl
752
777
  };
753
778
  const fallbackTtlMs = initialRscBootstrap?.initialCacheKind === "static" ? PREFETCH_CACHE_TTL : DYNAMIC_NAVIGATION_CACHE_TTL;
754
779
  hydrationCachePublication.publish(() => {
755
780
  if (cacheGeneration !== clientNavigationCacheGeneration) return () => {};
756
781
  if (isCacheRestorableAppPayloadMetadata(metadata)) return storeVisitedResponseSnapshot(rscUrl, metadata.interceptionContext, snapshot, initialParams, fallbackTtlMs, mountedSlotsHeader);
757
- seedPrefetchResponseSnapshot(rscUrl, snapshot, metadata.interceptionContext, mountedSlotsHeader, fallbackTtlMs);
758
- return () => deletePrefetchResponseSnapshot(rscUrl, snapshot, metadata.interceptionContext);
782
+ return storeVisitedResponseSnapshot(rscUrl, metadata.interceptionContext, snapshot, initialParams, fallbackTtlMs, mountedSlotsHeader, elements);
759
783
  });
760
784
  }).catch(() => {});
761
785
  historyController.writeBootstrapHistoryMetadata();
@@ -863,10 +887,11 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
863
887
  const shouldBypassNavigationCache = earlyIntentDecision?.kind === "flightNavigation" && earlyIntentDecision.bypassNavigationCache;
864
888
  const requestHeaders = createRscRequestHeaders({
865
889
  interceptionContext: requestInterceptionContext,
866
- mountedSlotsHeader,
867
- renderMode: navigationKind === "refresh" ? APP_RSC_RENDER_MODE_REFRESH_PRESERVE_UI : void 0
890
+ mountedSlotsHeader
868
891
  });
869
892
  const rscUrl = await createRscRequestUrl(url.pathname + url.search, requestHeaders);
893
+ const rewrittenNavigationHref = navigationKind === "navigate" ? resolveHybridClientRewriteHref(currentHref, __basePath) : null;
894
+ const additionalPrefetchRscUrls = rewrittenNavigationHref && rewrittenNavigationHref !== currentHref ? [await createRscRequestUrl(rewrittenNavigationHref, requestHeaders)] : [];
870
895
  const visitedResponseCandidate = shouldBypassNavigationCache ? {
871
896
  cacheKey: AppElementsWire.encodeCacheKey(rscUrl, requestInterceptionContext),
872
897
  entry: null,
@@ -883,7 +908,10 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
883
908
  visitedResponse
884
909
  });
885
910
  let routeManifest = navigationKind === "navigate" ? getBrowserRouteManifest() : null;
886
- const hasPrefetchCandidate = prefetchProbeDecision.kind === "probe" && hasPrefetchCacheEntryForNavigation(rscUrl, requestInterceptionContext, mountedSlotsHeader, { notifyInvalidation: false });
911
+ const hasPrefetchCandidate = prefetchProbeDecision.kind === "probe" && hasPrefetchCacheEntryForNavigation(rscUrl, requestInterceptionContext, mountedSlotsHeader, {
912
+ additionalRscUrls: additionalPrefetchRscUrls,
913
+ notifyInvalidation: false
914
+ });
887
915
  const reuseDecision = navigationPlanner.classifyNavigationReuse({
888
916
  bypassNavigationCache: shouldBypassNavigationCache,
889
917
  navigationKind,
@@ -941,12 +969,15 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
941
969
  let navResponseUrl = null;
942
970
  let fallbackReuseDecision = reuseDecision;
943
971
  if (reuseDecision.kind === "consumePrefetch") {
944
- const prefetchedResponse = await consumePrefetchResponseForNavigation(rscUrl, requestInterceptionContext, mountedSlotsHeader, { shouldConsume: () => browserNavigationController.isCurrentNavigation(navId) });
972
+ const prefetchedResponse = await consumePrefetchResponseForNavigation(rscUrl, requestInterceptionContext, mountedSlotsHeader, {
973
+ additionalRscUrls: additionalPrefetchRscUrls,
974
+ shouldConsume: () => browserNavigationController.isCurrentNavigation(navId)
975
+ });
945
976
  if (!browserNavigationController.isCurrentNavigation(navId)) return;
946
977
  if (prefetchedResponse) {
947
978
  navResponse = restoreRscResponse(prefetchedResponse, false);
948
979
  navResponseExpiresAt = prefetchedResponse.expiresAt;
949
- navResponseUrl = prefetchedResponse.url;
980
+ navResponseUrl = isAlternatePrefetchResponseUrl(prefetchedResponse.url, additionalPrefetchRscUrls) ? rscUrl : prefetchedResponse.url;
950
981
  }
951
982
  if (!navResponse) {
952
983
  routeManifest = navigationKind === "navigate" ? getBrowserRouteManifest() : null;
@@ -1053,7 +1084,6 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1053
1084
  committedState = state;
1054
1085
  if (activeNavigationAbortController === navigationAbortController) activeNavigationAbortController = null;
1055
1086
  }, detachedNavigationCommits ? "authoritative" : void 0) !== "committed") return;
1056
- if (!browserNavigationController.isCurrentNavigation(navId)) return;
1057
1087
  try {
1058
1088
  const renderedElements = await rscPayload;
1059
1089
  if (navigationCacheGeneration !== clientNavigationCacheGeneration) return;
@@ -1074,7 +1104,7 @@ function bootstrapHydration(rscStream, devErrorOverlay, initialRscBootstrap) {
1074
1104
  if (isCacheRestorableAppPayloadMetadata(metadata)) {
1075
1105
  if (navigationCacheGeneration !== clientNavigationCacheGeneration) return;
1076
1106
  storeVisitedResponseSnapshot(rscUrl, interceptionContext, snapshot, navParams, PREFETCH_CACHE_TTL, mountedSlotsHeader);
1077
- } else if (committedState !== null && getMountedSlotIdsHeader(committedState.elements) === null) {
1107
+ } else if (committedState !== null) {
1078
1108
  const state = committedState;
1079
1109
  const committedElements = {
1080
1110
  ...state.elements,
@@ -449,8 +449,11 @@ function createAppBrowserNavigationController(deps = {}) {
449
449
  return;
450
450
  }
451
451
  if (latestApproval.approvedCommit) {
452
- dispatchSynchronousVisibleCommit(latestApproval.approvedCommit);
453
- syncHistoryStatePreviousNextUrl(latestApproval.approvedCommit.previousNextUrl, latestApproval.approvedCommit.action.bfcacheIds);
452
+ const approvedRevalidationCommit = latestApproval.approvedCommit;
453
+ startTransition(() => {
454
+ dispatchSynchronousVisibleCommit(approvedRevalidationCommit);
455
+ });
456
+ syncHistoryStatePreviousNextUrl(approvedRevalidationCommit.previousNextUrl, approvedRevalidationCommit.action.bfcacheIds);
454
457
  } else notifyDiscardedServerActionRevalidation(lifecycleOptions);
455
458
  } else if (decision.disposition === "no-commit") notifyDiscardedServerActionRevalidation(lifecycleOptions);
456
459
  if (returnValue) {
@@ -1,3 +1,4 @@
1
+ import { hasBasePath } from "../utils/base-path.js";
1
2
  import { ACTION_REDIRECT_HEADER, ACTION_REDIRECT_STATUS_HEADER } from "./headers.js";
2
3
  import { DANGEROUS_URL_BLOCK_MESSAGE, isDangerousScheme } from "../shims/url-safety.js";
3
4
  import { AppElementsWire } from "./app-elements-wire.js";
@@ -9,7 +10,7 @@ import { applyServerActionResultDecision } from "./app-browser-server-action-nav
9
10
  import { throwOnServerActionNotFound } from "./server-action-not-found.js";
10
11
  import { createFromFetch, createTemporaryReferenceSet, encodeReply } from "@vitejs/plugin-rsc/browser";
11
12
  //#region src/server/app-browser-server-action-client.ts
12
- function resolveActionRedirectTarget(response, performHardNavigation) {
13
+ function resolveActionRedirectTarget(response, basePath, performHardNavigation) {
13
14
  const actionRedirect = response.headers.get(ACTION_REDIRECT_HEADER);
14
15
  if (!actionRedirect) return null;
15
16
  if (isDangerousScheme(actionRedirect)) {
@@ -25,7 +26,7 @@ function resolveActionRedirectTarget(response, performHardNavigation) {
25
26
  if (!baseDir.endsWith("/")) baseDir += "/";
26
27
  redirectUrl = new URL(actionRedirect, `${baseParsed.origin}${baseDir}${baseParsed.search}`);
27
28
  }
28
- if (redirectUrl.origin !== window.location.origin) {
29
+ if (redirectUrl.origin !== window.location.origin || basePath !== "" && !hasBasePath(redirectUrl.pathname, basePath)) {
29
30
  performHardNavigation(actionRedirect);
30
31
  return null;
31
32
  }
@@ -60,6 +61,7 @@ async function invokeClientServerAction(id, args, actionInitiation, deps) {
60
61
  actionId: id,
61
62
  basePath: deps.basePath,
62
63
  elements: actionInitiation.routerState.elements,
64
+ interceptionContext: actionInitiation.routerState.interception !== null ? actionInitiation.routerState.interceptionContext : null,
63
65
  previousNextUrl: actionInitiation.routerState.previousNextUrl
64
66
  }).headers;
65
67
  const fetchResponse = await fetch(createServerActionRequestUrl(actionInitiation.path), {
@@ -69,7 +71,7 @@ async function invokeClientServerAction(id, args, actionInitiation, deps) {
69
71
  });
70
72
  throwOnServerActionNotFound(fetchResponse, id);
71
73
  const hasActionRedirect = fetchResponse.headers.has(ACTION_REDIRECT_HEADER);
72
- const actionRedirectTarget = resolveActionRedirectTarget(fetchResponse, (url, historyMode) => deps.performHardNavigation(url, historyMode));
74
+ const actionRedirectTarget = resolveActionRedirectTarget(fetchResponse, deps.basePath, (url, historyMode) => deps.performHardNavigation(url, historyMode));
73
75
  if (hasActionRedirect && !actionRedirectTarget) return void 0;
74
76
  const actionResultFacts = createServerActionResultFacts({
75
77
  actionRedirectHref: actionRedirectTarget?.href ?? null,
@@ -107,6 +107,7 @@ type ResolveServerActionRequestStateOptions = {
107
107
  actionId: string;
108
108
  basePath: string;
109
109
  elements: AppElements;
110
+ interceptionContext?: string | null;
110
111
  previousNextUrl: string | null;
111
112
  };
112
113
  type ResolveServerActionRequestStateResult = {