TanStack Routerの使い方

TanStack Routerとは?

TanStack Routerは、型安全で宣言的なルーターです。TanStack Queryなどの人気ライブラリを含むTanStackファミリーの一部になっています。TanStack Routerは、特にTypeScriptを前提に設計されており、既存のライブラリと比較して型づけをしやすくなっています。

React Routerとの違い

大きく違う箇所を挙げます。

ファイルベースのルーティング

React Routerでは、コード上でパスとコンポーネントの紐付けを行いました。

TanStack Routerでは、routes配下のパスで自動的にルーティングが決定します。

詳しくは後述します。

(React Router同様のコードベースのルーティングも可能、推奨はファイルベースです。)

型安全性の向上

パスやクエリパラメータに対し、簡単に型をつけることができます。

画像のように、定義済みのパスが補完で出てくるようになります。

TanStack Routerの使い方

TanStack RouterをViteプロジェクトに導入する手順です。

Viteが立ち上がっていない場合、

npm create vite@latest

でインストールをしてください。

インストール

まず、npmまたはyarnを使用してTanStack Routerをインストールします。

npm i @tanstack/react-router

Viteプラグインを設定する

ライブラリをインストールします。

npm i -D @tanstack/router-plugin @tanstack/router-devtools
npm i @vitejs/plugin-react

vite.config.tsにpluginsを追記します。

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

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    TanStackRouterVite(),
    viteReact(),
    // ...,
  ],
});

ルートの定義

次に、アプリケーションのルートを定義します。

src/routes/__root.tsxを作成します。

import React from 'react'
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'

export const Route = createRootRoute({
    component: () => (
        <>
            <div className="p-2 flex gap-2">
                <Link to="/" className="[&.active]:font-bold">
                    Home
                </Link>{' '}
                <Link to="/about" className="[&.active]:font-bold">
                    About
                </Link>
            </div>
            <hr />
            <Outlet />
            <TanStackRouterDevtools />
        </>
    ),
})

HomeとAboutのコンポーネントを作成します。

import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/')({
    component: Index,
})

function Index() {
    return (
        <div className="p-2">
            <h3>Welcome Home!</h3>
        </div>
    )
}
import { createLazyFileRoute } from '@tanstack/react-router'


export const Route = createLazyFileRoute('/about')({
    component: About,
})

function About() {
    return <div className="p-2">Hello from About!</div>
}

main.tsxを次のようにします。

import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'

// Import the generated route tree
import { routeTree } from './routeTree.gen'

// Create a new router instance
const router = createRouter({ routeTree })

// Register the router instance for type safety
declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}

// Render the app
const rootElement = document.getElementById('root')!
if (!rootElement.innerHTML) {
  const root = ReactDOM.createRoot(rootElement)
  root.render(
    <StrictMode>
      <RouterProvider router={router} />
    </StrictMode>,
  )
}

import { routeTree } from ‘./routeTree.gen’
(routeTree.genはnpm run devで自動でできます)

これで準備が整いました!

この状態で「npm run dev」でサーバーを起動させます。

ブラウザを開くと、

__root.tsx、index.lazy.tsxの内容が表示されています。

/aboutに遷移すると表示が切り替わります。

__root.tsxで定義したヘッダー部分

            <div className="p-2 flex gap-2">
                <Link to="/" className="[&.active]:font-bold">
                    Home
                </Link>{' '}
                <Link to="/about" className="[&.active]:font-bold">
                    About
                </Link>
            </div>

が共通で表示されています。

<Outlet />

で定義した部分には、src/routes/index.lazy.tsxやsrc/routes/about.lazy.tsxで定義したコンポーネントが差し込まれています。

ファイルベースルーティング

実際にルーティングを追加してみます。

src/routes/posts/index.tsx

を作成します。

ファイルを作成するだけで、下記が自動で反映されるはずです。

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/')({
    component: () => <div>Hello /posts/!</div>
})

ヘッダーにリンクを追加します。

                <Link to="/" className="[&.active]:font-bold">
                    Home
                </Link>{' '}
                <Link to="/about" className="[&.active]:font-bold">
                    About
                </Link>
                <Link to="/posts">
                    Posts
                </Link>

ちなみに、「to」の箇所には補完が効いています。

これは開発しやすいですね!

/postsに遷移して確認します。

パスパラメータ

src/routes/posts/$postId.tsx

を作成します。

するとこちらもまた、自動で内容が反映されるはずです。

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
    component: () => <div>Hello /posts/$postId!</div>
})

/posts/1にアクセス。

次に、Reactコンポーネント側でpostIdを取得します。

import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
    component: () => <PostComponent />
})

function PostComponent() {
    const { postId } = Route.useParams()
    return <div>Post {postId}</div>
}

Route.useParams()で、パスパラメータを取得できます。

クエリパラメータ

src/routes/index.lazy.tsxに、リンクを追加します。

import { createLazyFileRoute, Link } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/')({
    component: Index,
})

function Index() {
    return (
        <div className="p-2">
            <h3>Welcome Home!</h3>
            <Link
                to="/posts"
                search={{
                    page: 3,
                    filter: 'react',
                    sort: 'newest',
                }}>Link</Link>
        </div>
    )
}

オブジェクトの形式でパラメータを定義できます。この例の場合、

/posts?page=3&filter=react&sort=newest

に遷移します。

TanStack Routerでは、さらに型をつけることができます。

src/routes/posts/index.tsxにvalidateSearchオプションを追加。

import { createFileRoute } from '@tanstack/react-router'

type ProductSearchSortOptions = 'newest' | 'oldest' | 'price'

type ProductSearch = {
    page: number
    filter: string
    sort: ProductSearchSortOptions
}

export const Route = createFileRoute('/posts/')({
    component: () => <PostComponent />,
    validateSearch: (search: Record<string, unknown>): ProductSearch => {
        return {
            page: Number(search?.page ?? 1),
            filter: (search.filter as string) || '',
            sort: (search.sort as ProductSearchSortOptions) || 'newest',
        }
    },
})

function PostComponent() {
    return <div>Hello /posts/!</div>
}

受け取る側を作成します。

パラメータはRoute.useSearch()で受け取ることができます。


function PostComponent() {
    const { page, filter, sort } = Route.useSearch()
    return <div>Hello /posts/! page: {page} filter: {filter} sort: {sort}</div>
}

型がつくようになっています。

クエリパラメータに簡単に型をつけることができる点も、大きな魅力に感じました。

イベントハンドラでの遷移

src/routes/index.lazy.tsxに、onClickで遷移するようなコードを追加します。

import { createLazyFileRoute, Link } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/')({
    component: Index,
})

function Index() {
    const navigate = Route.useNavigate()
    return (
        <div className="p-2">
            <h3>Welcome Home!</h3>
            <Link
                to="/posts"
                search={{
                    page: 3,
                    filter: 'react',
                    sort: 'newest',
                }} >Link</Link>
            <hr />
            <button onClick={() => navigate({
                to: '/about',
            })}>About Page</button>
        </div>
    )
}

useNavigateを使用しています。

React Routerとほぼ同じですが、toを指定する際に補完が出てくれます。

パラメータをつける時はこのようにします。

// クエリパラメータ
<button onClick={() => navigate({
    to: '/posts',
    search: {
        page: 3,
        filter: 'react',
        sort: 'newest',
    },
})}>Posts Page</button>

// パスパラメータ
<button onClick={() => navigate({
    to: '/posts/$postId',
    params: { postId: '123' }
})}>Post 123  Page</button>

まとめ

TanStack Routerを触ってみて、より型安全性が増したと感じました。

特にクエリパラメータに簡単に型をつけることができる点も、大きな魅力に思いました。

既存のライブラリだと実現しづらかった、細かい型付けにまで手が届いており使いやすい印象です。

ぜひ一度試してみてください!