Skip to content

Web Development · Frameworks

React Router v8, Two Months In: What Actually Breaks When You Upgrade

React Router v8 shipped in June as a deliberately boring release. Here's what the version bump actually requires, what the codemod handles for you, and the three things that still catch teams off guard.

Abhishek Gupta

Abhishek Gupta

5 min read

React Router v8, Two Months In: What Actually Breaks When You Upgrade

Sponsored

Share

React Router v8 came out in June, and the team’s own description of it was “deliberately boring.” No new routing paradigm, no rewritten data layer, just a version bump that finishes migrations the project had been telegraphing through future flags for over a year. Two months on, that framing holds up. The people getting burned by this upgrade aren’t hitting new APIs they don’t understand, they’re hitting old assumptions that quietly stopped being true.

If you’re planning the upgrade now, here’s what actually changes and where teams are losing time.

The version floor moved, and there’s no soft landing

v8 raises three minimums at once, and none of them are negotiable:

Requirementv7v8
Node.js18+22.12+
React18+19.2.5+
Vite (if used)5+7+

Node 20 gets dropped because it reaches end of life in April 2026, and the team didn’t want to ship a major version with a runtime floor that expires within the year. React 18 support is gone because keeping it around meant maintaining compatibility shims for React 19-only behavior, and the team decided that cost wasn’t worth it once 19 had been stable for a while. If your app is still on React 18 for reasons unrelated to routing, this is the release that forces the conversation, not a routing bug.

The Vite requirement is a side effect of a specific default flip: v8_viteEnvironmentApi, previously opt-in, is now always on, and it needs Vite 7’s Environment API to function. Projects still pinned to Vite 6 for an unrelated plugin compatibility reason hit this immediately.

react-router-dom is actually gone

This is the change that breaks builds outright rather than subtly. In v7, react-router-dom stuck around as a re-export layer so existing imports kept working while people migrated at their own pace. In v8 it’s removed. Anything DOM-specific, BrowserRouter, Link, useSearchParams used in a browser context, now comes from react-router/dom:

// v7 (also worked in earlier versions via react-router-dom)
import { BrowserRouter, Link } from "react-router-dom";

// v8
import { BrowserRouter } from "react-router/dom";
import { Link } from "react-router";

The split matters because not every export moved to the same place. Link and most hooks live in the base react-router package now since they’re not actually DOM-specific, while things like BrowserRouter and HydratedRouter that genuinely depend on the browser environment live in react-router/dom. A find-and-replace that swaps every react-router-dom import for react-router/dom wholesale will compile, but it’s the wrong fix for half the imports, and it’s worth actually reading which exports moved where rather than assuming a 1:1 swap.

The build is ESM-only now

Node 20.19+ and 22.12+ both support require(esm) without a flag, which is what let the team drop CommonJS builds entirely rather than maintaining dual output. For most app code this is invisible. For build tooling and internal packages that still require() React Router directly instead of importing it, this is where CI breaks. If your monorepo has an older internal tool or a Jest config that hasn’t been touched in a while doing a raw require("react-router"), that’s the failure to look for first, before assuming the upgrade itself is broken.

What the codemod actually covers

The migration story depends heavily on whether you did the prep work in v7. React Router ships new behavior behind future flags one major version ahead of making it default, specifically so teams can adopt changes incrementally instead of all at once during the major bump. If you were already running v7 with v8_middleware, v8_viteEnvironmentApi, and the other future flags enabled, the v8 codemod handles the bulk of the mechanical changes: import paths, a handful of renamed APIs, config shape updates.

npx react-router-codemod v8-migration ./app

If you skipped the future flags, you’re doing the same amount of underlying work, adjusting to middleware’s new default behavior, the Vite Environment API, the import path changes, just without a tool automating most of it, because the codemod’s transforms assume you’re starting from the flagged state. The honest recommendation for a large app on v6 or early v7: upgrade to the latest v7 first, turn on every future flag, fix what breaks in that smaller step, then do the v8 bump. It’s more commits, but each one is small enough to actually review.

Where the silent breakage hides

Two defaults flipped in this release, and neither one throws an error when it changes your app’s behavior:

  • Middleware went from opt-in to default. If your loaders or actions had code that looked like it was doing middleware-shaped work without the flag turned on, it now runs through an actual middleware pipeline instead of whatever ad hoc pattern you had before, and the execution order can differ.
  • The Vite Environment API is now load-bearing rather than optional. Custom Vite plugins that patched around the old dev-server behavior sometimes conflict with the new default rather than failing to load.

Both of these pair well with a broader monorepo tooling audit if you’re touching build config anyway, since a Vite major bump tends to surface other stale plugin pins in the same pass.

Should you upgrade now

If you’re building something new, start on v8, there’s no reason to intentionally build on a version with a shorter support runway. If you’re maintaining an existing app on v6, the honest path is v7 with future flags first, not a direct jump. If you’re already on current v7, the upgrade is close to what the team promised: boring, mostly automated, and worth doing before Node 20’s April EOL forces the runtime question anyway.

The team has said they’re aiming for an annual major release cadence going forward, which is a reasonable trade if each release stays this contained. One boring, well-telegraphed major version a year beats the alternative most frameworks have settled into: infrequent majors that break everything at once because nobody wanted to ship a small one.

Frequently asked questions

What are the minimum versions required for React Router v8?
Node.js 22.12 or later, React 19.2.5 or later, and Vite 7 or later if you're using the Vite plugin. React Router dropped Node 20 support because it reaches end of life in April 2026, and dropped React 18 to reduce internal complexity and use React 19-only features directly.
Do I need to migrate off react-router-dom?
Yes. react-router-dom existed in v7 purely as a re-export for migration convenience, and it's removed entirely in v8. DOM-specific exports (BrowserRouter, Link, and the rest) now come from react-router/dom, while everything else imports from react-router directly.
Is the v7 to v8 migration actually automated?
Mostly, if you did the prep work. The React Router team ships upgrades through future flags in the prior major version, so anyone running v7 with the future flags already enabled gets most of the migration handled by a codemod. Teams that skipped the future flags are doing the same migration work, just without the tooling doing it for them, and should expect it to take longer.
What changed with middleware in v8?
Middleware, previously gated behind the v8_middleware future flag, is now the default behavior. If your app defined middleware-shaped code without opting in via the flag, or relied on the pre-middleware request lifecycle, this is one of the most common sources of subtle behavior differences after upgrading, not a hard error, just requests running through a different pipeline than before.
Are React Router v6 and Remix v2 still supported?
No, both have reached end of life. Remix's framework-mode ideas were folded into React Router v7 and now v8 as one of its three router modes, so a Remix v2 app migrating forward is really migrating to React Router's Framework Mode rather than to a separate product.

Sources

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored