Future Changes
On this page

Future Changes

We try our best to keep major version upgrades simple and boring through the use of opt-in APIs and Future Flags. Future flags are used to gate breaking changes that don't otherwise have a good call-site opt-in strategy. By adopting all opt-in APIs and future flags, you should be able to upgrade to the next major version of React Router with minimal changes.

We highly recommend you make a commit after each step and ship it instead of doing everything all at once. Most flags can be adopted in any order, with exceptions noted below.

Minimum Versions



React Router v8 will require the following minimum versions. You can prepare for the upgrade by updating them while still on v7:

  • node@22.22+
  • react@19.2.7+/react-dom@19.2.7+

Framework mode will also require:

  • vite@7+ (requires future.v8_viteEnvironmentApi)
    • also make sure any custom Vite plugins or config are compatible with Vite 7.

Update to latest v7.x

Before adopting any future flags or call-site opt-in changes, you should update to the latest minor version of v7.x to make sure you have access to the latest flags. You may see a number of deprecation warnings as you upgrade, which we'll cover below.

๐Ÿ‘‰ Update to latest v7

npm install react-router@7 @react-router/{dev,node,etc.}@7

Future Flags

future.v8_middleware



Background

Middleware allows you to run code before and after the Response generation for the matched path. This enables common patterns like authentication, logging, error handling, and data preprocessing in a reusable way. Please see the docs for more information.

๐Ÿ‘‰ Enable the Flag

In Framework mode:

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_middleware: true,
  },
} satisfies Config;

In Data mode:

import { createBrowserRouter } from "react-router";

const router = createBrowserRouter(routes, {
  future: {
    v8_middleware: true,
  },
});

Update your Code

If you're using the context parameter in loader and action functions, you may need to update your code:

  • In Framework mode, if you're using react-router-serve, you should not need to make any updates. Otherwise, this only applies if you have a custom server with a getLoadContext function. Please see the docs on the middleware getLoadContext changes and the instructions to migrate to the new API.
  • In Data mode, add the Future module augmentation described in the middleware docs so context is typed correctly.

future.v8_splitRouteModules



Background

This feature enables splitting client-side route exports (clientLoader, clientAction, clientMiddleware, HydrateFallback) into separate chunks that can be loaded independently from the route component. This allows these exports to be fetched and executed while the component code is still downloading, improving performance for client-side data loading.

This can be set to true for opt-in behavior, or "enforce" to require all routes to be splittable (which will cause build failures for routes that cannot be split due to shared code).

๐Ÿ‘‰ Enable the Flag

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_splitRouteModules: true,
  },
} satisfies Config;

Update your Code

No code changes are required. This is an optimization feature that works automatically once enabled.

future.v8_viteEnvironmentApi



Background

This enables support for the experimental Vite Environment API, which provides a more flexible and powerful way to configure Vite environments. This is only available when using Vite 6+.

๐Ÿ‘‰ Enable the Flag

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_viteEnvironmentApi: true,
  },
} satisfies Config;

Update your Code

Most users won't need to make any changes. However, if you have custom Vite configuration that previously relied on the isSsrBuild flag โ€” such as a custom server build that sets build.rollupOptions.input โ€” you'll need to move that configuration under the per-environment Environment API config instead.

For example, a custom server build should move its SSR rollupOptions from the top-level build config into environments.ssr.build:

import { reactRouter } from "@react-router/dev/vite";
import { defineConfig } from "vite";

-export default defineConfig(({ isSsrBuild }) => ({
-  build: {
-    rollupOptions: isSsrBuild
-      ? {
-          input: "./server/app.ts",
-        }
-      : undefined,
-  },
+export default defineConfig({
+  environments: {
+    ssr: {
+      build: {
+        rollupOptions: {
+          input: "./server/app.ts",
+        },
+      },
+    },
+  },
   plugins: [reactRouter()],
-}));
+});

See the node-custom-server template for a complete example.

future.v8_passThroughRequests



Background

By default, React Router normalizes the request.url passed to your loader, action, and middleware functions by removing React Router's internal implementation details. Specifically, it removes .data suffixes and internal search parameters like ?index and ?_routes.

This flag eliminates that normalization and passes the raw HTTP request instance to your handlers. This provides a few benefits:

  • Reduces server-side overhead by eliminating multiple new Request() calls on the critical path
  • Allows you to distinguish document from data requests in your handlers based on the presence of a .data suffix (useful for observability purposes)

If you were previously relying on the normalization of request.url, you can switch to use the new sibling url parameter which contains a URL instance representing the normalized location.

๐Ÿ‘‰ Enable the Flag

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_passThroughRequests: true,
  },
} satisfies Config;

Update your Code

If your code relies on inspecting the request URL, you should review it for any assumptions about the URL format:

// โŒ Before: assuming no `.data` suffix in `request.url` pathname
export async function loader({
  request,
}: Route.LoaderArgs) {
  let url = new URL(request.url);
  if (url.pathname === "/path") {
    // This check might now behave differently because the request pathname will
    // contain the `.data` suffix on data requests
  }
}

// โœ… After: use `url` for normalized routing logic and `request.url`
// for raw routing logic
export async function loader({
  request,
  url,
}: Route.LoaderArgs) {
  if (url.pathname === "/path") {
    // This will always have the `.data` suffix stripped
  }

  // And now you can distinguish between document versus data requests
  let isDataRequest = new URL(
    request.url,
  ).pathname.endsWith(".data");
}

future.v8_trailingSlashAwareDataRequests



Background

React Router serves Framework mode data requests from .data URLs. Previously, data requests for routes with and without trailing slashes could map to the same .data URL because trailing slashes were not considered during URL generation. This flag preserves trailing slash semantics for data request URLs to avoid ambiguity when your app distinguishes between trailing-slash and non-trailing-slash URLs.

Currently, your HTTP and request pathnames would be as follows for /a/b/c and /a/b/c/

URL /a/b/c HTTP pathname request pathname`
Document /a/b/c /a/b/c โœ…
Data /a/b/c.data /a/b/c โœ…
URL /a/b/c/ HTTP pathname request pathname`
Document /a/b/c/ /a/b/c/ โœ…
Data /a/b/c.data /a/b/c โš ๏ธ

With this flag enabled, these pathnames will be made consistent though a new _.data format for client-side .data requests:

URL /a/b/c HTTP pathname request pathname`
Document /a/b/c /a/b/c โœ…
Data /a/b/c.data /a/b/c โœ…
URL /a/b/c/ HTTP pathname request pathname`
Document /a/b/c/ /a/b/c/ โœ…
Data /a/b/c/_.data โฌ…๏ธ /a/b/c/ โœ…

This flag also aligns the root data request to match this behavior by changing it from /_root.data to /_.data.

๐Ÿ‘‰ Enable the Flag

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    v8_trailingSlashAwareDataRequests: true,
  },
} satisfies Config;

Update your Code

If you have custom app, CDN, cache, or rewrite logic that matches .data request URLs, update it to handle the new trailing-slash-aware /_.data format.

Other Planned Breaking Changes

The changes in this section are not controlled by future flags, but you can update your code in v7 to be ready for v8.

meta data Argument



Background

The data fields passed to route module meta functions are deprecated and will be removed in React Router v8. Use loaderData instead on MetaArgs and each item in MetaArgs.matches.

๐Ÿ‘‰ Update your Code

Replace data with loaderData in your meta functions:

export function meta({
-  data,
+  loaderData,
  matches,
}: Route.MetaArgs) {
  return [
    {
-      title: data.title,
+      title: loaderData.title,
    },
  ];
}

If you read data from parent matches, update those references too:

export function meta({ matches }: Route.MetaArgs) {
  let rootMatch = matches.find((match) => match.id === "root");
-  let rootData = rootMatch?.data;
+  let rootData = rootMatch?.loaderData;

  return [{ title: rootData?.siteTitle }];
}

react-router-dom



Background

React Router v8 will remove the react-router-dom re-export package. In v8, you should import DOM-specific APIs from react-router/dom and everything else from react-router.

๐Ÿ‘‰ Update your Code

Uninstall react-router-dom:

npm uninstall react-router-dom

Replace react-router-dom imports with react-router imports:

-import { Link, useLocation } from "react-router-dom";
+import { Link, useLocation } from "react-router";

For DOM-specific APIs, import from react-router/dom:

-import { RouterProvider } from "react-router-dom";
+import { RouterProvider } from "react-router/dom";

Cloudflare Vite Plugin



Background

React Router v8 will remove the React Router Cloudflare dev proxy. Cloudflare projects should use @cloudflare/vite-plugin instead.

๐Ÿ‘‰ Update your Code

Replace cloudflareDevProxy with cloudflare:

import { reactRouter } from "@react-router/dev/vite";
-import { cloudflareDevProxy } from "@react-router/dev/vite/cloudflare";
+import { cloudflare } from "@cloudflare/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
-    cloudflareDevProxy(),
+    cloudflare(),
    reactRouter(),
  ],
});

@react-router/architect useRequestContextDomainName



Background

The @react-router/architect adapter currently uses X-Forwarded-Host when creating the request, falling back to the Host header. In React Router v8, the adapter will use event.requestContext.domainName by default, falling back to the Host header.

๐Ÿ‘‰ Update your Code

Opt in to the v8 behavior now by passing useRequestContextDomainName: true:

import { createRequestHandler } from "@react-router/architect";
import * as build from "./build/server";

export const handler = createRequestHandler({
  build,
  useRequestContextDomainName: true,
});

This option will be removed in v8 once the event.requestContext.domainName behavior is the default.

Unstable Future Flags (Optional)

We document some unstable flags here as a reference for folks contributing to the project via beta testing, but they are not generally recommended for production use and may have breaking changes in patch or minor releases - adopt with caution!

future.unstable_optimizeDeps



Background

This flag lets React Router provide Vite's dependency optimizer with the client entry file and route module files. This can improve dependency optimization in development, but the behavior is still experimental.

๐Ÿ‘‰ Enable the Flag

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    unstable_optimizeDeps: true,
  },
} satisfies Config;

Update your Code

No code changes are required. If you run into dependency optimization issues after enabling this flag, remove the flag and restart the dev server.

future.unstable_previewServerPrerendering



Background

This flag switches prerendering to use Vite's preview-server request flow instead of the current build-time prerendering path so that it works in non-Node environments such as workerd. Enabling this flag also enables future.v8_viteEnvironmentApi, so you should review the future.v8_viteEnvironmentApi guidance above before adopting it.

This ends up only changing the underlying prerender implementation but is not expected to cause any breaking changes. Because it is not expected to break, you do not have to adopt this flag prior to v8 and therefore it wasn't ever converted to a v8_ flag.

๐Ÿ‘‰ Enable the Flag

import type { Config } from "@react-router/dev/config";

export default {
  future: {
    unstable_previewServerPrerendering: true,
  },
} satisfies Config;

Update your Code

No code changes are required unless your app has a custom Vite configuration that is affected by future.v8_viteEnvironmentApi.

Docs and examples CC 4.0
Edit