React Router v7 — The Remix Merge & Its Three Modes Compared (2026)
A bilingual guide to React Router v7: how Remix merged into it, the three additive modes (Declarative, Data, Framework), type-safe routing, migrating from v6 and Remix v2, and how Framework mode stacks up against Next.js.
The Big News — Remix Is Now React Router {Tin lớn — Remix giờ là React Router}
For years there were two libraries from the same team {Nhiều năm qua có hai thư viện từ cùng một đội}: React Router (client routing) and Remix (a full-stack framework built on top of React Router) {React Router (routing phía client) và Remix (framework full-stack xây trên React Router)}. Over time Remix ported so many features back into React Router that the two became nearly identical — the only real gap was Remix’s Vite plugin {Theo thời gian Remix đẩy ngược quá nhiều tính năng vào React Router đến mức hai cái gần như giống hệt — khác biệt thật sự duy nhất là Vite plugin của Remix}.
So in v7 they merged {Nên ở v7 chúng hợp nhất}. React Router v7 is the next version after Remix v2 {React Router v7 là phiên bản kế tiếp sau Remix v2}. Everything ships in one package: react-router {Tất cả gói trong một package: react-router}.
The catch — and the genius — is that v7 doesn’t force the full framework on you {Điểm hay — và cũng khôn ngoan — là v7 không ép bạn dùng cả framework}. It offers three modes that are additive: each adds features at the cost of architectural control {Nó cung cấp ba mode cộng dồn: mỗi mode thêm tính năng nhưng đánh đổi bằng quyền kiểm soát kiến trúc}.
Mental model {Mental model}: pick a mode based on how much help vs. how much control you want {chọn mode dựa trên bạn muốn được hỗ trợ bao nhiêu so với muốn kiểm soát bao nhiêu}.
Three Modes at a Glance {Ba mode trong một cái nhìn}
| Declarative | Data | Framework | |
|---|---|---|---|
| Was previously {Trước đây là} | RR v5/v6 | RR v6.4+ | Remix v2 |
| Routes defined in {Định nghĩa route} | JSX (<Route>) | createBrowserRouter | routes.ts + file modules |
| Loaders / actions {Loader / action} | ❌ | ✅ | ✅ |
| SSR / SSG | ❌ | DIY {tự dựng} | ✅ built-in |
| Type-safe routes {Route có type} | ❌ | ❌ | ✅ |
| Vite plugin | ❌ | ❌ | ✅ |
| You control the server/bundler {Bạn kiểm soát server/bundler} | n/a | ✅ | mostly handled {phần lớn lo sẵn} |
The features are a superset chain {Tính năng là chuỗi siêu tập}: Framework ⊃ Data ⊃ Declarative {Framework ⊃ Data ⊃ Declarative}. Framework mode is just Data mode wrapped in a Vite plugin with conventions and codegen {Framework mode chỉ là Data mode bọc trong một Vite plugin kèm quy ước và codegen}.
Declarative Mode — The Classic SPA Router {Declarative Mode — Router SPA cổ điển}
This is React Router as most people have always known it {Đây là React Router mà hầu hết mọi người vẫn biết}: routes are JSX, everything runs client-side, no data abstractions {route là JSX, mọi thứ chạy phía client, không có lớp trừu tượng dữ liệu}.
import { BrowserRouter, Routes, Route, Link } from 'react-router';
function App() {
return (
<BrowserRouter>
<nav><Link to="/about">About</Link></nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<User />} />
</Routes>
</BrowserRouter>
);
}
Use it when {Dùng khi}: you want routing as simple as possible, you’re on a pure SPA, or your data layer already handles loading/pending states (e.g. TanStack Query, or a local-first sync engine) {bạn muốn routing đơn giản nhất có thể, bạn làm SPA thuần, hoặc lớp dữ liệu của bạn đã lo loading/pending (vd TanStack Query, hay engine sync local-first)}.
Skip it when {Bỏ qua khi}: you want the router to coordinate data fetching with navigation {bạn muốn router phối hợp việc fetch dữ liệu với điều hướng}.
Data Mode — Loaders, Actions, Fetchers {Data Mode — Loader, Action, Fetcher}
Data mode moves route configuration outside React’s render tree {Data mode đưa cấu hình route ra ngoài cây render của React}. That single change unlocks the router-driven data lifecycle: it can load data before rendering a route, handle mutations, and track pending states {Thay đổi nhỏ đó mở khóa vòng đời dữ liệu do router điều phối: nó có thể load dữ liệu trước khi render route, xử lý mutation, và theo dõi pending state}.
import {
createBrowserRouter,
RouterProvider,
useLoaderData,
Form,
} from 'react-router';
const router = createBrowserRouter([
{
path: '/users/:id',
// runs BEFORE the component renders — no loading spinner waterfall
loader: async ({ params }) => {
const res = await fetch(`/api/users/${params.id}`);
return res.json();
},
// handles <Form method="post"> submissions
action: async ({ request }) => {
const data = await request.formData();
return updateUser(data);
},
Component: UserPage,
},
]);
function UserPage() {
const user = useLoaderData(); // data is ready on first render
return (
<Form method="post">
<input name="name" defaultValue={user.name} />
<button>Save</button>
</Form>
);
}
export default () => <RouterProvider router={router} />;
The mental shift {Cú dịch chuyển tư duy}: instead of fetching inside a useEffect after the component mounts (which causes spinner waterfalls), the loader runs first and the component renders with data ready {thay vì fetch bên trong useEffect sau khi component mount (gây thác nước spinner), loader chạy trước và component render với dữ liệu đã sẵn sàng}. useFetcher handles mutations without navigating {useFetcher xử lý mutation mà không điều hướng}.
Use it when {Dùng khi}: you want loaders/actions but want to keep your own server, bundler, and deployment setup {bạn muốn loader/action nhưng muốn tự giữ server, bundler, và cách deploy của mình}.
Data mode and a server-cache library like TanStack Query solve overlapping problems {Data mode và một thư viện cache server như TanStack Query giải quyết các vấn đề chồng lấn}. Loaders couple data to routes; TanStack Query decouples it and adds caching/refetching. Many teams use one or the other, not both {Loader gắn dữ liệu với route; TanStack Query tách nó ra và thêm cache/refetch. Nhiều đội dùng cái này hoặc cái kia, không dùng cả hai}.
Framework Mode — The Full Remix Experience {Framework Mode — Trải nghiệm Remix đầy đủ}
Framework mode wraps Data mode in the @react-router/dev Vite plugin and adds conventions, code splitting, SSR/SSG/SPA strategies, and — the headline feature — type-safe routes {Framework mode bọc Data mode trong Vite plugin @react-router/dev và thêm quy ước, code splitting, chiến lược SSR/SSG/SPA, và — tính năng nổi bật — route có type}.
Routes are declared in a routes.ts file {Route được khai báo trong file routes.ts}:
// app/routes.ts
import { type RouteConfig, route, index } from '@react-router/dev/routes';
export default [
index('routes/home.tsx'),
route('about', 'routes/about.tsx'),
route('users/:id', 'routes/user.tsx'),
] satisfies RouteConfig;
Each route is a module that co-locates its loader, action, and component {Mỗi route là một module đặt chung loader, action, và component}:
// app/routes/user.tsx
import type { Route } from './+types/user';
// server-side loader
export async function loader({ params }: Route.LoaderArgs) {
const user = await db.user.find(params.id); // params.id is typed!
return { user };
}
// the component receives typed loaderData as a prop
export default function User({ loaderData }: Route.ComponentProps) {
return <h1>{loaderData.user.name}</h1>;
}
Rendering strategy is one config flag {Chiến lược render chỉ là một cờ config}:
// react-router.config.ts
import type { Config } from '@react-router/dev/config';
export default {
ssr: true, // false → SPA mode
// prerender: ['/', '/about'], // opt specific routes into SSG
} satisfies Config;
Use it when {Dùng khi}: you want an all-in-one, opinionated full-stack React framework, you’re coming from Remix, or you’re comparing Next.js / TanStack Start / SvelteKit / Astro {bạn muốn một framework React full-stack trọn gói, có chính kiến, bạn đến từ Remix, hoặc đang so sánh Next.js / TanStack Start / SvelteKit / Astro}.
Type Safety — The Real Upgrade {Type Safety — Nâng cấp thật sự}
Framework mode’s standout is generated, route-aware types {Điểm sáng của Framework mode là type được sinh ra, hiểu route}. The Vite plugin writes type files into .react-router/types/ based on your route config {Vite plugin ghi các file type vào .react-router/types/ dựa trên cấu hình route của bạn}. You import them with the +types convention {Bạn import chúng qua quy ước +types}:
import type { Route } from './+types/user';
// Route.LoaderArgs → params typed from the route path ('users/:id' → { id: string })
// Route.ComponentProps → { loaderData, actionData, params, ... } all typed
// Route.ActionArgs → typed request/params for the action
The chain is end-to-end {Chuỗi này thông suốt đầu-cuối}: define users/:id → params.id is string, the loader’s return type flows into the component’s loaderData prop, with no manual generics {định nghĩa users/:id → params.id là string, kiểu trả về của loader chảy thẳng vào prop loaderData của component, không cần generic thủ công}. This is the kind of safety Next.js still doesn’t give you out of the box for loaders {Đây là loại an toàn mà Next.js vẫn chưa cho sẵn với loader}.
Migrating to v7 {Di trú lên v7}
From React Router v6 {Từ React Router v6}
Mostly a drop-in {Phần lớn là thay thẳng}. The path is {Lộ trình là}:
- Upgrade to the latest v6 minor and enable the future flags (so you opt into v7 behavior gradually) {Nâng lên bản minor v6 mới nhất và bật các future flag (để dần chuyển sang hành vi v7)}.
- Bump to v7 and update imports — most code keeps working {Lên v7 và cập nhật import — phần lớn code vẫn chạy}.
// v6
import { useNavigate } from 'react-router-dom';
// v7 — react-router-dom re-exports from react-router; prefer:
import { useNavigate } from 'react-router';
From Remix v2 {Từ Remix v2}
This is the official “next version” path, and there are codemods to automate it {Đây là lộ trình “phiên bản kế tiếp” chính thức, và có codemod để tự động hóa}:
- Collapse
@remix-run/*dependencies into the singlereact-router(and@react-router/*) packages {Gộp các dependency@remix-run/*vào packagereact-router(và@react-router/*) duy nhất}. - Replace
@remix-run/*imports withreact-router{Thay import@remix-run/*bằngreact-router}. - Update scripts:
remix dev→react-router dev,remix build→react-router build{Cập nhật script}. - Move the Vite plugin config from
vite.configintoreact-router.config.ts, and adopt the newroutes.tsformat {Chuyển cấu hình Vite plugin sangreact-router.config.ts, và dùng định dạngroutes.tsmới}.
If you’re happy on Remix v2 today, you can stay — the move is “just a codemod that updates your imports” when you’re ready {Nếu bạn đang ổn với Remix v2, cứ ở lại — khi sẵn sàng, việc chuyển “chỉ là một codemod cập nhật import”}.
Framework Mode vs Next.js {Framework Mode vs Next.js}
Both are full-stack React frameworks; they differ in philosophy {Cả hai đều là framework React full-stack; khác nhau ở triết lý}:
| React Router v7 (Framework) | Next.js (App Router) | |
|---|---|---|
| Routing | routes.ts config + route modules | File-system (app/ folders) |
| Data loading {Tải dữ liệu} | loader / action (web-standard Request/Response) | Server Components + fetch, Server Actions |
| Rendering | SSR / SSG / SPA via one config | RSC streaming, SSR, SSG, ISR, PPR |
| Type-safe routes {Route có type} | ✅ generated Route.* types | Partial (typed routes flag) {Một phần (cờ typed routes)} |
| Mental model {Mental model} | Web fundamentals: requests, forms, responses | React Server Components first {RSC trước tiên} |
| Lock-in {Khóa} | Low — built on web standards {Thấp — dựa trên chuẩn web} | Higher — RSC + Next conventions {Cao hơn — RSC + quy ước Next} |
| Best fit {Hợp nhất} | Teams wanting standards + portability {Đội muốn chuẩn + tính di động} | Teams all-in on RSC + Vercel ecosystem {Đội all-in RSC + hệ Vercel} |
The honest summary {Tóm tắt thật lòng}: React Router v7 framework mode feels closer to the web platform (you work with Request/Response, FormData, real forms); Next.js App Router asks you to think in React Server Components first {Framework mode của React Router v7 gần với nền tảng web hơn (bạn làm việc với Request/Response, FormData, form thật); App Router của Next.js yêu cầu bạn nghĩ theo React Server Components trước}. See Next.js Core Concepts & Version Comparison for the Next side {Xem bài Next.js Core Concepts để rõ phía Next}.
Which Mode Should You Pick? {Nên chọn mode nào?}
Need SSR / SSG / type-safe routes / an all-in-one framework?
│ yes │ no
▼ ▼
FRAMEWORK MODE Need loaders/actions but own your server/bundler?
(was Remix v2) │ yes │ no
▼ ▼
DATA MODE DECLARATIVE MODE
(RR v6.4 data router) (classic SPA routing)
- Declarative — simple SPAs; data handled elsewhere {SPA đơn giản; dữ liệu xử lý nơi khác}.
- Data — want loaders/actions/fetchers, keep your own stack {muốn loader/action/fetcher, giữ stack riêng}.
- Framework — want the batteries-included, type-safe, SSR-capable framework {muốn framework trọn gói, có type, làm được SSR}.
Quick Reference {Tham chiếu nhanh}
| Question {Câu hỏi} | Answer {Trả lời} |
|---|---|
| Is Remix dead? {Remix chết chưa?} | No — it became React Router v7 {Không — nó trở thành React Router v7} |
| One package now? {Giờ một package?} | Yes — react-router |
| Loaders without a framework? {Loader mà không cần framework?} | Data mode |
| Type-safe params + loaderData? {Params + loaderData có type?} | Framework mode (generated +types) |
| Migrate from v6? {Di trú từ v6?} | Enable future flags → bump → fix imports |
| Migrate from Remix v2? {Di trú từ Remix v2?} | Codemod: deps + imports + routes.ts |
React Router v7 turned “a routing library” into “a routing library that can also be a full framework — your choice, incrementally” {React Router v7 biến “một thư viện routing” thành “một thư viện routing cũng có thể là một framework đầy đủ — tùy bạn, theo từng bước”}. Start declarative, grow into data, graduate to framework — same library the whole way {Bắt đầu declarative, lớn lên thành data, tốt nghiệp lên framework — cùng một thư viện suốt chặng đường}.
Related {Liên quan}: Next.js Core Concepts & Version Comparison · TanStack Query — The Complete Practical Guide · Astro Deep Dive.