React Router v6 から Tanstack Router v1に移行して感じたメリット
作成日時: 2025-11-19
はじめに
TanStack Routerは、型安全性を重視した次世代のReactルーターで、ファイルベースルーティング、自動コード分割、完全な型推論など、モダンなReactアプリケーション開発に必要な機能を提供しています。
この記事では、実際にReact Router v6からTanStack Router v1へ移行した際に感じたメリットについて、具体的なコード例とともに紹介します。
なお、この記事内で特に断りがない限り、React Routerはv6、TanStack Routerはv1を指しています。
Automatic Code Splitting
TanStack Routerは、ルート単位での自動コード分割をサポートしています。React Routerでコード分割を実現するには、React.lazyとSuspenseを手動で設定する必要がありましたが、TanStack Routerでは設定ファイルに記述するだけで自動的に処理されます。
React Router の場合
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
);
}
TanStack Router (File-Based Routing) の場合
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
target: 'react',
autoCodeSplitting: true,
}),
],
});
この設定により、各ルートのコンポーネントが自動的に分割され、必要になったタイミングで読み込まれます。
File-Based Routing
TanStack Routerは、ファイルベースルーティング、コードベースルーティングどちらにも対応しており、今回の移行ではファイルベースルーティングを採用しました。React Routerのコードベースルーティングでは、ルーティングがネストしている場合、ページのURLから該当のコンポーネントを辿るのに少々難があるという課題がありました。ファイルベースルーティングを採用することで、URLとファイルのパスが一致するため、この課題が解決しました。
React Router の場合
React Routerでは、ルート定義をコードで明示的に記述する必要があります。
<Routes>
<Route path="/" element={<Root />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="posts" element={<Posts />} />
<Route path="posts/:postId" element={<Post />} />
<Route path="users/:userId/profile" element={<UserProfile />} />
</Route>
</Routes>
TanStack Router の場合
TanStack Routerでは、ファイルシステムの構造から自動的にルート定義を生成できます。移行したプロジェクトではviteを利用しているため、こちらの設定をすることで、vite devで開発サーバを起動しておくと、src/routes/配下の変更を検知して自動的にルート定義ファイルを生成してくれるようになります。
ディレクトリ構造の設計については公式ドキュメントに詳しくルールが記載されているので、そちらを参照すれば問題なくできたのですが、1点だけハマった点があるので紹介します。
それはindexの扱いです。公式ドキュメントではルートのパスにposts.tsxやabout.tsxの例があったので、同じように設計していたのですが、ネストされたパスを実装した時に意図しないコンポーネントアウトプットになっていました。少し分かりづらいので、indexの有無によるディレクトリ構造を例示しながら説明します。
ディレクトリ構造の例 (index無し)
src/routes/
├── __root.tsx # <Root>
├── index.tsx # <Root><RootIndex>
├── posts.tsx # <Root><Posts>
├── posts.$postId.tsx # <Root><Posts><Post>
└── posts.$postId.edit.tsx # <Root><Posts><Post><EditPost>
ディレクトリ構造の例 (index有り)
src/routes/
├── __root.tsx # <Root>
├── index.tsx # <Root><RootIndex>
├── posts.index.tsx # <Root><PostsIndex>
├── posts.$postId.index.tsx # <Root><PostIndex>
└── posts.$postId.edit.index.tsx # <Root><EditPostIndex>
最初はindex無しのディレクトリ構造で設計していたのですが、コンポーネントアウトプットを見ると、他のルートがレイアウトとして振る舞っていることに気づきました。なのでレイアウト以外には必ずindexを付ける設計に変更しました。
Routeの型付け
TanStack Routerでは、Path、Path params、Search paramsの全てにおいて型付けが可能です。これにより、ルーティングに関連する全ての要素で型安全性が保証され、コンパイル時にエラーを検出できるようになります。
Pathの型付け
TanStack Routerでは、ルートパスに対して完全な型推論が効きます。これにより、存在しないパスへのナビゲーションをコンパイル時に検出できます。
React Router の場合
import { useNavigate } from "react-router-dom";
function MyComponent() {
const navigate = useNavigate();
// タイポしても気づけない
navigate("/posst/123"); // コンパイルエラーにならない
}
TanStack Router の場合
import { useNavigate } from "@tanstack/react-router";
function MyComponent() {
const navigate = useNavigate();
// タイポするとコンパイルエラー
navigate({ to: "/posst/$postId", params: { postId: "123" } }); // エラー: '/posst/$postId' は存在しません
// 正しいパスのみ許可される
navigate({ to: "/posts/$postId", params: { postId: "123" } }); // OK
}
自動生成された型定義により、存在するルートパスのみが補完され、タイポや存在しないパスへの遷移を防げます。
Path paramsの型付け
React Routerで、URL(/posts/:postId)からパラメータ:postIdを取得する場合は以下のように書きます。
const { postId } = useParams();
このときpostIdはstring | undefined型となり、型付けもされていないため補完が効きません。
TanStack Routerの場合は、
export const Route = createFileRoute("/posts/$postId/")({
component: Post,
}); // 自動生成
function Post() {
const { postId } = Route.useParams();
}
と書けて、このときpostIdはstringになります。またzodを利用すると、postIdをnumberでvalidateすることもできます。
export const Route = createFileRoute("/posts/$postId/")({
component: Post,
params: {
parse: (params) => ({ postId: z.coerce.number().parse(params.postId) }),
},
});
function Post() {
const { postId } = Route.useParams();
}
この時、postIdはnumber型となり、パースに失敗するとエラーがthrowされます。
Search paramsの型付け
クエリパラメータ(Search params)についても、TanStack Routerでは完全な型安全性が提供されます。
React Router の場合
import { useSearchParams } from "react-router-dom";
function PostList() {
const [searchParams] = useSearchParams();
// 型が付いていないため、補完が効かない
const page = searchParams.get("page"); // string | null
const sort = searchParams.get("sort"); // string | null
}
TanStack Router の場合
export const Route = createFileRoute("/posts/")({
component: PostList,
validateSearch: z.object({
page: z.number().optional().default(1),
sort: z.enum(["asc", "desc"]).optional().default("desc"),
}),
});
function PostList() {
const { page, sort } = Route.useSearch();
// page: number
// sort: 'asc' | 'desc'
}
zodスキーマを使ってバリデーションと型定義を同時に行えるため、不正なクエリパラメータを防ぎつつ、型安全なコードが書けます。また、デフォルト値も設定できるため、undefinedチェックが不要になります。
URL作成の簡略化
TanStack Routerでは、URLを文字列として組み立てる必要がなく、オブジェクト形式で型安全にURLを構築できます。
React Router の場合
import { Link } from 'react-router-dom';
function PostCard({ postId }: { postId: number }) {
// 文字列として組み立てる必要がある
return (
<Link to={`/posts/${postId}?sort=desc&page=1`}>
View Post
</Link>
);
}
TanStack Router の場合
import { Link } from '@tanstack/react-router';
function PostCard({ postId }: { postId: number }) {
// オブジェクト形式で型安全に記述
return (
<Link
to="/posts/$postId"
params={{ postId }}
search={{ sort: 'desc', page: 1 }}
>
View Post
</Link>
);
}
パラメータとクエリパラメータが明確に分離され、型チェックも効くため、タイポやパラメータの渡し忘れを防げます。また、URLエンコードなども自動的に処理されます。
Data Loading
TanStack Routerには、ルートの読み込み前に実行されるbeforeLoadやloaderフックがあります。これを使うと、認証チェックやデータの事前読み込みなどを型安全に実装できます。
認証チェックの例
export const Route = createFileRoute("/dashboard/")({
component: Dashboard,
beforeLoad: async ({ context }) => {
// 認証チェック
if (!context.auth.isAuthenticated) {
throw redirect({ to: "/login" });
}
},
});
データの事前読み込み
export const Route = createFileRoute("/posts/$postId/")({
component: Post,
loader: async ({ params }) => {
const post = await fetchPost(params.postId);
return { post };
},
});
function Post() {
const { post } = Route.useLoaderData();
// post のデータが保証されている
}
React Routerのloaderに似た機能ですが、TanStack Routerでは型推論が効くため、返り値の型がコンポーネント側で自動的に推論されます。これにより、データの存在が保証され、安全にデータを扱えます。
まとめ
React Router v6からTanStack Router v1への移行で感じた主なメリットをまとめます。
型安全性の大幅な向上
- パスの型チェック: 存在しないルートへの遷移をコンパイル時に検出
- パラメータの型推論: Path paramsとSearch paramsが完全に型付けされる
-
データの型保証:
loaderの返り値がコンポーネントで自動推論される
開発体験の改善
- ファイルベースルーティング: ファイル構造がそのままルート構造になり、直感的
- 自動コード分割: 簡単な設定だけで各ルートが自動的に分割される
- URL構築の簡略化: オブジェクト形式で型安全にURLを作成できる
TypeScriptとの親和性
TanStack Routerは、TypeScriptファーストで設計されており、型推論が非常に強力です。これにより、コード補完が効きやすく、リファクタリングも安全に行えます。
React Routerも優れたライブラリですが、TypeScriptを使ったプロジェクトで型安全性を重視するなら、TanStack Routerへの移行を検討する価値は十分にあると思います。
Discussion