React Router v8, released on 17 June 2026, is the current major version of React Router. It is a deliberately small upgrade from React Router v7: it removes the react-router-dom package, makes v7’s “future flags” (including middleware) the default, ships as ESM only, and requires React 19.2.7+, Node 22.22+ and Vite 7+. If your v7 app already uses react-router imports and the future flags, upgrading is mostly a version bump.
I first wrote this post about React Router v7. This updated version explains what v7 introduced (most of it is still how React Router works), what changed in v8, the three modes with working code, and how to upgrade. I tested the examples on React Router 8.5 in both data mode and framework mode, including type generation, server rendering and middleware.
React Router v7 vs v8 at a Glance
| React Router v7 | React Router v8 | |
|---|---|---|
| Released | November 2024 | 17 June 2026 |
| Packages | react-router (plus react-router-dom as a re-export for v6 users) | react-router only; DOM pieces from react-router/dom |
| Minimum React | 18 | 19.2.7 |
| Minimum Node (framework mode) | 20 | 22.22 |
| Vite (framework mode) | 5.1+ at launch | 7+ (Environment API always on) |
| Middleware | Opt-in with future.v8_middleware | Always on |
| Split route modules | Opt-in flag | On by default (splitRouteModules) |
| Module format | ESM and CommonJS | ESM only |
Route meta data | data (deprecated) or loaderData | loaderData only |
| Support status | Previous major | Current; next major (v9) planned around mid-2027 |
What Is New in React Router v8?
The official changelog describes v8 as the first release on a new yearly major-version cadence, timed with Node 20’s end of life. The team’s strategy is to make majors “boring” by shipping breaking changes early behind future flags. The changes:
react-router-domis gone. ImportRouterProviderandHydratedRouterfromreact-router/dom, and everything else fromreact-router.- New minimum versions: Node 22.22.0, React 19.2.7 and Vite 7. React Router now supports all Active LTS Node versions and only the latest minor of Maintenance LTS lines.
- ESM only, with TypeScript targets raised to ES2022.
- Middleware is always enabled. The
contextpassed to loaders, actions and middleware is always aRouterContextProvider, and custom servergetLoadContextfunctions must return one. - Other v7 future flags became defaults: trailing-slash-aware data requests, the raw request passed through to loaders and actions, and the Vite Environment API. Split route modules moved to a top-level
splitRouteModulesoption that is on by default. - Removed deprecated APIs: the
datafield inmetafunctions (useloaderData), the internalhasErrorBoundaryroute field, and the Cloudflare dev proxy (use@cloudflare/vite-plugin).
Since 8.0, minor releases have added observability metadata and an optional agent skill in create-react-router (8.1), a Web Streams server entry (8.2), and fewer route re-renders (8.4). React Router 8.5 (19 October 2026) deprecates React Router’s own view transition APIs in favour of React’s <ViewTransition> component in React 19.3. Experimental React Server Components support is still marked unstable.
What React Router v7 Introduced (Still True in v8)
Most of what makes modern React Router different arrived in v7, and v8 keeps it:
- A non-breaking path from v6: v6 apps that enabled the v7 future flags could upgrade without code changes.
- Remix merged in: Remix v2’s framework features (server rendering, file-based route modules, loaders and actions, bundling) became React Router’s framework mode. Remix v2 apps upgrade to React Router v7 or later.
- Three modes (declarative, data and framework), so you choose how much React Router does for you.
- Type generation for route params, loader data and actions in framework mode.
- Pre-rendering, streaming and SPA mode as configuration options rather than separate tools.
The Three Modes
| Mode | How you set it up | Features | Use it for |
|---|---|---|---|
| Declarative | <BrowserRouter>, <Routes>, <Route> | Matching, links, params, navigation | Simple SPAs, or when another tool handles data |
| Data | createBrowserRouter and <RouterProvider> | Adds loaders, actions, pending states, error elements, middleware | SPAs that want data loading tied to routes |
| Framework | The React Router Vite plugin, routes.ts and route modules | Adds server rendering, pre-rendering, type generation, code splitting | Full-stack apps and SEO-sensitive sites |
The official modes guide compares them in detail. If you’re new to routing, start with my React Router v8 tutorial for beginners, which uses declarative mode.
Data Mode in v8: Loaders, Actions and Middleware
Data mode is a good middle ground for client-rendered apps. Note the v8 imports: createBrowserRouter from react-router, and RouterProvider from react-router/dom:
import { createBrowserRouter, createContext, redirect } from 'react-router';
import { RouterProvider } from 'react-router/dom';
export const requestTimeContext = createContext(0);
async function timingMiddleware({ request, context }, next) {
const start = performance.now();
context.set(requestTimeContext, start);
await next(); // runs child middleware and loaders
console.log(`${new URL(request.url).pathname} loaded in ${Math.round(performance.now() - start)} ms`);
}
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
errorElement: <ErrorPage />,
middleware: [timingMiddleware], // no future flag needed in v8
children: [
{ path: 'products', element: <Products />, loader: productsLoader },
{ path: 'products/new', element: <NewProduct />, action: newProductAction },
],
},
]);
createRoot(document.getElementById('root')).render(<RouterProvider router={router} />);Loaders run before a route renders and their result is read with useLoaderData(); actions handle <Form method="post"> submissions and can redirect(); errors thrown with data(..., { status }) render the nearest errorElement. I used this exact setup for a product list with search, filters and pagination; the full code and test results are in my guide to React query params. The middleware logged lines like /products loaded in 5 ms on each navigation.
Framework Mode in v8: Typed Routes, SSR and Middleware
Create a framework-mode project with the official starter, which gave me React Router 8.5, React 19.2 and Vite 8:
npx create-react-router@latest my-app
cd my-app
npm run devRoutes are declared in app/routes.ts:
import { type RouteConfig, index, route } from "@react-router/dev/routes";
export default [
index("routes/home.tsx"),
route("products/:productId", "routes/product.tsx"),
] satisfies RouteConfig;A typed context for sharing data between middleware and loaders:
import { createContext } from "react-router";
export type User = { name: string };
export const userContext = createContext<User | null>(null);And a route module that uses middleware, a loader, an action, meta and an error boundary, with types generated from the route config:
import { data, Form } from "react-router";
import type { Route } from "./+types/product";
import { userContext } from "../context";
const products: Record<string, { name: string; price: number; likes: number }> = {
"1": { name: "Mechanical keyboard", price: 120, likes: 0 },
};
// Runs before every loader and action on this route
export const middleware: Route.MiddlewareFunction[] = [
async ({ request, context }, next) => {
const name = new URL(request.url).searchParams.get("as") ?? "guest";
context.set(userContext, { name });
return next();
},
];
export async function loader({ params, context }: Route.LoaderArgs) {
const product = products[params.productId]; // params.productId is typed as string
if (!product) throw data("Product not found", { status: 404 });
return { product, user: context.get(userContext) };
}
export async function action({ params }: Route.ActionArgs) {
products[params.productId].likes += 1;
return { ok: true };
}
// v8: meta receives loaderData (the old `data` field was removed)
export function meta({ loaderData }: Route.MetaArgs) {
return [{ title: loaderData ? `${loaderData.product.name} | Shop` : "Product not found" }];
}
export default function Product({ loaderData }: Route.ComponentProps) {
const { product, user } = loaderData;
return (
<main>
<h1>{product.name}</h1>
<p>${product.price} · {product.likes} likes · viewing as {user?.name}</p>
<Form method="post">
<button type="submit">Like</button>
</Form>
</main>
);
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
return <h1>{(error as { status?: number }).status === 404 ? "Not found" : "Error"}</h1>;
}What the tests showed
- Type generation caught a real bug. My first
metafunction usedloaderData.product.namedirectly.npm run typecheck(which runsreact-router typegen && tsc) failed with “‘loaderData’ is possibly ‘undefined'”, becausemetaalso runs when the loader throws a 404. After adding the check, it passed with no errors. - Server rendering:
/products/1?as=Snehasishreturned HTML with<title>Mechanical keyboard | Shop</title>and “viewing as Snehasish”, set by the middleware through the typed context. - Real 404s:
/products/999returned HTTP status 404 with the “Not found” error boundary and the fallback title, which is what search engines need. - Actions: a POST to
/products/1incremented the like count shown on the next request.
Framework mode server-renders by default (ssr: true in react-router.config.ts); set it to false for SPA mode, or use prerender to generate static HTML at build time. See the middleware guide for more patterns such as authentication and logging. If you want the background on why server rendering matters, read how server-side rendering works in React.
How to Upgrade from React Router v7 to v8
The official upgrade guide recommends adopting the future flags one at a time while still on v7, then bumping the version:
- Update the platform first: React 19.2.7+, Node 22.22+ and, for framework mode, Vite 7+.
- Swap imports: replace
react-router-domwithreact-router, and importRouterProvider/HydratedRouterfromreact-router/dom. Then uninstallreact-router-dom. - Enable the v8 future flags on v7 one by one and fix anything that breaks:
v8_middleware,v8_passThroughRequests,v8_trailingSlashAwareDataRequests,v8_splitRouteModulesandv8_viteEnvironmentApi. - Fix deprecated APIs: use
loaderDatainstead ofdatainmetafunctions; make customgetLoadContextfunctions return aRouterContextProvider; replace the Cloudflare dev proxy with@cloudflare/vite-plugin. - Bump the packages (
react-router,@react-router/devand any adapters) to v8, remove the now-unnecessary future flags from your config, then run your typecheck and tests.
npm uninstall react-router-dom
npm install react-router@8
npm install -D @react-router/dev@8 # framework mode onlyUpgrading from v6
Go to v7 first, using the v6 future flags and the v6 upgrade guide, then follow the steps above. React Router v6 and Remix v2 are end-of-life and no longer receive security fixes, so plan the move even if your app works today.
Should You Upgrade to React Router v8 Now?
- New projects: yes, start on v8.
- v7 apps already on React 19: yes; if you have adopted the future flags, it is a small change, and v8 is where fixes land.
- v7 apps still on React 18 or older Node: plan the React and Node upgrades first; those are the real work.
- v6 apps: upgrade to v7 now; v6 no longer gets security updates.
Conclusion
React Router v8 is a maintenance-focused major release: one package instead of two, middleware and the other v7 future flags always on, ESM only, and modern React, Node and Vite baselines. Everything that made v7 significant, including the three modes, loaders and actions, framework mode from Remix and type generation, carries straight over. If you’re on v7 with React 19, enable the future flags, swap your imports and bump the version; if you’re on v6, now is the time to move.
FAQs
What is the latest version of React Router?
React Router 8. Version 8.0.0 was released on 17 June 2026, and 8.5.0 was released on 19 October 2026. Install it with npm install react-router.
What changed in React Router v8?
The react-router-dom package was removed, v7 future flags such as middleware became the default, the package became ESM-only, the meta data field was removed in favour of loaderData, and the minimum versions rose to React 19.2.7, Node 22.22 and Vite 7.
Is React Router v8 a breaking change?
Technically yes, but deliberately small. If your v7 app already uses react-router imports, has the v8 future flags enabled and meets the new React, Node and Vite minimums, upgrading is mostly a version bump.
Is react-router-dom deprecated?
It was removed in React Router v8. In v7 it only re-exported react-router. Import from react-router, and from react-router/dom for RouterProvider and HydratedRouter.
What are the three modes in React Router?
Declarative mode (BrowserRouter and Routes), data mode (createBrowserRouter with loaders and actions) and framework mode (the Vite plugin with route modules, server rendering and type generation).
Is Remix the same as React Router now?
Remix v2’s framework features were merged into React Router v7 as framework mode, so Remix v2 apps upgrade to React Router. Remix v2 itself is end-of-life.
Does React Router v8 support React 18?
No. React Router v8 requires React 19.2.7 or later. Stay on React Router v7 until you can upgrade React.








