jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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

DeclarativeDataFramework
Trước đây làRR v5/v6RR v6.4+Remix v2
Định nghĩa routeJSX (<Route>)createBrowserRouterroutes.ts + file modules
Loader / action
SSR / SSGtự dựng✅ built-in
Route có type
Vite plugin
Bạn kiểm soát server/bundlern/aphầ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/:idparams.idstring, 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à:

  1. 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).
  2. 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:

  1. Gộp các dependency @remix-run/* vào package react-router (và @react-router/*) duy nhất.
  2. Thay import @remix-run/* bằng react-router.
  3. Cập nhật script.
  4. Chuyển cấu hình Vite plugin sang react-router.config.ts, và dùng định dạng routes.ts mớ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)
Routingroutes.ts config + route modulesFile-system (app/ folders)
Tải dữ liệuloader / action (web-standard Request/Response)Server Components + fetch, Server Actions
RenderingSSR / SSG / SPA via one configRSC streaming, SSR, SSG, ISR, PPR
Route có type✅ generated Route.* typesMột phần (cờ typed routes)
Mental modelWeb fundamentals: requests, forms, responsesRSC trước tiên
KhóaThấp — dựa trên chuẩn webCao 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ỏiTrả 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: