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.

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}

DeclarativeDataFramework
Was previously {Trước đây là}RR v5/v6RR v6.4+Remix v2
Routes defined in {Định nghĩa route}JSX (<Route>)createBrowserRouterroutes.ts + file modules
Loaders / actions {Loader / action}
SSR / SSGDIY {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/amostly 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/:idparams.id is string, the loader’s return type flows into the component’s loaderData prop, with no manual generics {đị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}. 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à}:

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

  1. Collapse @remix-run/* dependencies into the single react-router (and @react-router/*) packages {Gộp các dependency @remix-run/* vào package react-router (và @react-router/*) duy nhất}.
  2. Replace @remix-run/* imports with react-router {Thay import @remix-run/* bằng react-router}.
  3. Update scripts: remix devreact-router dev, remix buildreact-router build {Cập nhật script}.
  4. Move the Vite plugin config from vite.config into react-router.config.ts, and adopt the new routes.ts format {Chuyển cấu hình Vite plugin sang react-router.config.ts, và dùng định dạng routes.ts mớ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)
Routingroutes.ts config + route modulesFile-system (app/ folders)
Data loading {Tải dữ liệu}loader / action (web-standard Request/Response)Server Components + fetch, Server Actions
RenderingSSR / SSG / SPA via one configRSC streaming, SSR, SSG, ISR, PPR
Type-safe routes {Route có type}✅ generated Route.* typesPartial (typed routes flag) {Một phần (cờ typed routes)}
Mental model {Mental model}Web fundamentals: requests, forms, responsesReact 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.