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.
Tin lớn — Remix giờ là React Router
Nhiều năm qua có hai thư viện từ cùng một đội: React Router (routing phía client) và Remix (framework full-stack xây trên React Router). 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.
Nên ở v7 chúng hợp nhất. React Router v7 là phiên bản kế tiếp sau Remix v2. Tất cả gói trong một package: react-router.
Điểm hay — và cũng khôn ngoan — là v7 không ép bạn dùng cả framework. 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: 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.
Ba mode trong một cái nhìn
| Declarative | Data | Framework | |
|---|---|---|---|
| Trước đây là | RR v5/v6 | RR v6.4+ | Remix v2 |
| Định nghĩa route | JSX (<Route>) | createBrowserRouter | routes.ts + file modules |
| Loader / action | ❌ | ✅ | ✅ |
| SSR / SSG | ❌ | tự dựng | ✅ built-in |
| Route có type | ❌ | ❌ | ✅ |
| Vite plugin | ❌ | ❌ | ✅ |
| Bạn kiểm soát server/bundler | n/a | ✅ | phần lớn lo sẵn |
Tính năng là chuỗi siêu tập: Framework ⊃ Data ⊃ Declarative. Framework mode chỉ là Data mode bọc trong một Vite plugin kèm quy ước và codegen.
Declarative Mode — Router SPA cổ điển
Đây là React Router mà hầu hết mọi người vẫn biết: 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>
);
}
Dùng khi: 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).
Bỏ qua khi: bạn muốn router phối hợp việc fetch dữ liệu với điều hướng.
Data Mode — Loader, Action, Fetcher
Data mode đưa cấu hình route ra ngoài cây render của React. 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} />;
Cú dịch chuyển tư duy: 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 xử lý mutation mà không điều hướng.
Dùng khi: bạn muốn loader/action nhưng muốn tự giữ server, bundler, và cách deploy của mình.
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. 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 — Trải nghiệm Remix đầy đủ
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.
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;
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>;
}
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;
Dùng khi: 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 — Nâng cấp thật sự
Điểm sáng của Framework mode là type được sinh ra, hiểu route. 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. 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
Chuỗi này thông suốt đầu-cuối: đị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. Đây là loại an toàn mà Next.js vẫn chưa cho sẵn với loader.
Di trú lên v7
Từ React Router v6
Phần lớn là thay thẳng. Lộ trình là:
- 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).
- 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';
Từ Remix v2
Đây là lộ trình “phiên bản kế tiếp” chính thức, và có codemod để tự động hóa:
- Gộp các dependency
@remix-run/*vào packagereact-router(và@react-router/*) duy nhất. - Thay import
@remix-run/*bằngreact-router. - Cập nhật script.
- Chuyển cấu hình Vite plugin sang
react-router.config.ts, và dùng định dạngroutes.tsmới.
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
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) |
| 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 |
| Route có type | ✅ generated Route.* types | Một phần (cờ typed routes) |
| Mental model | Web fundamentals: requests, forms, responses | RSC trước tiên |
| Khóa | Thấp — dựa trên chuẩn web | Cao hơn — RSC + quy ước Next |
| Hợp nhất | Đội muốn chuẩn + tính di động | Đội all-in RSC + hệ Vercel |
Tóm tắt thật lòng: 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. Xem bài Next.js Core Concepts để rõ phía Next.
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)
- SPA đơn giản; dữ liệu xử lý nơi khác.
- muốn loader/action/fetcher, giữ stack riêng.
- muốn framework trọn gói, có type, làm được SSR.
Tham chiếu nhanh
| Câu hỏi | Trả lời |
|---|---|
| Remix chết chưa? | Không — nó trở thành React Router v7 |
| Giờ một package? | Yes — react-router |
| Loader mà không cần framework? | Data mode |
| Params + loaderData có type? | Framework mode (generated +types) |
| Di trú từ v6? | Enable future flags → bump → fix imports |
| Di trú từ Remix v2? | Codemod: deps + imports + routes.ts |
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”. 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.
Liên quan: