@react-router/dev 8.4.0 → 8.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## v8.5.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix critical CSS collection in dev registering server-only route imports as client dependencies, which triggered dependency re-optimization and full page reloads ([#15595](https://github.com/remix-run/react-router/pull/15595))
8
+ - Fix intermittent `Prerender: Request failed for ... connect ECONNREFUSED` errors during prerendering ([#15325](https://github.com/remix-run/react-router/pull/15325))
9
+ - Send requests to the address the Vite preview server bound to
10
+ - Avoid re-resolving `localhost` for each request, which could select a different address family (`127.0.0.1` vs `::1`)
11
+
12
+ ### Unstable Changes
13
+
14
+ ⚠️ _[Unstable features](https://reactrouter.com/community/api-development-strategy#unstable-flags) are not recommended for production use_
15
+
16
+ - Add `ssr: "unstable_api-only"` to retain a runtime server for loaders, actions, and route discovery while disabling production document rendering ([#15601](https://github.com/remix-run/react-router/pull/15601))
17
+ - Generate a root-only SPA shell when prerendering is disabled
18
+ - Support prerendered pages and data files alongside runtime loaders and actions, with a SPA fallback for non-prerendered paths
19
+ - Default API-only builds to lazy route discovery
20
+ - Reject API-only configuration in RSC Framework Mode
21
+ - Preserve server-only route exports in additional Vite server environments in both API-only and SSR modes
22
+ - Serve prerendered pages and the SPA fallback in Vite preview while retaining live data requests and route discovery
23
+ - Add the `future.unstable_customRouteDiscovery` flag to opt into application-controlled route discovery in conventional Framework Mode ([#15602](https://github.com/remix-run/react-router/pull/15602))
24
+ - Fix source maps for route modules in unstable RSC Framework Mode ([#15479](https://github.com/remix-run/react-router/pull/15479))
25
+ - Add `unstable_apiServerOrigin` to configure the origin for API-only loader, action, and route discovery requests ([#15601](https://github.com/remix-run/react-router/pull/15601))
26
+ - Updated dependencies:
27
+ - [`react-router@8.5.0`](https://github.com/remix-run/react-router/releases/tag/react-router@8.5.0)
28
+ - [`@react-router/node@8.5.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/node@8.5.0)
29
+ - [`@react-router/serve@8.5.0`](https://github.com/remix-run/react-router/releases/tag/@react-router/serve@8.5.0)
30
+
3
31
  ## v8.4.0
4
32
 
5
33
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { _ as getVite, v as preloadVite } from "./typegen-WywajSVn.js";
11
+ import { _ as getVite, v as preloadVite } from "./typegen-CiHN93zF.js";
12
12
  //#region vite/build.ts
13
13
  async function build(root, viteBuildOptions) {
14
14
  await preloadVite();
package/dist/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @react-router/dev v8.4.0
3
+ * @react-router/dev v8.5.0
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * @license MIT
11
11
  */
12
- import { _ as getVite, l as hasNodeDependency, n as watch, t as run$1, u as loadConfig, v as preloadVite } from "../typegen-WywajSVn.js";
12
+ import { _ as getVite, l as hasNodeDependency, n as watch, t as run$1, u as loadConfig, v as preloadVite } from "../typegen-CiHN93zF.js";
13
13
  import { createRequire } from "node:module";
14
14
  import { parseArgs } from "node:util";
15
15
  import semver from "semver";
@@ -172,7 +172,7 @@ async function routes(rootDirectory, flags = {}) {
172
172
  }
173
173
  async function build(root, options = {}) {
174
174
  root = resolveRootDirectory(root, options);
175
- let { build } = await import("../build-CsJF5XUO.js");
175
+ let { build } = await import("../build-ZbZgdE7Y.js");
176
176
  if (options.profile) await start();
177
177
  try {
178
178
  await build(root, options);
@@ -182,7 +182,7 @@ async function build(root, options = {}) {
182
182
  }
183
183
  async function dev(root, options = {}) {
184
184
  if (developmentConditionEnabled) {
185
- let { dev } = await import("../dev-_GaivbeG.js");
185
+ let { dev } = await import("../dev-DGFm0uWr.js");
186
186
  if (options.profile) await start();
187
187
  exitHook(() => stop(console.info));
188
188
  root = resolveRootDirectory(root, options);
@@ -38,6 +38,7 @@ type ServerBundlesBuildManifest = BaseBuildManifest & {
38
38
  };
39
39
  type ServerModuleFormat = "esm" | "cjs";
40
40
  interface FutureConfig {
41
+ unstable_customRouteDiscovery: boolean;
41
42
  unstable_enableNodeReadableStream: boolean;
42
43
  unstable_optimizeDeps: boolean;
43
44
  }
@@ -140,12 +141,30 @@ type ReactRouterConfig = {
140
141
  */
141
142
  serverBundles?: ServerBundlesFunction;
142
143
  /**
143
- * Enable server-side rendering for your application. Disable to use "SPA
144
- * Mode", which will request the `/` path at build-time and save it as an
145
- * `index.html` file with your assets so your application can be deployed as a
146
- * SPA without server-rendering. Default's to `true`.
144
+ * Control runtime server-side document rendering. Defaults to `true`.
145
+ *
146
+ * Set `false` to generate a static client build without a runtime server.
147
+ * Without prerendering, this generates a root-only SPA shell. With
148
+ * prerendering, it generates HTML and data files for selected paths plus
149
+ * a SPA fallback for non-prerendered paths.
150
+ *
151
+ * Set `"unstable_api-only"` to retain a runtime server for loaders, actions,
152
+ * and route discovery while disabling production document rendering.
153
+ * Documents follow the same prerendering and SPA fallback behavior as
154
+ * `ssr:false`, with runtime loaders and actions available for all routes.
155
+ * API-only mode is unstable and is not supported in RSC Framework Mode.
156
+ */
157
+ ssr?: boolean | "unstable_api-only";
158
+ /**
159
+ * The origin to use for server requests when `ssr` is `"unstable_api-only"`.
160
+ * This is ignored when `ssr` is `true` or `false`. Defaults to the same
161
+ * origin that serves the client build. Applies to loader, action, and route
162
+ * discovery requests.
163
+ *
164
+ * **Note:** The server origin will need to be configured appropriately for
165
+ * Cross-Origin Request Sharing (CORS).
147
166
  */
148
- ssr?: boolean;
167
+ unstable_apiServerOrigin?: string;
149
168
  /**
150
169
  * Enable subresource integrity hashes on asset script tags. Defaults to
151
170
  * `false`.
@@ -256,12 +275,24 @@ type ResolvedReactRouterConfig = Readonly<{
256
275
  */
257
276
  serverModuleFormat: ServerModuleFormat;
258
277
  /**
259
- * Enable server-side rendering for your application. Disable to use "SPA
260
- * Mode", which will request the `/` path at build-time and save it as an
261
- * `index.html` file with your assets so your application can be deployed as a
262
- * SPA without server-rendering. Default's to `true`.
278
+ * Control runtime server-side document rendering. Defaults to `true`.
279
+ *
280
+ * Set `false` to generate a static client build without a runtime server.
281
+ * Without prerendering, this generates a root-only SPA shell. With
282
+ * prerendering, it generates HTML and data files for selected paths plus
283
+ * a SPA fallback for non-prerendered paths.
284
+ *
285
+ * Set `"unstable_api-only"` to retain a runtime server for loaders, actions,
286
+ * and route discovery while disabling production document rendering.
287
+ * Documents follow the same prerendering and SPA fallback behavior as
288
+ * `ssr:false`, with runtime loaders and actions available for all routes.
289
+ * API-only mode is unstable and is not supported in RSC Framework Mode.
290
+ */
291
+ ssr: boolean | "unstable_api-only";
292
+ /**
293
+ * The origin to use for server requests in API-only mode.
263
294
  */
264
- ssr: boolean;
295
+ unstable_apiServerOrigin?: string;
265
296
  /**
266
297
  * Whether to generate subresource integrity hashes for asset script tags.
267
298
  */
package/dist/config.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
 
2
- import { i as ServerBundlesFunction, n as Preset, r as ReactRouterConfig, t as BuildManifest } from "./config-t89niiFv.js";
2
+ import { i as ServerBundlesFunction, n as Preset, r as ReactRouterConfig, t as BuildManifest } from "./config-DP95WFpo.js";
3
3
  export type { BuildManifest, ReactRouterConfig as Config, Preset, ServerBundlesFunction };
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { _ as getVite, v as preloadVite } from "./typegen-WywajSVn.js";
11
+ import { _ as getVite, v as preloadVite } from "./typegen-CiHN93zF.js";
12
12
  import { n as start, r as stop, t as getSession } from "./cli/index.js";
13
13
  import colors from "picocolors";
14
14
  //#region vite/dev.ts
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
package/dist/routes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { a as prefix, i as layout, n as getAppDirectory, o as relative, r as index, s as route } from "./routes-BFdkiVnp.js";
11
+ import { a as prefix, i as layout, n as getAppDirectory, o as relative, r as index, s as route } from "./routes-Cyup1Gnm.js";
12
12
  export { getAppDirectory, index, layout, prefix, relative, route };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { c as setAppDirectory, l as validateRouteConfig, t as configRoutesToRouteManifest, u as invariant } from "./routes-BFdkiVnp.js";
11
+ import { c as setAppDirectory, l as validateRouteConfig, t as configRoutesToRouteManifest, u as invariant } from "./routes-Cyup1Gnm.js";
12
12
  import { createRequire } from "node:module";
13
13
  import colors from "picocolors";
14
14
  import fs from "node:fs";
@@ -228,10 +228,18 @@ async function resolveConfig({ root, viteRunnerContext, reactRouterConfigFile, s
228
228
  ssr: true
229
229
  };
230
230
  let userAndPresetConfigs = mergeReactRouterConfig(...presets, reactRouterUserConfig);
231
- let { appDirectory: userAppDirectory, basename, buildDirectory: userBuildDirectory, buildEnd, prerender, routeDiscovery: userRouteDiscovery, serverBuildFile, serverBundles, serverModuleFormat, ssr } = {
231
+ let { appDirectory: userAppDirectory, basename, buildDirectory: userBuildDirectory, buildEnd, prerender, routeDiscovery: userRouteDiscovery, serverBuildFile, serverBundles, serverModuleFormat, ssr, unstable_apiServerOrigin } = {
232
232
  ...defaults,
233
233
  ...userAndPresetConfigs
234
234
  };
235
+ if (ssr === "unstable_api-only" && unstable_apiServerOrigin != null) try {
236
+ let url = new URL(unstable_apiServerOrigin);
237
+ if (url.pathname !== "/" || url.search || url.hash) return err("The `unstable_apiServerOrigin` config must be an origin without a path, query, or hash.");
238
+ unstable_apiServerOrigin = url.origin;
239
+ } catch {
240
+ return err("The `unstable_apiServerOrigin` config must be a valid absolute URL.");
241
+ }
242
+ else unstable_apiServerOrigin = void 0;
235
243
  if (!ssr && serverBundles) serverBundles = void 0;
236
244
  if (prerender) {
237
245
  let isValidPrerenderPathsConfig = (p) => typeof p === "boolean" || typeof p === "function" || Array.isArray(p);
@@ -295,6 +303,7 @@ async function resolveConfig({ root, viteRunnerContext, reactRouterConfigFile, s
295
303
  if ("unstable_subResourceIntegrity" in futureConfig) return err("The `future.unstable_subResourceIntegrity` flag has been stabilized and moved to a top-level `config.subResourceIntegrity` field");
296
304
  }
297
305
  let future = {
306
+ unstable_customRouteDiscovery: userAndPresetConfigs.future?.unstable_customRouteDiscovery ?? false,
298
307
  unstable_enableNodeReadableStream: userAndPresetConfigs.future?.unstable_enableNodeReadableStream ?? false,
299
308
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false
300
309
  };
@@ -314,6 +323,7 @@ async function resolveConfig({ root, viteRunnerContext, reactRouterConfigFile, s
314
323
  serverBundles,
315
324
  serverModuleFormat,
316
325
  ssr,
326
+ unstable_apiServerOrigin,
317
327
  splitRouteModules,
318
328
  subResourceIntegrity,
319
329
  allowedActionOrigins,
@@ -624,6 +634,8 @@ function generateServerBuild(ctx) {
624
634
  export const routeDiscovery: ServerBuild["routeDiscovery"];
625
635
  export const routes: ServerBuild["routes"];
626
636
  export const ssr: ServerBuild["ssr"];
637
+ export const unstable_apiOnly: ServerBuild["unstable_apiOnly"];
638
+ export const unstable_apiServerOrigin: ServerBuild["unstable_apiServerOrigin"];
627
639
  export const allowedActionOrigins: ServerBuild["allowedActionOrigins"];
628
640
  export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
629
641
  }
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v8.4.0
2
+ * @react-router/dev v8.5.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { _ as getVite, a as t, c as createConfigLoader, d as resolveEntryFiles, f as resolveRSCEntryFiles, g as getUserBuildRollupOptions, h as defineOptimizeDepsCompilerOptions, i as parse$1, l as hasNodeDependency, m as defineCompilerOptions, n as watch, o as traverse, p as ssrExternals, r as generate, s as configRouteToBranchRoute, v as preloadVite } from "./typegen-WywajSVn.js";
12
- import { u as invariant } from "./routes-BFdkiVnp.js";
11
+ import { _ as getVite, a as t, c as createConfigLoader, d as resolveEntryFiles, f as resolveRSCEntryFiles, g as getUserBuildRollupOptions, h as defineOptimizeDepsCompilerOptions, i as parse$1, l as hasNodeDependency, m as defineCompilerOptions, n as watch, o as traverse, p as ssrExternals, r as generate, s as configRouteToBranchRoute, v as preloadVite } from "./typegen-CiHN93zF.js";
12
+ import { u as invariant } from "./routes-Cyup1Gnm.js";
13
13
  import { createRequire } from "node:module";
14
14
  import colors from "picocolors";
15
15
  import { existsSync, readFileSync, readdirSync } from "node:fs";
@@ -114,7 +114,8 @@ const findDeps = async (vite, node, deps) => {
114
114
  }
115
115
  }
116
116
  async function addFromUrl(url) {
117
- let node = await vite.moduleGraph.getModuleByUrl(url);
117
+ let ssrNode = await vite.environments.ssr.moduleGraph.getModuleByUrl(url);
118
+ let node = ssrNode?.id ? vite.moduleGraph.getModuleById(ssrNode.id) : void 0;
118
119
  if (node) await addFromNode(node);
119
120
  }
120
121
  if (node.ssrTransformResult) {
@@ -916,7 +917,7 @@ async function nodeHttpFetch(request, { signal }) {
916
917
  return new Promise((resolve, reject) => {
917
918
  const req = http.request({
918
919
  protocol: url.protocol,
919
- hostname: url.hostname,
920
+ hostname: url.hostname.replace(/^\[|\]$/g, ""),
920
921
  port: url.port || void 0,
921
922
  path: url.pathname + url.search,
922
923
  method: request.method,
@@ -966,9 +967,15 @@ async function startPreviewServer(viteConfig) {
966
967
  }
967
968
  }
968
969
  function getResolvedUrl(previewServer) {
969
- const baseUrl = previewServer.resolvedUrls?.local[0];
970
- if (!baseUrl) throw new Error("Prerender: No resolved URL is available from the Vite preview server");
971
- return new URL(baseUrl);
970
+ const printableUrl = previewServer.resolvedUrls?.local[0];
971
+ const address = previewServer.httpServer?.address();
972
+ if (address && typeof address === "object" && address.address !== "::" && address.address !== "0.0.0.0") {
973
+ const protocol = printableUrl ? new URL(printableUrl).protocol : "http:";
974
+ const hostname = address.family === "IPv6" ? `[${address.address}]` : address.address;
975
+ return new URL(`${protocol}//${hostname}:${address.port}/`);
976
+ }
977
+ if (!printableUrl) throw new Error("Prerender: No resolved URL is available from the Vite preview server");
978
+ return new URL(printableUrl);
972
979
  }
973
980
  //#endregion
974
981
  //#region vite/plugin.ts
@@ -1237,14 +1244,16 @@ const reactRouterVitePlugin = () => {
1237
1244
  import * as entryServer from ${JSON.stringify(resolveFileUrl(ctx, ctx.entryServerFilePath, { publicPath: ctx.publicPath }))};
1238
1245
  ${Object.keys(routes).map((key, index) => {
1239
1246
  let route = routes[key];
1240
- if (isSpaMode && key !== "root") return `const route${index} = { default: () => null };`;
1247
+ if (isSpaMode && !isApiOnlyMode(ctx.reactRouterConfig) && key !== "root") return `const route${index} = { default: () => null };`;
1241
1248
  else return `import * as route${index} from ${JSON.stringify(resolveFileUrl(ctx, resolveRelativeRouteFilePath(route, ctx.reactRouterConfig), { publicPath: ctx.publicPath }))};`;
1242
1249
  }).join("\n")}
1243
1250
  export { default as assets } from ${JSON.stringify(virtual$1.serverManifest.id)};
1244
1251
  export const assetsBuildDirectory = ${JSON.stringify(path$2.relative(ctx.rootDirectory, getClientBuildDirectory$1(ctx.reactRouterConfig)))};
1245
1252
  export const basename = ${JSON.stringify(ctx.reactRouterConfig.basename)};
1246
1253
  export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
1247
- export const ssr = ${ctx.reactRouterConfig.ssr};
1254
+ export const ssr = ${Boolean(ctx.reactRouterConfig.ssr)};
1255
+ export const unstable_apiOnly = ${isApiOnlyMode(ctx.reactRouterConfig)};
1256
+ export const unstable_apiServerOrigin = ${isApiOnlyMode(ctx.reactRouterConfig) ? JSON.stringify(ctx.reactRouterConfig.unstable_apiServerOrigin) : "undefined"};
1248
1257
  export const isSpaMode = ${isSpaMode};
1249
1258
  export const prerender = ${JSON.stringify(prerenderPaths)};
1250
1259
  export const routeDiscovery = ${JSON.stringify(ctx.reactRouterConfig.routeDiscovery)};
@@ -1490,7 +1499,7 @@ const reactRouterVitePlugin = () => {
1490
1499
  invariant(serverEnvironment);
1491
1500
  return {
1492
1501
  __reactRouterPluginContext: ctx,
1493
- appType: viteCommand === "serve" && _viteConfigEnv.mode === "production" && ctx.reactRouterConfig.ssr === false ? "spa" : "custom",
1502
+ appType: viteCommand === "serve" && _viteConfigEnv.mode === "production" && (!ctx.reactRouterConfig.ssr || ctx.reactRouterConfig.ssr === "unstable_api-only") ? isApiOnlyMode(ctx.reactRouterConfig) ? "mpa" : "spa" : "custom",
1494
1503
  ssr: {
1495
1504
  external: serverEnvironment.resolve?.external,
1496
1505
  resolve: serverEnvironment.resolve
@@ -1723,6 +1732,21 @@ const reactRouterVitePlugin = () => {
1723
1732
  previewServer.middlewares.use(async (req, res, next) => {
1724
1733
  if (!ctx.reactRouterConfig.ssr && (!process.env.hasOwnProperty("IS_RR_BUILD_REQUEST") ? true : process.env.IS_RR_BUILD_REQUEST !== "yes")) return next();
1725
1734
  try {
1735
+ if (isApiOnlyMode(ctx.reactRouterConfig) && process.env.IS_RR_BUILD_REQUEST !== "yes") {
1736
+ let { basename, routeDiscovery } = ctx.reactRouterConfig;
1737
+ let manifestPath = routeDiscovery?.mode === "lazy" ? path$2.posix.join(basename, routeDiscovery.manifestPath ?? "/__manifest") : void 0;
1738
+ let pathname = new URL(req.originalUrl ?? req.url ?? "/", "http://localhost").pathname;
1739
+ if (!pathname.endsWith(".data") && pathname !== manifestPath) {
1740
+ let clientBuildDirectory = getClientBuildDirectory$1(ctx.reactRouterConfig);
1741
+ let previewPathname = decodeURIComponent(new URL(req.url ?? "/", "http://localhost").pathname);
1742
+ if (previewPathname.endsWith(".html") && existsSync(path$2.join(clientBuildDirectory, previewPathname))) return next();
1743
+ let fallbackFile = path$2.join(clientBuildDirectory, "__spa-fallback.html");
1744
+ if (!existsSync(fallbackFile)) fallbackFile = path$2.join(clientBuildDirectory, "index.html");
1745
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
1746
+ res.end(await readFile(fallbackFile));
1747
+ return;
1748
+ }
1749
+ }
1726
1750
  let handler = await getHandler();
1727
1751
  let request = await fromNodeRequest(req, res);
1728
1752
  await sendResponse(res, await handler(request, await reactRouterDevLoadContext(request)));
@@ -1955,7 +1979,8 @@ const reactRouterVitePlugin = () => {
1955
1979
  if (isRouteChunkModuleId(id)) id = id.split("?")[0];
1956
1980
  let route = getRoute(ctx.reactRouterConfig, id);
1957
1981
  if (!route) return;
1958
- if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
1982
+ let isServerEnvironment = isReactRouterServerEnvironment(ctx, this.environment.name);
1983
+ if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig) && !isApiOnlyMode(ctx.reactRouterConfig)) {
1959
1984
  let exportNames = getExportNames(code);
1960
1985
  let serverOnlyExports = exportNames.filter((exp) => {
1961
1986
  if (route.id === "root" && exp === "loader") return false;
@@ -1976,6 +2001,7 @@ const reactRouterVitePlugin = () => {
1976
2001
  let [filepath] = id.split("?");
1977
2002
  let ast = parse$1(code, { sourceType: "module" });
1978
2003
  if (!options?.ssr) removeExports(ast, SERVER_ONLY_ROUTE_EXPORTS);
2004
+ else if (isApiOnlyMode(ctx.reactRouterConfig) && isServerEnvironment && !isPrerenderingEnabled(ctx.reactRouterConfig) && viteCommand === "build" && route.id !== "root") removeExports(ast, CLIENT_ROUTE_EXPORTS$1);
1979
2005
  decorateComponentExportsWithProps(ast);
1980
2006
  return generate(ast, {
1981
2007
  sourceMaps: true,
@@ -2117,7 +2143,7 @@ const reactRouterVitePlugin = () => {
2117
2143
  else requests.push(createRouteRequest(prerenderPath, reactRouterConfig));
2118
2144
  }
2119
2145
  }
2120
- if (!ctx.reactRouterConfig.ssr) requests.push(createSpaModeRequest(ctx.reactRouterConfig));
2146
+ if (!ctx.reactRouterConfig.ssr || ctx.reactRouterConfig.ssr === "unstable_api-only") requests.push(createSpaModeRequest(ctx.reactRouterConfig));
2121
2147
  return requests;
2122
2148
  },
2123
2149
  async postProcess(request, response, metadata) {
@@ -2184,7 +2210,7 @@ const reactRouterVitePlugin = () => {
2184
2210
  async finalize(buildDirectory) {
2185
2211
  invariant(viteConfig);
2186
2212
  let { ssr } = ctx.reactRouterConfig;
2187
- if (!ssr) {
2213
+ if (!ssr || ssr === "unstable_api-only") {
2188
2214
  let spaFallback = path$2.join(buildDirectory, "__spa-fallback.html");
2189
2215
  let index = path$2.join(buildDirectory, "index.html");
2190
2216
  let finalSpaPath;
@@ -2319,7 +2345,10 @@ function isPrerenderingEnabled(reactRouterConfig) {
2319
2345
  return reactRouterConfig.prerender != null && reactRouterConfig.prerender !== false;
2320
2346
  }
2321
2347
  function isSpaModeEnabled(reactRouterConfig) {
2322
- return reactRouterConfig.ssr === false && !isPrerenderingEnabled(reactRouterConfig);
2348
+ return (!reactRouterConfig.ssr || reactRouterConfig.ssr === "unstable_api-only") && !isPrerenderingEnabled(reactRouterConfig);
2349
+ }
2350
+ function isApiOnlyMode(reactRouterConfig) {
2351
+ return reactRouterConfig.ssr === "unstable_api-only";
2323
2352
  }
2324
2353
  function getStaticPrerenderPaths(routes) {
2325
2354
  let paths = ["/"];
@@ -2677,7 +2706,7 @@ function createDataRequest(prerenderPath, reactRouterConfig, onlyRoutes, isResou
2677
2706
  let url = new URL(`http://localhost${normalizedPath}`);
2678
2707
  if (onlyRoutes?.length) url.searchParams.set("_routes", onlyRoutes.join(","));
2679
2708
  return {
2680
- request: new Request(url),
2709
+ request: new Request(url, { headers: { "X-React-Router-Prerender": "yes" } }),
2681
2710
  metadata: {
2682
2711
  type: "data",
2683
2712
  path: prerenderPath,
@@ -2687,7 +2716,7 @@ function createDataRequest(prerenderPath, reactRouterConfig, onlyRoutes, isResou
2687
2716
  }
2688
2717
  function createRouteRequest(prerenderPath, reactRouterConfig, data) {
2689
2718
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/");
2690
- let headers = new Headers();
2719
+ let headers = new Headers({ "X-React-Router-Prerender": "yes" });
2691
2720
  if (data) {
2692
2721
  let encodedData = encodeURI(data);
2693
2722
  if (encodedData.length < 8 * 1024) headers.set("X-React-Router-Prerender-Data", encodedData);
@@ -2702,8 +2731,13 @@ function createRouteRequest(prerenderPath, reactRouterConfig, data) {
2702
2731
  }
2703
2732
  function createResourceRouteRequest(prerenderPath, reactRouterConfig, requestInit) {
2704
2733
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
2734
+ let headers = new Headers(requestInit?.headers);
2735
+ headers.set("X-React-Router-Prerender", "yes");
2705
2736
  return {
2706
- request: new Request(`http://localhost${normalizedPath}`, requestInit),
2737
+ request: new Request(`http://localhost${normalizedPath}`, {
2738
+ ...requestInit,
2739
+ headers
2740
+ }),
2707
2741
  metadata: {
2708
2742
  type: "resource",
2709
2743
  path: prerenderPath
@@ -2863,6 +2897,38 @@ const ENSURE_CLIENT_ROUTE_MODULE_CHUNK_FOR_HMR = `
2863
2897
  import * as ___EnsureClientRouteModuleForHMR_REACT___ from "react";
2864
2898
  export function EnsureClientRouteModuleForHMR___() { return ___EnsureClientRouteModuleForHMR_REACT___.createElement(___EnsureClientRouteModuleForHMR_REACT___.Fragment, null) }
2865
2899
  `;
2900
+ function createEmptySourceMap() {
2901
+ return {
2902
+ version: 3,
2903
+ names: [],
2904
+ sources: [],
2905
+ mappings: ""
2906
+ };
2907
+ }
2908
+ function generateRouteModule(code, map, filename) {
2909
+ const ast = parse$1(code, { sourceType: "module" });
2910
+ const generatorOptions = {
2911
+ sourceMaps: true,
2912
+ sourceFileName: filename,
2913
+ inputSourceMap: map
2914
+ };
2915
+ return {
2916
+ ast,
2917
+ generate: () => generate(ast, generatorOptions)
2918
+ };
2919
+ }
2920
+ function prependUnmappedCode(prefix, generated) {
2921
+ if (generated.map) generated.map.mappings = ";".repeat(prefix.match(/\n/g)?.length ?? 0) + generated.map.mappings;
2922
+ return {
2923
+ code: prefix + generated.code,
2924
+ map: generated.map
2925
+ };
2926
+ }
2927
+ function appendUnmappedCode(generated, code) {
2928
+ if (!generated.code.endsWith("\n") && !code.startsWith("\n")) code = "\n" + code;
2929
+ if (generated.map) generated.map.mappings += ";A".repeat(code.match(/\n/g)?.length ?? 0);
2930
+ generated.code += code;
2931
+ }
2866
2932
  function virtualRouteModulesPlugin({ enforceSplitRouteModules, environments: { client = ["client", "ssr"], server = ["rsc"] } = {}, getRouteIdForFile, isRootRouteModule, transformToJs, shouldTransform }) {
2867
2933
  let clientEnvironments = new Set(client);
2868
2934
  let serverEnvironments = new Set(server);
@@ -2898,7 +2964,10 @@ function virtualRouteModulesPlugin({ enforceSplitRouteModules, environments: { c
2898
2964
  }
2899
2965
  });
2900
2966
  }
2901
- return { code: "\"use client\";\n" + result };
2967
+ return {
2968
+ code: "\"use client\";\n" + result,
2969
+ map: createEmptySourceMap()
2970
+ };
2902
2971
  }
2903
2972
  async function createServerRouteEntry(id, code, isRootRouteModule, routeId) {
2904
2973
  let result = "";
@@ -2938,16 +3007,19 @@ ${result}`;
2938
3007
  }
2939
3008
  });
2940
3009
  }
2941
- return { code: result };
3010
+ return {
3011
+ code: result,
3012
+ map: createEmptySourceMap()
3013
+ };
2942
3014
  }
2943
- function createServerRouteModule(code) {
2944
- const ast = parse$1(code, { sourceType: "module" });
3015
+ function createServerRouteModule(code, map, filename) {
3016
+ const { ast, generate } = generateRouteModule(code, map, filename);
2945
3017
  removeExports(ast, CLIENT_ROUTE_EXPORTS);
2946
- return generate(ast);
3018
+ return generate();
2947
3019
  }
2948
- async function createClientRouteModuleChunk(id, code, chunk, routeId, isRootRouteModule, isDevMode) {
3020
+ async function createClientRouteModuleChunk(id, code, chunk, routeId, isRootRouteModule, isDevMode, map, filename) {
2949
3021
  let routeChunks = detectRouteChunks(cache, id, code, isRootRouteModule);
2950
- const ast = parse$1(code, { sourceType: "module" });
3022
+ const { ast, generate } = generateRouteModule(code, map, filename);
2951
3023
  const { staticExports } = await parseRouteExports(code);
2952
3024
  if (chunk === "shared") removeExports(ast, [...SERVER_ROUTE_EXPORTS, ...routeChunks.chunkedExports]);
2953
3025
  else {
@@ -2955,33 +3027,26 @@ ${result}`;
2955
3027
  toRemove.delete(chunk);
2956
3028
  removeExports(ast, Array.from(toRemove));
2957
3029
  }
2958
- let result = "\"use client\";\n" + generate(ast).code;
3030
+ let result = prependUnmappedCode("\"use client\";\n", generate());
2959
3031
  if (chunk === "shared") {
2960
- if (isRootRouteModule && !staticExports.includes("ErrorBoundary") && !staticExports.includes("ServerErrorBoundary")) {
2961
- const hasRootLayout = staticExports.includes("Layout") || staticExports.includes("ServerLayout");
2962
- result += `\nimport { createElement as __rr_createElement } from "react";\n`;
2963
- result += `import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";\n`;
2964
- result += `export function ErrorBoundary() {\n`;
2965
- result += ` return __rr_createElement(UNSAFE_RSCDefaultRootErrorBoundary, { hasRootLayout: ${hasRootLayout} });\n`;
2966
- result += `}\n`;
2967
- }
2968
- result += ENSURE_CLIENT_ROUTE_MODULE_CHUNK_FOR_HMR;
3032
+ if (isRootRouteModule && !staticExports.includes("ErrorBoundary") && !staticExports.includes("ServerErrorBoundary")) appendUnmappedCode(result, `
3033
+ import { createElement as __rr_createElement } from "react";
3034
+ import { UNSAFE_RSCDefaultRootErrorBoundary } from "react-router";
3035
+ export function ErrorBoundary() {
3036
+ return __rr_createElement(UNSAFE_RSCDefaultRootErrorBoundary, { hasRootLayout: ${staticExports.includes("Layout") || staticExports.includes("ServerLayout")} });\n}\n`);
3037
+ appendUnmappedCode(result, ENSURE_CLIENT_ROUTE_MODULE_CHUNK_FOR_HMR);
2969
3038
  }
2970
3039
  let hasAction = staticExports.includes("action");
2971
3040
  let hasLoader = staticExports.includes("loader");
2972
3041
  let hasComponent = staticExports.includes("default") || staticExports.includes("ServerComponent");
2973
3042
  let hasErrorBoundary = staticExports.includes("ErrorBoundary") || staticExports.includes("ServerErrorBoundary");
2974
- if (isDevMode) {
2975
- result += `export function ReactRouterHMRMeta___() {return null;};\n`;
2976
- result += `Object.assign(ReactRouterHMRMeta___, {
3043
+ if (isDevMode) appendUnmappedCode(result, `export function ReactRouterHMRMeta___() {return null;};\nObject.assign(ReactRouterHMRMeta___, {
2977
3044
  hasAction: ${JSON.stringify(hasAction)},
2978
3045
  hasComponent: ${JSON.stringify(hasComponent)},
2979
3046
  hasErrorBoundary: ${JSON.stringify(hasErrorBoundary)},
2980
3047
  hasLoader: ${JSON.stringify(hasLoader)},
2981
3048
  hasClientLoader: ${JSON.stringify(staticExports.includes("clientLoader"))},
2982
- });\n`;
2983
- result += `\nif (import.meta.hot) {\n`;
2984
- result += ` import.meta.hot.accept((mod) => {
3049
+ });\n\nif (import.meta.hot) {\n import.meta.hot.accept((mod) => {
2985
3050
  if (typeof __reactRouterDataRouter === "object") {
2986
3051
  __reactRouterDataRouter._updateRoutesForHMR(new Map([[${JSON.stringify(routeId)}, {
2987
3052
  routeModule: mod,
@@ -2995,10 +3060,8 @@ ${result}`;
2995
3060
  }
2996
3061
  }
2997
3062
  });
2998
- `;
2999
- result += `}\n`;
3000
- }
3001
- return { code: result };
3063
+ }\n`);
3064
+ return result;
3002
3065
  }
3003
3066
  return {
3004
3067
  name: "react-router-rsc-virtual-route-modules",
@@ -3010,12 +3073,13 @@ ${result}`;
3010
3073
  let isClientEnvironment = clientEnvironments.has(this.environment.name);
3011
3074
  let isServerEnvironment = serverEnvironments.has(this.environment.name);
3012
3075
  if (!isClientEnvironment && !isServerEnvironment) return;
3013
- let code = await transformToJs(_code, filename);
3076
+ let transformed = await transformToJs(_code, filename);
3077
+ let code = transformed.code;
3014
3078
  let searchParams = rest.length > 0 ? new URLSearchParams(rest.join("?")) : null;
3015
3079
  let clientRouteModuleType = searchParams?.get("client-route-module");
3016
3080
  let isServerRouteModule = searchParams?.has("server-route-module");
3017
- if (clientRouteModuleType) return await createClientRouteModuleChunk(id, code, clientRouteModuleType, routeId, isRootRouteModule(filename), this.environment.mode === "dev");
3018
- if (isServerRouteModule) return createServerRouteModule(code);
3081
+ if (clientRouteModuleType) return await createClientRouteModuleChunk(id, code, clientRouteModuleType, routeId, isRootRouteModule(filename), this.environment.mode === "dev", transformed.map, filename);
3082
+ if (isServerRouteModule) return createServerRouteModule(code, transformed.map, filename);
3019
3083
  if (isClientEnvironment) return await createClientRouteEntry(id, code, isRootRouteModule(filename), routeId);
3020
3084
  return await createServerRouteEntry(id, code, isRootRouteModule(filename), routeId);
3021
3085
  }
@@ -3181,7 +3245,7 @@ function reactRouterRSCVitePlugin() {
3181
3245
  await preloadVite();
3182
3246
  let vite = getVite();
3183
3247
  let lang = getTransformLanguage(filename);
3184
- return ("transformWithOxc" in vite && typeof vite.transformWithOxc === "function" ? await vite.transformWithOxc(code, filename, {
3248
+ return "transformWithOxc" in vite && typeof vite.transformWithOxc === "function" ? await vite.transformWithOxc(code, filename, {
3185
3249
  lang,
3186
3250
  jsx: {
3187
3251
  runtime: "automatic",
@@ -3194,7 +3258,7 @@ function reactRouterRSCVitePlugin() {
3194
3258
  format: "esm",
3195
3259
  jsx: "automatic",
3196
3260
  jsxDev: viteCommand !== "build"
3197
- })).code;
3261
+ });
3198
3262
  }
3199
3263
  async function getClientVersion(source) {
3200
3264
  const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(source));
@@ -3223,6 +3287,7 @@ function reactRouterRSCVitePlugin() {
3223
3287
  if (userConfig.buildEnd) errors.push("buildEnd");
3224
3288
  if (userConfig.presets?.length) errors.push("presets");
3225
3289
  if (userConfig.serverBundles) errors.push("serverBundles");
3290
+ if (userConfig.ssr === "unstable_api-only") errors.push("ssr: \"unstable_api-only\"");
3226
3291
  if (errors.length) return `RSC Framework Mode does not currently support the following React Router config:\n${errors.map((x) => ` - ${x}`).join("\n")}\n`;
3227
3292
  }
3228
3293
  });
@@ -3654,7 +3719,7 @@ function createRSCOptimizeDepsRouteModulesPlugin({ routeFiles, transformToJs })
3654
3719
  let normalizedFilename = getVite().normalizePath(filename);
3655
3720
  if (!routeFiles.has(normalizedFilename)) return;
3656
3721
  return {
3657
- code: createClientRouteModuleForOptimizeDepsScan(await transformToJs(code, filename)).code,
3722
+ code: createClientRouteModuleForOptimizeDepsScan((await transformToJs(code, filename)).code).code,
3658
3723
  map: null,
3659
3724
  moduleType: "js"
3660
3725
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
3
  "type": "module",
4
- "version": "8.4.0",
4
+ "version": "8.5.0",
5
5
  "description": "Dev tools and CLI for React Router",
6
6
  "keywords": [
7
7
  "react",
@@ -107,7 +107,7 @@
107
107
  "semver": "^7.8.1",
108
108
  "tinyglobby": "^0.2.16",
109
109
  "valibot": "^1.4.1",
110
- "@react-router/node": "8.4.0"
110
+ "@react-router/node": "8.5.0"
111
111
  },
112
112
  "devDependencies": {
113
113
  "@babel/plugin-syntax-jsx": "^7.29.7",
@@ -132,8 +132,8 @@
132
132
  "vite": "^7.0.0",
133
133
  "wireit": "0.14.12",
134
134
  "wrangler": "^4.95.0",
135
- "react-router": "^8.4.0",
136
- "@react-router/serve": "8.4.0"
135
+ "react-router": "^8.5.0",
136
+ "@react-router/serve": "8.5.0"
137
137
  },
138
138
  "peerDependencies": {
139
139
  "@vitejs/plugin-rsc": "~0.5.26",
@@ -141,8 +141,8 @@
141
141
  "typescript": "^5.1.0 || ^6.0.0 || ^7.0.0",
142
142
  "vite": "^7.0.0 || ^8.0.0",
143
143
  "wrangler": "^4.0.0",
144
- "@react-router/serve": "^8.4.0",
145
- "react-router": "^8.4.0"
144
+ "@react-router/serve": "^8.5.0",
145
+ "react-router": "^8.5.0"
146
146
  },
147
147
  "peerDependenciesMeta": {
148
148
  "@vitejs/plugin-rsc": {