@react-router/dev 7.11.0 → 7.12.0-pre.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,50 @@
1
1
  # `@react-router/dev`
2
2
 
3
+ ## 7.12.0-pre.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add additional layer of CSRF protection by rejecting submissions to UI routes from external origins. If you need to permit access to specific external origins, you can specify them in the `react-router.config.ts` config `allowedActionOrigins` field. ([#14708](https://github.com/remix-run/react-router/pull/14708))
8
+
9
+ ### Patch Changes
10
+
11
+ - Fix `Maximum call stack size exceeded` errors when HMR is triggered against code with cyclic imports ([#14522](https://github.com/remix-run/react-router/pull/14522))
12
+ - fix(vite): Skip SSR middleware in preview server for SPA mode ([#14673](https://github.com/remix-run/react-router/pull/14673))
13
+ - [UNSTABLE] Add a new `future.unstable_trailingSlashAwareDataRequests` flag to provide consistent behavior of `request.pathname` inside `middleware`, `loader`, and `action` functions on document and data requests when a trailing slash is present in the browser URL. ([#14644](https://github.com/remix-run/react-router/pull/14644))
14
+
15
+ Currently, your HTTP and `request` pathnames would be as follows for `/a/b/c` and `/a/b/c/`
16
+
17
+ | URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
18
+ | ------------ | ----------------- | ----------------------- |
19
+ | **Document** | `/a/b/c` | `/a/b/c` ✅ |
20
+ | **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
21
+
22
+ | URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
23
+ | ------------- | ----------------- | ----------------------- |
24
+ | **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
25
+ | **Data** | `/a/b/c.data` | `/a/b/c` ⚠️ |
26
+
27
+ With this flag enabled, these pathnames will be made consistent though a new `_.data` format for client-side `.data` requests:
28
+
29
+ | URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
30
+ | ------------ | ----------------- | ----------------------- |
31
+ | **Document** | `/a/b/c` | `/a/b/c` ✅ |
32
+ | **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
33
+
34
+ | URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
35
+ | ------------- | ------------------ | ----------------------- |
36
+ | **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
37
+ | **Data** | `/a/b/c/_.data` ⬅️ | `/a/b/c/` ✅ |
38
+
39
+ This a bug fix but we are putting it behind an opt-in flag because it has the potential to be a "breaking bug fix" if you are relying on the URL format for any other application or caching logic.
40
+
41
+ Enabling this flag also changes the format of client side `.data` requests from `/_root.data` to `/_.data` when navigating to `/` to align with the new format. This does not impact the `request` pathname which is still `/` in all cases.
42
+
43
+ - Updated dependencies:
44
+ - `react-router@7.12.0-pre.0`
45
+ - `@react-router/node@7.12.0-pre.0`
46
+ - `@react-router/serve@7.12.0-pre.0`
47
+
3
48
  ## 7.11.0
4
49
 
5
50
  ### Minor Changes
@@ -247,7 +292,6 @@
247
292
  - Stabilize middleware and context APIs. ([#14215](https://github.com/remix-run/react-router/pull/14215))
248
293
 
249
294
  We have removed the `unstable_` prefix from the following APIs and they are now considered stable and ready for production use:
250
-
251
295
  - [`RouterContextProvider`](https://reactrouter.com/api/utils/RouterContextProvider)
252
296
  - [`createContext`](https://reactrouter.com/api/utils/createContext)
253
297
  - `createBrowserRouter` [`getContext`](https://reactrouter.com/api/data-routers/createBrowserRouter#optsgetcontext) option
@@ -990,7 +1034,6 @@
990
1034
  ```
991
1035
 
992
1036
  This initial implementation targets type inference for:
993
-
994
1037
  - `Params` : Path parameters from your routing config in `routes.ts` including file-based routing
995
1038
  - `LoaderData` : Loader data from `loader` and/or `clientLoader` within your route module
996
1039
  - `ActionData` : Action data from `action` and/or `clientAction` within your route module
@@ -1005,7 +1048,6 @@
1005
1048
  ```
1006
1049
 
1007
1050
  Check out our docs for more:
1008
-
1009
1051
  - [_Explanations > Type Safety_](https://reactrouter.com/dev/guides/explanation/type-safety)
1010
1052
  - [_How-To > Setting up type safety_](https://reactrouter.com/dev/guides/how-to/setting-up-type-safety)
1011
1053
 
@@ -1205,7 +1247,6 @@
1205
1247
  - Vite: Provide `Unstable_ServerBundlesFunction` and `Unstable_VitePluginConfig` types ([#8654](https://github.com/remix-run/remix/pull/8654))
1206
1248
 
1207
1249
  - Vite: add `--sourcemapClient` and `--sourcemapServer` flags to `remix vite:build` ([#8613](https://github.com/remix-run/remix/pull/8613))
1208
-
1209
1250
  - `--sourcemapClient`
1210
1251
 
1211
1252
  - `--sourcemapClient=inline`
@@ -1542,7 +1583,6 @@
1542
1583
  - Add support for `clientLoader`/`clientAction`/`HydrateFallback` route exports ([RFC](https://github.com/remix-run/remix/discussions/7634)) ([#8173](https://github.com/remix-run/remix/pull/8173))
1543
1584
 
1544
1585
  Remix now supports loaders/actions that run on the client (in addition to, or instead of the loader/action that runs on the server). While we still recommend server loaders/actions for the majority of your data needs in a Remix app - these provide some levers you can pull for more advanced use-cases such as:
1545
-
1546
1586
  - Leveraging a data source local to the browser (i.e., `localStorage`)
1547
1587
  - Managing a client-side cache of server data (like `IndexedDB`)
1548
1588
  - Bypassing the Remix server in a BFF setup and hitting your API directly from the browser
@@ -1946,7 +1986,6 @@
1946
1986
  - Output esbuild metafiles for bundle analysis ([#6772](https://github.com/remix-run/remix/pull/6772))
1947
1987
 
1948
1988
  Written to server build directory (`build/` by default):
1949
-
1950
1989
  - `metafile.css.json`
1951
1990
  - `metafile.js.json` (browser JS)
1952
1991
  - `metafile.server.json` (server JS)
@@ -2044,7 +2083,6 @@
2044
2083
  - built-in tls support ([#6483](https://github.com/remix-run/remix/pull/6483))
2045
2084
 
2046
2085
  New options:
2047
-
2048
2086
  - `--tls-key` / `tlsKey`: TLS key
2049
2087
  - `--tls-cert` / `tlsCert`: TLS Certificate
2050
2088
 
@@ -2315,7 +2353,6 @@
2315
2353
  ```
2316
2354
 
2317
2355
  The dev server will:
2318
-
2319
2356
  - force `NODE_ENV=development` and warn you if it was previously set to something else
2320
2357
  - rebuild your app whenever your Remix app code changes
2321
2358
  - restart your app server whenever rebuilds succeed
package/dist/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @react-router/dev v7.11.0
3
+ * @react-router/dev v7.12.0-pre.0
4
4
  *
5
5
  * Copyright (c) Remix Software Inc.
6
6
  *
@@ -506,10 +506,12 @@ async function resolveConfig({
506
506
  let future = {
507
507
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
508
508
  unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
509
+ unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
509
510
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
510
511
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
511
512
  v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
512
513
  };
514
+ let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
513
515
  let reactRouterConfig = deepFreeze({
514
516
  appDirectory,
515
517
  basename: basename3,
@@ -523,6 +525,7 @@ async function resolveConfig({
523
525
  serverBundles,
524
526
  serverModuleFormat,
525
527
  ssr,
528
+ allowedActionOrigins,
526
529
  unstable_routeConfig: routeConfig
527
530
  });
528
531
  for (let preset of reactRouterUserConfig.presets ?? []) {
@@ -956,6 +959,7 @@ function generateServerBuild(ctx) {
956
959
  export const routeDiscovery: ServerBuild["routeDiscovery"];
957
960
  export const routes: ServerBuild["routes"];
958
961
  export const ssr: ServerBuild["ssr"];
962
+ export const allowedActionOrigins: ServerBuild["allowedActionOrigins"];
959
963
  export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
960
964
  }
961
965
  `;
package/dist/config.d.ts CHANGED
@@ -39,6 +39,7 @@ type ServerModuleFormat = "esm" | "cjs";
39
39
  interface FutureConfig {
40
40
  unstable_optimizeDeps: boolean;
41
41
  unstable_subResourceIntegrity: boolean;
42
+ unstable_trailingSlashAwareDataRequests: boolean;
42
43
  /**
43
44
  * Enable route middleware
44
45
  */
@@ -147,6 +148,11 @@ type ReactRouterConfig = {
147
148
  * SPA without server-rendering. Default's to `true`.
148
149
  */
149
150
  ssr?: boolean;
151
+ /**
152
+ * The allowed origins for actions / mutations. Does not apply to routes
153
+ * without a component. micromatch glob patterns are supported.
154
+ */
155
+ allowedActionOrigins?: string[];
150
156
  };
151
157
  type ResolvedReactRouterConfig = Readonly<{
152
158
  /**
@@ -212,6 +218,11 @@ type ResolvedReactRouterConfig = Readonly<{
212
218
  * SPA without server-rendering. Default's to `true`.
213
219
  */
214
220
  ssr: boolean;
221
+ /**
222
+ * The allowed origins for actions / mutations. Does not apply to routes
223
+ * without a component. micromatch glob patterns are supported.
224
+ */
225
+ allowedActionOrigins: string[] | false;
215
226
  /**
216
227
  * The resolved array of route config entries exported from `routes.ts`
217
228
  */
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v7.11.0
2
+ * @react-router/dev v7.12.0-pre.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 v7.11.0
2
+ * @react-router/dev v7.12.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v7.11.0
2
+ * @react-router/dev v7.12.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -535,10 +535,12 @@ async function resolveConfig({
535
535
  let future = {
536
536
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
537
537
  unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
538
+ unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
538
539
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
539
540
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
540
541
  v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
541
542
  };
543
+ let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
542
544
  let reactRouterConfig = deepFreeze({
543
545
  appDirectory,
544
546
  basename,
@@ -552,6 +554,7 @@ async function resolveConfig({
552
554
  serverBundles,
553
555
  serverModuleFormat,
554
556
  ssr,
557
+ allowedActionOrigins,
555
558
  unstable_routeConfig: routeConfig
556
559
  });
557
560
  for (let preset of reactRouterUserConfig.presets ?? []) {
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v7.11.0
2
+ * @react-router/dev v7.12.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -562,10 +562,12 @@ async function resolveConfig({
562
562
  let future = {
563
563
  unstable_optimizeDeps: userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
564
564
  unstable_subResourceIntegrity: userAndPresetConfigs.future?.unstable_subResourceIntegrity ?? false,
565
+ unstable_trailingSlashAwareDataRequests: userAndPresetConfigs.future?.unstable_trailingSlashAwareDataRequests ?? false,
565
566
  v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
566
567
  v8_splitRouteModules: userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
567
568
  v8_viteEnvironmentApi: userAndPresetConfigs.future?.v8_viteEnvironmentApi ?? false
568
569
  };
570
+ let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
569
571
  let reactRouterConfig = deepFreeze({
570
572
  appDirectory,
571
573
  basename: basename3,
@@ -579,6 +581,7 @@ async function resolveConfig({
579
581
  serverBundles,
580
582
  serverModuleFormat,
581
583
  ssr,
584
+ allowedActionOrigins,
582
585
  unstable_routeConfig: routeConfig
583
586
  });
584
587
  for (let preset of reactRouterUserConfig.presets ?? []) {
@@ -966,6 +969,7 @@ function generateServerBuild(ctx) {
966
969
  export const routeDiscovery: ServerBuild["routeDiscovery"];
967
970
  export const routes: ServerBuild["routes"];
968
971
  export const ssr: ServerBuild["ssr"];
972
+ export const allowedActionOrigins: ServerBuild["allowedActionOrigins"];
969
973
  export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
970
974
  }
971
975
  `;
@@ -2921,7 +2925,9 @@ var reactRouterVitePlugin = () => {
2921
2925
  href: "${ctx.publicPath}@react-router/critical.css?pathname=" + pathname,
2922
2926
  };
2923
2927
  }
2924
- ` : ""}`;
2928
+ ` : ""}
2929
+ export const allowedActionOrigins = ${JSON.stringify(ctx.reactRouterConfig.allowedActionOrigins)};
2930
+ `;
2925
2931
  };
2926
2932
  let loadViteManifest = async (directory) => {
2927
2933
  let manifestContents = await (0, import_promises2.readFile)(
@@ -3536,6 +3542,9 @@ var reactRouterVitePlugin = () => {
3536
3542
  },
3537
3543
  configurePreviewServer(previewServer) {
3538
3544
  return () => {
3545
+ if (!ctx.reactRouterConfig.ssr) {
3546
+ return;
3547
+ }
3539
3548
  previewServer.middlewares.use(async (req, res, next) => {
3540
3549
  try {
3541
3550
  let serverBuildDirectory = getServerBuildDirectory(
@@ -4099,10 +4108,8 @@ var reactRouterVitePlugin = () => {
4099
4108
  if (this.environment.name !== "ssr" && modules.length <= 0) {
4100
4109
  return;
4101
4110
  }
4102
- let clientModules = uniqueNodes(
4103
- modules.flatMap(
4104
- (mod) => getParentClientNodes(server.environments.client.moduleGraph, mod)
4105
- )
4111
+ let clientModules = modules.flatMap(
4112
+ (mod) => getParentClientNodes(server.environments.client.moduleGraph, mod)
4106
4113
  );
4107
4114
  for (let clientModule of clientModules) {
4108
4115
  server.environments.client.reloadModule(clientModule);
@@ -4113,30 +4120,22 @@ var reactRouterVitePlugin = () => {
4113
4120
  warnOnClientSourceMaps()
4114
4121
  ];
4115
4122
  };
4116
- function getParentClientNodes(clientModuleGraph, module2) {
4123
+ function getParentClientNodes(clientModuleGraph, module2, seenNodes = /* @__PURE__ */ new Set()) {
4117
4124
  if (!module2.id) {
4118
4125
  return [];
4119
4126
  }
4127
+ if (seenNodes.has(module2.url)) {
4128
+ return [];
4129
+ }
4130
+ seenNodes.add(module2.url);
4120
4131
  let clientModule = clientModuleGraph.getModuleById(module2.id);
4121
4132
  if (clientModule) {
4122
4133
  return [clientModule];
4123
4134
  }
4124
4135
  return [...module2.importers].flatMap(
4125
- (importer) => getParentClientNodes(clientModuleGraph, importer)
4136
+ (importer) => getParentClientNodes(clientModuleGraph, importer, seenNodes)
4126
4137
  );
4127
4138
  }
4128
- function uniqueNodes(nodes) {
4129
- let nodeUrls = /* @__PURE__ */ new Set();
4130
- let unique = [];
4131
- for (let node of nodes) {
4132
- if (nodeUrls.has(node.url)) {
4133
- continue;
4134
- }
4135
- nodeUrls.add(node.url);
4136
- unique.push(node);
4137
- }
4138
- return unique;
4139
- }
4140
4139
  function addRefreshWrapper(reactRouterConfig, code, id) {
4141
4140
  let route = getRoute(reactRouterConfig, id);
4142
4141
  let acceptExports = route ? CLIENT_NON_COMPONENT_EXPORTS : [];
@@ -4411,7 +4410,21 @@ function getStaticPrerenderPaths(routes) {
4411
4410
  };
4412
4411
  }
4413
4412
  async function prerenderData(handler, prerenderPath, onlyRoutes, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
4414
- let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
4413
+ let dataRequestPath;
4414
+ if (reactRouterConfig.future.unstable_trailingSlashAwareDataRequests) {
4415
+ if (prerenderPath.endsWith("/")) {
4416
+ dataRequestPath = `${prerenderPath}_.data`;
4417
+ } else {
4418
+ dataRequestPath = `${prerenderPath}.data`;
4419
+ }
4420
+ } else {
4421
+ if (prerenderPath === "/") {
4422
+ dataRequestPath = "/_root.data";
4423
+ } else {
4424
+ dataRequestPath = `${prerenderPath.replace(/\/$/, "")}.data`;
4425
+ }
4426
+ }
4427
+ let normalizedPath = `${reactRouterConfig.basename}${dataRequestPath}`.replace(/\/\/+/g, "/");
4415
4428
  let url2 = new URL(`http://localhost${normalizedPath}`);
4416
4429
  if (onlyRoutes?.length) {
4417
4430
  url2.searchParams.set("_routes", onlyRoutes.join(","));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-router/dev",
3
- "version": "7.11.0",
3
+ "version": "7.12.0-pre.0",
4
4
  "description": "Dev tools and CLI for React Router",
5
5
  "homepage": "https://reactrouter.com",
6
6
  "bugs": {
@@ -92,7 +92,7 @@
92
92
  "tinyglobby": "^0.2.14",
93
93
  "valibot": "^1.2.0",
94
94
  "vite-node": "^3.2.2",
95
- "@react-router/node": "7.11.0"
95
+ "@react-router/node": "7.12.0-pre.0"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@types/babel__core": "^7.20.5",
@@ -116,8 +116,8 @@
116
116
  "vite": "^6.3.0",
117
117
  "wireit": "0.14.9",
118
118
  "wrangler": "^4.23.0",
119
- "@react-router/serve": "7.11.0",
120
- "react-router": "^7.11.0"
119
+ "@react-router/serve": "7.12.0-pre.0",
120
+ "react-router": "^7.12.0-pre.0"
121
121
  },
122
122
  "peerDependencies": {
123
123
  "@vitejs/plugin-rsc": "~0.5.7",
@@ -125,8 +125,8 @@
125
125
  "typescript": "^5.1.0",
126
126
  "vite": "^5.1.0 || ^6.0.0 || ^7.0.0",
127
127
  "wrangler": "^3.28.2 || ^4.0.0",
128
- "@react-router/serve": "^7.11.0",
129
- "react-router": "^7.11.0"
128
+ "@react-router/serve": "^7.12.0-pre.0",
129
+ "react-router": "^7.12.0-pre.0"
130
130
  },
131
131
  "peerDependenciesMeta": {
132
132
  "@vitejs/plugin-rsc": {