React RouterReact 19TypeScriptMigration GuideFrontend Engineering

React Router v7 升級指南:v6 無痛遷移與 Typegen 導入邊界

整理 React Router v6 到 v7 的 non-breaking 升級流程:future flags、import 遷移到 react-router/react-router/dom、回歸驗證,再說明 Typegen 與 Framework adoption 的導入前提。

· 5 分鐘閱讀

你會拿到一份可回滾的兩階段升級流程,先安全上 v7,再決定要不要進一步做 route type safety 與 Framework adoption。


前言

React Router v7 常被講成「升級很簡單」,但團隊實作時最容易踩到的坑其實不是版本號,而是把不同層級的變更混在一起。像是 package 升級、import 遷移、typegen、route module 重構、Framework Mode 導入,這些工作量和風險都不同。

如果你把它們包裝成一條「低風險一次完成」路線,PR 會瞬間失控,回歸點也難追。這篇的核心就是把邊界切清楚:先做官方定義的 non-breaking upgrade,再把 typegen 與 Framework 能力當作第二階段專案。

文中範例都以 React Router 7.13.2(2026-04-01)為基準,並對應官方 Upgrading from v6Route Module Type Safety 流程。


Phase 1:只做 non-breaking 升級(v6 -> v7)

先定義「non-breaking」在本文的範圍:

  1. 升到最新 v6 minor 並打開 future flags
  2. 升 v7 套件
  3. 把 import 由 react-router-dom 收斂到 react-router(含 DOM deep import)
  4. 驗證既有行為不變

不包含 route module 大改、不包含 Vite plugin、不包含 SSR/SSG 轉換。

Step 0:先確認最低版本前提

node -v
npm ls react react-dom react-router-dom

React Router v7 官方最低要求是 node@20react@18react-dom@18。如果你還沒到這個基線,先處理基礎升級,不要直接跳 v7。

Step 1:先在 v6 打開 future flags

// Data Router 範例(createBrowserRouter)
const router = createBrowserRouter(routes, {
  future: {
    v7_relativeSplatPath: true,
    v7_startTransition: true,
    v7_fetcherPersist: true,
    v7_normalizeFormMethod: true,
    v7_partialHydration: true,
    v7_skipActionErrorRevalidation: true,
  },
});

這一步的目的不是「新功能」,而是先把 v7 行為差異搬到 v6 階段處理,讓每個變更都可獨立回滾。

Step 2:升級到 v7,再處理 package 收斂

# 先升級到 v7
npm install react-router-dom@latest

# 驗證穩定後,再收斂成 react-router 單一套件
npm uninstall react-router-dom
npm install react-router@latest

react-router-dom -> react-router 不建議跟前一步混在同一個大 PR。分開做,比較容易定位問題。

Step 3:精準遷移 import,而不是一次全域替換

// before
import { Link, Routes, Route, useNavigate } from "react-router-dom";
import { RouterProvider } from "react-router-dom";

// after
import { Link, Routes, Route, useNavigate } from "react-router";
// 注意:DOM 綁定 API 要走 deep import
import { RouterProvider } from "react-router/dom";

RouterProviderHydratedRouter 這類依賴 react-dom 的 API 要走 react-router/dom。如果你在 Jest/非 DOM 情境測試,才用 top-level import。

Step 4:按 router 類型做風險驗證

npm run typecheck
npm test
npm run build

另外建議加兩類針對性檢查:

  • component routes:特別檢查 dashboard/* 這種 multi-segment splat 的相對連結
  • data routers:檢查 formMethod 比對大小寫、fetcher 生命週期、action error 後 revalidation

Phase 2:Typegen 是「型別化專案」,不是 v6->v7 升級附贈

很多文章把 typegen 包成 v7 升級自帶收益,這個說法不精準。typegen 能成立,需要你先具備 route config 與對應 tooling 流程;這已經是第二階段工程。

Typegen 前提清單(至少要滿足這些)

  1. 專案路由結構可產生 +types(通常搭配 React Router dev tooling)
  2. tsconfig 能吃 .react-router/types
  3. CI 有固定的 typegen 步驟
  4. 團隊接受 route module 型別約束(Route.LoaderArgs 等)

Step 1:設定 tsconfig 與 gitignore

{
  "include": ["src", ".react-router/types/**/*"],
  "compilerOptions": {
    "rootDirs": [".", "./.react-router/types"],
    "strict": true
  }
}
.react-router/

Step 2:把 typegen 變成 CI gate

{
  "scripts": {
    "typegen": "react-router typegen",
    "typecheck": "react-router typegen && tsc --noEmit"
  }
}

如果你只靠本地 react-router dev 自動生型別,CI 很容易出現「本地過、雲端掛」的不一致。

Step 3:在 route module 吃 Route.* 型別

// src/routes/product.tsx
import type { Route } from "./+types/product";

export async function loader({ params }: Route.LoaderArgs) {
  // params 會從路由定義推導出正確鍵名
  const res = await fetch(`/api/products/${params.productId}`);
  if (!res.ok) throw new Response("Not Found", { status: 404 });
  return (await res.json()) as { name: string; price: number };
}

export default function ProductPage({ loaderData }: Route.ComponentProps) {
  return (
    <section>
      <h1>{loaderData.name}</h1>
      <p>NT$ {loaderData.price}</p>
    </section>
  );
}

這個階段的成果是 compile-time 保護,不是版本升級本身。請把 KPI 分開看,才不會錯估工期。


Framework adoption:第三層決策,獨立評估

官方文件把 Framework Adoption(不論從 Component Routes 或 RouterProvider 進入)定義成另一條遷移路徑,常見工作包含:

  1. 把路由定義移到 route modules
  2. 安裝 @react-router/dev Vite plugin
  3. 新增 react-router.config.ts
  4. 調整 entry/root 結構(例如 root.tsxentry.client.tsx

這些都不該被描述成「v6 -> v7 升級順手做」。更合理的做法是另開 epic,選 1~2 條路由先試點,再決定是否全面 rollout。


常見問題 / 注意事項

Q1:文章標題寫「低風險升級」,那 typegen 到底算不算低風險?

只在前提明確、拆成獨立 phase 時可以說「可控風險」。若把 typegen + framework adoption 和版本升級綁一起,就不算低風險。

Q2:可以直接把 react-router-dom 全域取代成 react-router 嗎?

不建議直接盲替換。至少要先盤點 DOM 專用 API,確認 react-router/dom deep import 是否正確。

Q3:Component Routes 與 Data Routers 的升級注意點一樣嗎?

不一樣。Data Routers 要額外檢查 future flags(fetcher、form method、revalidation),Component Routes 主要看路徑匹配與相對導覽。

Q4:什麼時候該考慮 Framework adoption?

當你已經完成 v7 穩定運行,且團隊確定要投資 route module、型別路由、SSR/預渲染策略時,再進入下一階段。


總結

React Router v7 的低風險升級,真正可承諾的只有 Phase 1:版本與 import 遷移加上行為驗證。typegen 與 Framework adoption 都是「可後續導入」的工程計畫,不是同一層成本。

實作上最穩的路線是:

  1. 先在 v6 打開 future flags 並修完警告
  2. 升 v7、完成 import 收斂、跑回歸
  3. 另開 Phase 2 導入 typegen(含 CI gate)
  4. 視需求再評估 Framework adoption

把邊界講清楚,你就能同時保住交付速度和升級品質。