WAT Note(III).

TanStack Router入門:型安全ルーティングをハンズオンで体験する

Tatsuroh Wakasugi
Tatsuroh Wakasugi

今回は、前回の Tanstack Table に続き React アプリケーションのルーティングライブラリとして注目を集めている TanStack Router についてを取り上げます。

TanStack Router とは?

概要

TanStack Router は、TanStack が提供する React / Solid 向けの TypeScript ファーストのルーティングライブラリです。

主な特徴

特徴 説明
型推論ベースで非常に強い型安全性を持つ パス、パラメータ、検索クエリがすべて型チェックされる
ファイルベースルーティング Next.js のようなファイル構造でルートを自動生成
組み込みのデータローディング ルートレベルでのデータ取得をネイティブサポート
検索パラメータの状態管理 URL の検索パラメータを型安全に扱える
DevTools 開発時のデバッグを強力にサポート

React Router との違い

違いとしては主に以下があります。

React Router

  • 長い歴史と大きなコミュニティ
  • v6 で大幅に改善されたが、型安全性は限定的
  • 学習リソースが豊富

TanStack Router

  • TypeScript との親和性が非常に高い
  • パスの typo をコンパイル時に検出可能
  • 検索パラメータの型定義が容易
  • URL パラメータや検索クエリを型安全に扱える

環境構築

それでは、実際に TanStack Router を使ったハンズオンを実施してみましょう。

前提条件

  • Node.js 18 以上
  • npm または yarn または pnpm
  • TypeScript の基本的な知識

プロジェクトのセットアップ

# Viteでプロジェクトを作成
npm create vite@latest tanstack-router-demo -- --template react-ts

# プロジェクトディレクトリに移動
cd tanstack-router-demo

# TanStack Routerをインストール
npm install @tanstack/react-router

# 開発用のViteプラグインとルーター生成ツールをインストール
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools

ハンズオン:基本的なルーティングを実装する

Step 1: Vite 設定の更新

まず、vite.config.tsを編集して TanStack Router のプラグインを追加します。

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";

export default defineConfig({
  plugins: [TanStackRouterVite(), react()],
});

Step 2: ルートファイルの構造を作成

以下のようなディレクトリ構造を作成します。

src/
├── routes/
│   ├── __root.tsx      # ルートレイアウト
│   ├── index.tsx       # "/" のページ
│   ├── about.tsx       # "/about" のページ
│   └── posts/
│       ├── index.tsx   # "/posts" のページ
│       └── $postId.tsx # "/posts/:postId" のページ(動的ルート)
├── main.tsx
└── routeTree.gen.ts    # 自動生成される

Step 3: ルートレイアウトの作成

src/routes/__root.tsx を作ります。

このファイルは、アプリケーション全体のルートレイアウトを定義する特別なルートファイルです。

TanStack Router では、routes ディレクトリ内のファイル構造からルートツリーが自動生成されます。その中で __root.tsx は すべてのページの最上位に位置するルートになります。

このファイルでは、以下のような アプリ全体で共通の UI を定義することができます。

  • ナビゲーションバー
  • フッター
  • 共通レイアウト
  • DevTools
  • グローバルなコンテキスト
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";

export const Route = createRootRoute({
  component: () => (
    <>
      <nav
        style={{
          padding: "1rem",
          borderBottom: "1px solid #ccc",
          display: "flex",
          gap: "1rem",
        }}
      >
        <Link to="/" style={{ textDecoration: "none" }}>
          ホーム
        </Link>
        <Link to="/about" style={{ textDecoration: "none" }}>
          About
        </Link>
        <Link to="/posts" style={{ textDecoration: "none" }}>
          記事一覧
        </Link>
      </nav>
      <main style={{ padding: "1rem" }}>
        <Outlet />
      </main>
      <TanStackRouterDevtools />
    </>
  ),
});

Step 4: 各ページコンポーネントの作成

トップページとなる、src/routes/index.tsxを作ります。

import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
  component: HomePage,
});

function HomePage() {
  return (
    <div>
      <h1>TanStack Router デモへようこそ!</h1>
      <p>このサイトでTanStack Routerの基本的な使い方を学びましょう。</p>
    </div>
  );
}

About ページ(src/routes/about.tsx)を作ります。

import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/about")({
  component: AboutPage,
});

function AboutPage() {
  return (
    <div>
      <h1>About</h1>
      <p>TanStack Routerは、型安全なルーティングを提供するライブラリです。</p>
      <h2>主な特徴</h2>
      <ul>
        <li>TypeScriptファースト設計</li>
        <li>ファイルベースルーティング</li>
        <li>組み込みのデータローディング</li>
      </ul>
    </div>
  );
}

記事一覧ページ(src/routes/posts/index.tsx)を作ります。

import { createFileRoute, Link } from "@tanstack/react-router";

// サンプルデータ
const posts = [
  { id: 1, title: "TanStack Routerの基本" },
  { id: 2, title: "動的ルートの使い方" },
  { id: 3, title: "データローディングのベストプラクティス" },
];

export const Route = createFileRoute("/posts/")({
  component: PostsPage,
});

function PostsPage() {
  return (
    <div>
      <h1>記事一覧</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id} style={{ marginBottom: "0.5rem" }}>
            <Link
              to="/posts/$postId"
              params={{ postId: String(post.id) }}
              style={{ textDecoration: "none", color: "#0066cc" }}
            >
              {post.title}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Step 5: 動的ルート(パラメータ付き)の作成

src/routes/posts/$postId.tsxを作ります。

import { createFileRoute } from "@tanstack/react-router";

// サンプルデータ
const postsData: Record<string, { title: string; content: string }> = {
  "1": {
    title: "TanStack Routerの基本",
    content: "TanStack Routerは型安全なルーティングを実現します...",
  },
  "2": {
    title: "動的ルートの使い方",
    content: "$接頭辞を使ってパラメータを受け取ることができます...",
  },
  "3": {
    title: "データローディングのベストプラクティス",
    content: "loaderオプションを使用してデータを事前に取得できます...",
  },
};

export const Route = createFileRoute("/posts/$postId")({
  component: PostDetailPage,
});

function PostDetailPage() {
  // paramsは型安全! postIdが自動的に推論される
  const { postId } = Route.useParams();
  const post = postsData[postId];

  if (!post) {
    return <div>記事が見つかりませんでした</div>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      <p style={{ color: "#666", marginTop: "1rem" }}>記事ID: {postId}</p>
    </div>
  );
}

Step 6: main.tsx の設定

最後にsrc/main.tsxを作ります。

import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";

// 自動生成されるルートツリーをインポート
import { routeTree } from "./routeTree.gen";

// ルーターインスタンスを作成
const router = createRouter({ routeTree });

// 型安全のためのモジュール拡張
declare module "@tanstack/react-router" {
  interface Register {
    router: typeof router;
  }
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);

Step 7: 開発サーバーの起動

一連のページを作り終わったら、起動してみましょう。

npm run dev

これで、http://localhost:5173 でアプリケーションが起動します!

ホーム

About

記事一覧

記事(1 記事目)


型安全性を体験してみる

TanStack Router の最大の魅力は型安全性です。以下のコードをエディタで試してみてください。

存在しないパスやパラメータの指定漏れなど、誤った指定がある場合は、コンパイルエラーが発生します。

// 正しいパス - 型チェックOK
<Link to="/posts/$postId" params={{ postId: "1" }}>記事1</Link>

// 存在しないパス - コンパイルエラー!
<Link to="/nonexistent">存在しないページ</Link>

// パラメータの指定漏れ - コンパイルエラー!
<Link to="/posts/$postId">パラメータがない</Link>

IDE の補完機能を使うと、利用可能なルートが自動的にサジェストされるのも便利なポイントです。


検索パラメータの型安全な扱い

TanStack Router では、検索パラメータも型安全に扱えます。

※ 以下は Step4 の posts/index.tsx を検索パラメータ対応版に拡張した例です

// src/routes/posts/index.tsx を拡張
import { createFileRoute, Link } from "@tanstack/react-router";
import { z } from "zod";

// 検索パラメータのスキーマを定義
const postsSearchSchema = z.object({
  page: z.number().optional().default(1),
  filter: z.enum(["all", "published", "draft"]).optional().default("all"),
});

export const Route = createFileRoute("/posts/")({
  // 検索パラメータのバリデーション
  validateSearch: postsSearchSchema,
  component: PostsPage,
});

function PostsPage() {
  // 検索パラメータを型安全に取得
  const { page, filter } = Route.useSearch();

  return (
    <div>
      <h1>記事一覧</h1>
      <p>現在のページ: {page}</p>
      <p>フィルター: {filter}</p>

      {/* 検索パラメータ付きのリンク */}
      <Link to="/posts" search={{ page: 2, filter: "published" }}>
        2ページ目(公開済みのみ)
      </Link>
    </div>
  );
}

データローディング

ルートにローダーを定義して、ページ表示前にデータを取得することもできます。

// src/routes/posts/$postId.tsx
import { createFileRoute } from "@tanstack/react-router";

// 非同期でデータを取得する関数(実際はAPIコール)
async function fetchPost(postId: string) {
  // シミュレートされたAPI呼び出し
  await new Promise((resolve) => setTimeout(resolve, 500));
  return {
    id: postId,
    title: `記事 ${postId}`,
    content: "記事の内容...",
  };
}

export const Route = createFileRoute("/posts/$postId")({
  // ページ表示前にデータをロード
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId);
    return { post };
  },
  component: PostDetailPage,
});

function PostDetailPage() {
  // loaderのデータを取得(型安全!)
  const { post } = Route.useLoaderData();

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

まとめ

TanStack Router は、以下のような場面で特に威力を発揮します。ぜひ実際に利用してみて、型安全なルーティングの快適さを体験してみてください。

  • 大規模な TypeScript プロジェクト - 型安全性によりリファクタリングが安全に行える
  • 複雑な検索パラメータを扱うアプリ - フィルターやページネーションの管理が楽になる
  • チーム開発 - 型定義により、ルートの仕様が明確になる