Scientyfic World

React Router 8 Explained: What’s New and How to Upgrade From React Router 7

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...

Share:

Get an AI summary of this article

react router v7 blog banner

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 v7React Router v8
ReleasedNovember 202417 June 2026
Packagesreact-router (plus react-router-dom as a re-export for v6 users)react-router only; DOM pieces from react-router/dom
Minimum React1819.2.7
Minimum Node (framework mode)2022.22
Vite (framework mode)5.1+ at launch7+ (Environment API always on)
MiddlewareOpt-in with future.v8_middlewareAlways on
Split route modulesOpt-in flagOn by default (splitRouteModules)
Module formatESM and CommonJSESM only
Route meta datadata (deprecated) or loaderDataloaderData only
Support statusPrevious majorCurrent; 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-dom is gone. Import RouterProvider and HydratedRouter from react-router/dom, and everything else from react-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 context passed to loaders, actions and middleware is always a RouterContextProvider, and custom server getLoadContext functions 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 splitRouteModules option that is on by default.
  • Removed deprecated APIs: the data field in meta functions (use loaderData), the internal hasErrorBoundary route 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

ModeHow you set it upFeaturesUse it for
Declarative<BrowserRouter>, <Routes>, <Route>Matching, links, params, navigationSimple SPAs, or when another tool handles data
DatacreateBrowserRouter and <RouterProvider>Adds loaders, actions, pending states, error elements, middlewareSPAs that want data loading tied to routes
FrameworkThe React Router Vite plugin, routes.ts and route modulesAdds server rendering, pre-rendering, type generation, code splittingFull-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 dev

Routes 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 meta function used loaderData.product.name directly. npm run typecheck (which runs react-router typegen && tsc) failed with “‘loaderData’ is possibly ‘undefined'”, because meta also runs when the loader throws a 404. After adding the check, it passed with no errors.
  • Server rendering: /products/1?as=Snehasish returned HTML with <title>Mechanical keyboard | Shop</title> and “viewing as Snehasish”, set by the middleware through the typed context.
  • Real 404s: /products/999 returned HTTP status 404 with the “Not found” error boundary and the fallback title, which is what search engines need.
  • Actions: a POST to /products/1 incremented 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:

  1. Update the platform first: React 19.2.7+, Node 22.22+ and, for framework mode, Vite 7+.
  2. Swap imports: replace react-router-dom with react-router, and import RouterProvider/HydratedRouter from react-router/dom. Then uninstall react-router-dom.
  3. Enable the v8 future flags on v7 one by one and fix anything that breaks: v8_middleware, v8_passThroughRequests, v8_trailingSlashAwareDataRequests, v8_splitRouteModules and v8_viteEnvironmentApi.
  4. Fix deprecated APIs: use loaderData instead of data in meta functions; make custom getLoadContext functions return a RouterContextProvider; replace the Cloudflare dev proxy with @cloudflare/vite-plugin.
  5. Bump the packages (react-router, @react-router/dev and 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 only

Upgrading 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.

Mayukh Samadder
Lead AI Engineer @Cars24 | Ex-Gupsup
Connect with Mayukh Samadder

On This page

Take a Pause with Intervals

A Sunday letter on building, writing, and thinking deeper as a developer — short, honest, and worth your time.

Snehasish Konger profile photo

"Hey there — I'm Snehasish. Hope this post saved you some head-scratching time! I've spent years turning technical chaos into clarity, and I'm here to be your guide through the maze of modern tech. Stick around for more lightbulb moments — we're just getting started."

Related Posts