TanStack Router 公式ドキュメントを読み解く


Router の作成方法
Creating a Router | TanStack Router React Docs
Router Class
- まずは、Router インスタンスを作成する必要がある
- ルートの定義や遷移、URLのパラメータ処理など、すべての「ルーティングロジック」を一括で管理する
- React Routerでは
BrowserRouter
がベースになるように、TanStack RouterではcreateRouter()
で作ったインスタンスが中心となる
import { createRouter } from '@tanstack/react-router'
const router = createRouter({
// ...
})
Route Tree
- Router インスタンスを作成するときに
routeTree
を渡す必要がある -
routeTree
は「ルート階層をツリー状に表現したオブジェクト」であり、下記のような情報が定義されている- どんなパスがあるか
- そのパスにどんなコンポーネントを表示するか
- 親子関係やネスト構造
Filesystem Route Tree
-
ルーティングをフォルダとファイル構造で管理する方法
-
ViteやNext.jsのように
pages/
などのフォルダから自動でルート定義を生成して、routeTree.gen.ts
という自動生成ファイルが作成される -
下記のように
routeTree
をインポートして、createRouter()
に渡すimport { routeTree } from './routeTree.gen'
Code-Based Route Tree
-
自分で
route
インスタンスを作成して、明示的にルートツリーを組み立てる方法import { rootRouteWithLoader, Route } from '@tanstack/react-router' // 親ルート const rootRoute = rootRouteWithLoader({ loader: async () => { // グローバルデータフェッチなども可能 }, }) // 子ルート const homeRoute = new Route({ getParentRoute: () => rootRoute, path: '/', component: HomePage, }) const aboutRoute = new Route({ getParentRoute: () => rootRoute, path: '/about', component: AboutPage, }) // ルートツリーを組み立てる const routeTree = rootRoute.addChildren([ homeRoute, aboutRoute, ])
-
このように、親ルート (
rootRoute
) にaddChildren
メソッドで配下ルートを配列で追加して「ツリー」を作る -
最終的にこの
routeTree
をcreateRouter({ routeTree })
に渡す
Router Type Safety
-
TanStack Router は、ルーティングに関わるあらゆる場所で型安全にコードを書けるようになっている
-
型安全にするためには、TypeScript の Declaration Merging(宣言マージ) を使って、自分が作成した
router
インスタンスの型を、プロジェクト全体で参照できるように登録する必要がある -
@tanstack/react-routerのRegister
インタフェースを拡張し、ルータのインスタンスの型を持つルータプロパティを指定するdeclare module "@tanstack/react-router" { interface Register { router: ReturnType<typeof createRouter>; } }
-
コンポーネントで、router を取得して、存在しないパスをnavigateメソッドの引数に指定すると型エラーが出る!
import { useRouter } from '@tanstack/react-router' const router = useRouter() // 存在しないパスを指定する router.navigate('/non-existent')

Outlet
Outlets | TanStack Router React Docs
Outlet コンポーネント
- ネストされたルーティング(
/parent/child
)において、親ルート内で子ルートのコンポーネントをレンダリングする場所を指定するために使う -
<Outlet />
を親コンポーネントに記述することで、現在の子ルートにマッチしているコンポーネントをレンダリングしてくれる - Outlet コンポーネントはpropsを取らず、マッチする子ルートがない場合は
null
を返す
import { createRootRoute, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
getParentRoute: () => rootRoute,
path: '/parent',
component: ParentComponent,
})
function RootComponent() {
return (
<div>
<h1>My App</h1>
<Outlet /> {/* この場所に、マッチした子ルートの中身が表示される */}
</div>
)
}
- もし route の
component
を定義しなかった場合、そのルートは自動的に<Outlet />
をレンダリングする
const mypageRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/mypage',
// component が未定義なので、<Outlet /> を返す
})
const profileRoute = createRoute({
getParentRoute: () => mypageRoute,
path: 'profile',
component: ProfilePage,
})
const settingsRoute = createRoute({
getParentRoute: () => mypageRoute,
path: 'settings',
component: SettingsPage,
})
- 上記の場合、
/mypage
は単なる「親ルート」でUIは持たず、アクセスされた時は自動で<Outlet />
として、profileやsettings の中身を差し込む役割だけになる

Navigation
Navigation | TanStack Router React Docs
Everything is Relative(すべては相対的)
-
アプリ内のすべてのナビゲーションは相対パスとして処理される
-
リンクがクリックされたり、命令型のナビゲーションが呼び出されたりすると、「今どこにいるのか」と「どこへ行きたいのか」という相対的な関係でページ遷移する
-
TanStack Routerは、すべてのナビゲーションにおいて、この相対ナビゲーションの概念を常に念頭に置いているため、APIには常に2つのプロパティが表示される:
-
TanStack Router のナビゲーションAPIでは
to
とfrom
の2つのプロパティが表示される- from ⇒ 現在のルート
- to ⇒ 遷移先のルート
-
from が省略された場合、ルート(
/
)を基準にして絶対パス解決を行うrouter.navigate({ to: 'settings' }) // Router は from がないので、/settings という絶対パスに変換される
Shared Navigation API
- TanStack Router で使われるナビゲーションやルートマッチングの API は、すべて共通のコアインターフェースをベースに設計されている
-
router.navigate()
、<Link />
などルーティングに関わるAPが同じような引数(from
,to
params
,search
など)をとるようになっている ⇒ 学習コストが低い!
To Options Interface
-
router.navigate()
,router.matchRoute()
,<Link />
など、すべてのルーティング操作で共通して使われるオプション型 - 「どこから」「どこへ」「どんなパラメータやクエリをつけて」遷移・マッチングするかを全てここで指定する
項目 | 意味 | 例 |
---|---|---|
from | 現在のルート。省略した場合は / (ルート)からの絶対パス解決になる。 |
from: '/products' |
to | 遷移先のルート。絶対パスまたは、from からの相対パスを指定できる。 |
to: '../cart' or to: '/about'
|
params | 動的URLパラメータ(/product/:id の :id の部分)。オブジェクトまたは、前回のパラメータを引数にして返す関数として指定できる。 |
params: { productId: '123' } |
search | クエリパラメータ(?page=1 など)。オブジェクトまたは関数で渡せる。 |
search: { page: 2, sort: 'price' } |
hash | URLのハッシュ(#section1 など)。文字列または関数。 |
hash: 'top' |
state | 一時的な状態。履歴APIを使って一時的なデータを持たせる用途。関数形式でもOK。 | state: { modalOpen: true } |
【Tips】
-
動的ルートを使う場合、to に直接、paramsを埋め込む(
to: "product/123"
)はダメto: '/product/$productId', // <- このように動的パスを設定しておき、 params: { productId: '123' } // <- ここでパラメータを渡す
【Tips】
-
全てのルートオブジェクトは
to
プロパティを持っている -
この
to
プロパティは「そのルートに遷移する際に使用できる型安全な参照」として使えるexport const aboutRoute = createRoute({ path: '/about', getParentRoute: () => rootRoute, component: AboutPage, }) // ルート定義のとき、aboutRoute オブジェクトには // 自動的に aboutRoute.to というプロパティがつく
-
遷移先のパスを手動で記述する方法
<Link to="/about">About</Link> // パスが変更されると、手動で変更する必要がある
-
TanStackRouter での安全な書き方
import { Route as aboutRoute } from './routes/about.tsx' function Component() { return <Link to={aboutRoute.to}>About</Link> } // ルート定義を変更するとこの参照も自動的に型解決される!
NavigationOptions Interface
-
router.navigate()
や<Navigate />
でページ遷移する際に使用するオプション型 - 基本は
ToOptions
を使うが、「ナビゲーション挙動」をさらに細かくカスタマイズするときにはNavigateOptions
を使う
プロパティ | 意味 | 例 |
---|---|---|
replace |
true にすると、履歴スタックを上書き。デフォルトは push 。 |
replace: true で「戻る」ボタンで戻れない遷移になる |
resetScroll | 遷移後に自動的にスクロール位置を (0,0) にリセットするかどうか。 |
resetScroll: true で遷移後スクロールトップへ |
hashScrollIntoView | ハッシュ(#id )に一致する要素を自動スクロールさせるかどうか。true か ScrollIntoViewOptions で詳細設定可。 |
hashScrollIntoView: true で遷移後に該当ID要素までスクロール |
viewTransition |
document.startViewTransition() を使うかどうか。ブラウザが対応していれば、遷移時にスムーズなビジュアルトランジションを行う。 |
viewTransition: true でクロスフェードなどの滑らかな遷移が可能 |
ignoreBlocker | ナビゲーションブロッカー(例:フォーム入力中の警告など)を無視して強制遷移するかどうか。 |
ignoreBlocker: true で確認ポップアップを無視して遷移 |
reloadDocument | 通常は SPA 遷移だが、これを true にするとフルページリロードを行う。外部URL風挙動にしたい場合に使う。 |
reloadDocument: true でページ全体をリロード |
href |
to の代わりに直接 href を指定できる。主に外部リンクに飛ばしたい場合に使用。 |
href: 'https://example.com' で外部リンクに遷移 |
LinkOptions Interface
-
<Link>
コンポーネント専用のオプション型。 -
NavigateOptions
(from
,to
,params
,search
,replace
,resetScroll
など)を継承している -
<a>
タグとしての属性や、リンク時のプリフェッチ挙動、アクティブ判定などの設定を追加できる
プロパティ | 意味 | 例 |
---|---|---|
target | 通常の <a> タグの target 属性と同じで、_blank など指定可。 |
target="_blank" で新規タブで開く |
preload |
intent を指定すると、ユーザーがホバーした時にプリフェッチ(事前読み込み)が走る。 |
preload: 'intent' で hover 時プリフェッチ |
preloadDelay | ホバーしてからプリフェッチを始めるまでの遅延時間(ms 単位)。 | preloadDelay: 200 |
disabled |
true にするとリンクは無効化され、href 属性も付かなくなる。 |
disabled: true |
activeOptions | リンクがアクティブかどうか判定する条件を設定。exact や includeSearch など。 |
exact: true にすると完全一致のみアクティブ扱い |
activeOptions の詳細
オプション | 意味 | よく使うケース |
---|---|---|
exact | パスが完全一致したときだけアクティブにする |
/about と /about/team が別扱いでほしい場合 |
includeHash | URL のハッシュ部分まで含めてアクティブ判定する | 特定ハッシュでセクション切り替えをする場合 |
includeSearch | クエリパラメータを含めてアクティブ判定する |
/products?page=1 と /products?page=2 を区別したい場合 |
explicitUndefined |
search や params が undefined でも「一致」とみなす条件を設定 |
通常は意識しなくてOK |
Navigation API の種類と役割
-
Link コンポーネント
- 実際の
<a>
タグを生成し、href
も生成してくれる - クリック、cmd/ctrl+クリックで新しいタブでも開ける
- 基本的には画面上に表示する遷移ボタンやメニューリンクに使う
- プリフェッチ機能 や アクティブ状態管理 もできる
- 実際の
-
useNavigate() フック
- フックとしてナビゲーション関数を取得できる
- 「ユーザーの操作ではなく、副作用やイベント処理の中で遷移したい場合」に使う
- React コンポーネント内でだけ使用できる
-
Navigate コンポーネント
-
コンポーネントとして即時遷移を行う
-
レンダー時に自動的に遷移を実行する
-
条件付きリダイレクトなどに便利。(例:ログインしていない時に強制遷移)
if (!isLoggedIn) { return <Navigate to="/login" /> }
-
-
router.navigate() メソッド
-
ルーターインスタンスに直接アクセスして使う「最強・最上位」のナビゲーションAPI
-
useNavigate()
と違い、React コンポーネント外でも使える -
ユーティリティ関数やカスタムフック内でもルーターにアクセスできれば利用可能
import { router } from './router' export function logoutAndRedirect() { // ログアウト処理後に強制遷移 router.navigate({ to: '/login', replace: true }) }
-
【Tips】
- それぞれの使い分け
API | 使いどころ |
---|---|
<Link> |
画面に表示するナビゲーションボタンやリンク |
useNavigate() |
イベントハンドラーや副作用内での遷移処理 |
<Navigate> |
条件付きリダイレクトや即時遷移(コンポーネントとして使う) |
router.navigate() |
React コンポーネント外やグローバルな場所から強制遷移 |
【Tips】
- クライアントサイドのページ遷移専用
- サーバーサイドリダイレクト(HTTPリダイレクト)が必要な場面では使えない
Link コンポーネント
activeProps と inactiveProps
-
リンクが、アクティブ状態(現在のルートとマッチしているとき)は、activePropsが適用され、マッチしていないときは inactiveProps が適用される
-
渡された
style
とclassName
の設定は全て、マージされる -
静的なオブジェクトか、関数で動的に返してもOK
<Link to="/about" activeProps={{ className: 'text-blue-500 font-bold' }} inactiveProps={{ className: 'text-gray-400' }} > About </Link>
【Tips】
- デフォルトでは
to
で指定したパスと現在のパスが「部分一致」した場合にアクティブ状態と判定される - 厳密一致させたい場合は
activeOptions={{ exact: true }}
を追加する
Absolute Links(絶対リンク)
<Link to="/about">About</Link>
- 現在のルートに関係なく
/about
に遷移する - 常に同じページにリンクしたい場合に使う
Dynamic Links(動的リンク)
<Link
to="/blog/post/$postId"
params={{ postId: '123' }}
>
Blog Post
</Link>
- URL にパラメータを含むルート(例:
/blog/post/:postId
)に対するリンク -
params
に{ postId: '...' }
を渡すことで、URL の$postId
が埋め込まれる
Relative Links(相対リンク)
const postIdRoute = createRoute({
path: '/blog/post/$postId',
})
const link = (
<Link from={postIdRoute.fullPath} to="../categories">
Categories
</Link>
)
- 相対パスでリンクを作る場合は
from
を明示的に指定する -
from="/blog/post/$postId"
を基準に../categories
を解決すると →/blog/categories
に遷移する
Search Params Links(サーチパラメータリンク)
<Link
to="/search"
search={{
query: 'hoge',
}}
>
Search
</Link>
-
search
プロパティにオブジェクトを渡すことで、クエリパラメータが付与される - 上記の例では、
/search?query=hoge
に遷移する
<Link
to="."
search={(prev) => ({
...prev,
page: prev.page + 1,
})}
>
Next Page
</Link>
-
search
に関数を渡すと、現在のクエリをもとに一部だけ更新できる -
page
を 1 増やしながら、他のパラメータ(例:query
など)は維持
Hash Links(ハッシュリンク)
<Link
to="/blog/post/$postId"
params={{ postId: '123' }}
hash="section-1"
>
Section 1
</Link>
- ページ内の特定のセクションにジャンプしたいときに使う
- 上記の例だと、
/blog/post/123#section-1
に遷移する - ブラウザはそのページ内の
id="section-1"
を持つ要素に自動でスクロールする
activeOptions
-
<Link>
コンポーネントに渡せるオプション - 「このリンクはアクティブ状態かどうか?」の判定ルールを細かく制御できる
プロパティ | デフォルト | 説明 |
---|---|---|
exact | false |
to のパスと完全一致したときだけアクティブにする(部分一致を無効にする) |
includeHash | false |
hash (例:#section1 )まで一致しているかを判定に含める |
includeSearch | true |
search (クエリパラメータ)が一致しているかを判定に含める |
explicitUndefined | false |
search のキーが undefined のとき、「そのクエリがないこと」を条件にアクティブ判定する |
Passing isActive to children
- isActive プロパティを子コンポーネントに渡して使うことができる
- そのリンクが今アクティブかどうか を内部で自由に使って、表示内容やスタイルを変えられる
<Link to="/about">
{({ isActive }) => (
<span className={isActive ? 'text-blue-500' : 'text-gray-400'}>
About
</span>
)}
</Link>
Link Preloading(リンクの事前読み込み)
- ユーザーがリンクに マウスホバー(PC)または タップ開始(スマホ)した瞬間に、そのルートに必要なコンポーネントやデータを事前に読み込む機能
-
preload="intent”
をLinkコンポーネントに渡すと使える -
preloadTimeout={100}
のように設定すると、ユーザーがリンクにマウスホバーしてから、事前読み込みするまでの時間を変更することができる。(デフォルトは50ms)
<Link to="/blog/post/$postId" params={{ postId: '123' }} preload="intent">
Blog Post
</Link>
useNavigateフック
-
useNavigate()
はナビゲーション用の関数を返すフック -
副作用の中で動的に遷移する場合に使う
⇒ 処理やフォーム送信の成功後など、「ロジックの中で遷移したいとき」
-
リンクやボタンなど、ユーザーの操作によってユーザーが操作できる場合は、Linkコンポーネントを使う!
function Component() {
const navigate = useNavigate({ from: '/posts/$postId' })
const handleSubmit = async (e: FrameworkFormEvent) => {
e.preventDefault()
const response = await fetch('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'My First Post' }),
})
const { id: postId } = await response.json()
if (response.ok) {
navigate({ to: '/posts/$postId', params: { postId } })
}
}
}
-
useNavigate({ from: '/posts/$postId' })
⇒ ここで、現在のルートを指定しておくことで、navigate()を呼び出すたびに from を渡す必要がなくなる -
navigate({ to, params })
⇒ to は遷移先、params はURLパラメータを埋め込む
Navigate コンポーネント
- Navigayeコンポーネントを使うと、コンポーネントがマウントされたときに即時にページ遷移させることができる
<Navigate to="/posts/$postId" params={{ postId: 'my-first-post' }} />
router.navigate() 関数
router.navigate()
は、useNavigate()
の代わりに使えるナビゲーション関数
- Reactコンポーネント外でも使える
- 渡すことのできる props は
useNavigate()
と同じ
// router.ts で作成したインスタンスをインポートする
import { router } from '@/app/router'
export function logoutAndRedirect() {
// ログアウト処理
localStorage.removeItem('token')
// ログインページへリダイレクト
router.navigate({ to: '/login', replace: true })
}
useMatchRoute フック と MatchRoute コンポーネント
-
useMatchRoute
フックと<MatchRoute>
コンポーネントを使うと、「今そのルートにマッチしているか?」を判定することができる -
useMatchRoute
フックは、ロジック内で判定するときに使う -
<MatchRoute>
コンポーネントは、JSX要素内で判定するときに使う -
ToOptions
インターフェースに対応している(to
,params
,search
,hash
,from
など)
pending オプション
-
ルートがあるページに遷移中である場合に、true を返す
-
ページ切り替え時のスピナーの表示に使うことができる
-
JSX要素をそのまま渡す
⇒ スピナーを表示するかしないかだけを切り替えるときに使う
<MatchRoute to="/users" pending> <Spinner /> </MatchRoute>
-
関数として、children を渡す
⇒ 表示方法やスタイルも動的に変えたい場合に使う
<MatchRoute to="/users" pending> {(match) => <Spinner show={match} />} </MatchRoute>
- hookをを使う
⇒ イベントハンドラ・副作用(useEffect)・条件分岐などで使う
⇒ UIの表示/非表示ではなく、副作用を実行するのに適している
useEffect(() => { if (matchRoute({ to: '/users', pending: true })) { console.info('The /users route is matched and pending') } }, [])
-

Path Params(動的ルート)
Path Params | TanStack Router React Docs
基本的な使い方
- Path Params は URLの中で動的に変わる部分を変数として扱える仕組み
-
$変数名
の部分が「変数」として抽出され、ルートのloader
,component
, などからアクセスできる
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
return fetchPost(params.postId)
},
})
- 上記の場合、
/posts/abc123
にアクセスされたとき、params.postId === 'abc123'
になる
Path Params は子ルートで使う
-
パスパラメータは / で区切られる「1つの区切り」単位でしかマッチしないが、子ルートを定義すれば、使うことができる
'/posts/$postId' ← 親ルート '/posts/$postId/edit' ← 子ルート '/posts/$postId/comments' ← 子ルート
-
params.postId
は 親ルートの$postId
から取得された値だが、子ルートである/edit
や/comments
などでは そのまま利用できるexport const Route = createFileRoute('/posts/$postId')({ component: PostLayout, })
export const Route = createFileRoute('/posts/$postId/edit')({ loader: ({ params }) => { return fetchPostForEdit(params.postId) // 親のパラメータを普通に使える! }, })
loader
や beforeLoad
と合わせて使う
- Path Params は、params オブジェクトとして、loader や beforeLoad に渡される
-
/blog/123
にアクセスすると、{ postId: ‘123’ }
のようなオブジェクトが渡される
export const Route = createFileRoute('/posts/$postId')({
beforeLoad: async ({ params }) => {
// do something with params.postId
console.log('今アクセスされようとしている記事ID:', params.postId)
},
})
コンポーネント内で Path Params を取得する
- コンポーネント内で Path Params を取得するには、
useParams
フックを使う必要がある
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
})
function PostComponent() {
const { postId } = Route.useParams()
return <div>Post {postId}</div>
}
-
Route.useParams()
で、そのルートにマッチしたパスパラメータが取得できる -
useParams()
の戻り値はルート定義に基づいた型安全なオブジェクトとなる⇒
$postId
と定義したら、useParams()
の戻り値にpostId
が含まれることを TypeScript が認識してくれる
【Tips】
-
コード分割してコンポーネントを遅延読み込みするとき、
Route
(ルートの定義オブジェクト)を直接 import したくない⇒
Route.useParams()
を使うために import してしまうと、ルート定義ファイルも一緒にロードされるため、コード分割の意味がなくなってしまう -
getRouteApi()
を使うと、Route を直接 import する必要がないので、コード分割することができるimport { getRouteApi } from '@tanstack/react-router' const { useParams } = getRouteApi('/posts/$postId') function PostPage() { const { postId } = useParams() return <div>Post {postId}</div> }
Route に属していないコンポーネントで Path Params を使う
-
Route.useParams()
は Route にマッチしているコンポーネント内でしか使えない - ルート外のコンポーネント(レイアウトコンポーネント、ヘッダー、フッターなど)では、グローバルな
useParams()
を使うことで取得できる
import { useParams } from '@tanstack/react-router'
function PostComponent() {
const { postId } = useParams({ strict: false })
return <div>Post {postId}</div>
}
-
strict: false
を指定することで、「ルートに紐づかなくても path param を取得する」ことを明示する - 現在のルートツリー全体から、
params
を探す
Path Params 付きのルートへ遷移する
-
$postId
のように動的ルート(Path Params)が含まれている場合、ページ遷移するときに params を必ず指定する必要がある
例:オブジェクトで指定する
<Link to="/blog/$postId" params={{ postId: '123' }}>
Post 123
</Link>
例:関数で指定する
<Link to="/blog/$postId" params={(prev) => ({ ...prev, postId: '123' })}>
Post 123
</Link>
-
params
に「関数」を渡すことで、現在のパスパラメータprev
を元に新しいパラメータを生成 -
prev
は現在の URL に存在している path params - 一部のパラメータだけを上書きして、他のパラメータはそのまま維持したいときに便利。

Search Params
Search Params | TanStack Router React Docs
なぜ URL Search Params をそのまま使わないのか?
-
従来のSearch Param APIはいくつかのことを前提としているため扱いづらい…
-
Search Params が文字列であること
- 数値として扱いたいときは、
Number()
やparseInt()
を使って変換が必要
- 数値として扱いたいときは、
-
フラットなデータが扱えない
- 配列やオブジェクトを直接扱えない
- ネストされた状態(例:
filters={ category: 'space', price: [100, 200] }
)を表現しづらい
-
シリアライズ/デシリアライズが貧弱
-
URLSearchParams
は文字列との相互変換(toString()
やget()
)に頼るだけ。 - カスタムな変換処理(JSON形式など)を挟めない。
- 例:
?filters=%7B%22category%22%3A%22space%22%7D
← 読みにくくメンテしにくい。
-
-
変更の粒度が粗い
-
URLSearchParams
を使って search params を更新すると、常に URL の pathname も一緒に扱わなければならない
window.history.pushState({}, '', `/products?page=2`) // パスごと変更
-
-
-
TanStackRouter でどのように改善するか…
課題 TanStack Router の解決策 string
にしか対応していないパラメータごとに型定義できる(boolean, number, string など) フラットな構造しか扱えない ネストしたオブジェクトや配列にも対応 カスタムシリアライズ不可 parseSearch
,stringifySearch
で自由に定義できるリアクティブに扱えない useSearch()
,useNavigate()
で search を状態のように扱える無駄な再パースでパフォーマンス低下 Structural Sharing を考慮して参照の整合性を保つ pathname と search が強く結びついている pathname に影響を与えず search だけ変更できる( navigate({ to: ".", search })
)
Search Params は、元祖グローバル状態管理だ!
ユーザー視点
- 状態がURLにあるからこそ、ユーザー体験が保たれる
ユーザー行動 | なぜ Search Params が重要? |
---|---|
Cmd/Ctrl + クリック | タブを開いても、ちゃんとフィルターやページ番号が保持されているべき |
ブックマークやURL共有 | 他人が開いたときにも同じ状態でページが表示されるべき |
リロード・戻る・進む | UI状態が消えないように、URLに状態を保つべき |
開発者視点
開発者のニーズ | TanStack Routerが解決するポイント |
---|---|
簡単にURLの状態を書き換えたい |
useSearch() , navigate() でできる |
値の型を明示したい | search schema によって boolean , number もOK |
シリアライズに悩みたくない |
parseSearch , stringifySearch が自動対応 |
JSON ファーストな Search Params
ポイント
- TanStackRouter では、Search Parms を文字列ではなく、JSONデータとして扱えるようにしている
特徴 | 説明 |
---|---|
自動で数値として扱える |
"3" → 3 に自動変換される |
boolean型も扱える |
"true" → true に自動変換される |
配列・オブジェクトも扱える | JSON 形式で encode/decode される |
双方向に変換 |
URL → JSON → URL の変換が自動かつ安全 |
他のツールとの互換性 | 第一階層は通常のURLと同じ key?=value 形式のため、互換性が保たれる |
実用例
-
通常の React の state オブジェクトのように、型付きの構造化データをそのまま渡す
<Link to="/shop" search={{ pageIndex: 3, includeCategories: ['electronics', 'gifts'], sortBy: 'price', desc: true, }} />
-
生成されるURL
/shop?pageIndex=3 &includeCategories=%5B%22electronics%22%2C%22gifts%22%5D &sortBy=price &desc=true
-
URLをパースして取得できる値
{ "pageIndex": 3, "includeCategories": ["electronics", "gifts"], "sortBy": "price", "desc": true }
Search Parms の型付けとバリデーション
何故バリデーションが必要なのか?
-
Serach Params はユーザーが任意に変更できるので、そのまま使うとクラッシュしたり、予期せぬ挙動を引き起こすことになる
/shop/products?page=aaa&sort=hacked page → "aaa"(数値じゃない) sort → "hacked"(存在しない値)
ValidateSearch オプション
- Route の validateSearch オプションを使うことで、各ルートで Search Params を検証することができる
export const Route = createFileRoute('/shop/products')({
validateSearch: (search: Record<string, unknown>): ProductSearch => {
return {
page: Number(search?.page ?? 1),
filter: (search.filter as string) || '',
sort: (search.sort as ProductSearchSortOptions) || 'newest',
}
},
})

Authenticated Routes(認証ルート)
Authenticated Routes | TanStack Router React Docs
概要
- Authenticated Routes を使うと、ログインしていないときに表示させたくないパスを設定したり、ログインページにリダイレクトさせることができる
route.beforeLoad オプション
- router.beforeLoadオプションを使うと、ルートが読み込まれる前に実行される関数を指定することができる
- イメージとしては、「このページを表示してもいいか?」を判断する門番さん的な存在
ページが読み込まれるまでの処理順
- Route Matching(ルートマッチング)
- パラメータを解析
route.params.parse
- クエリを検証
route.validateSearch
- パラメータを解析
- Route Loading(ルートロード前の処理)
- 認証チェック
route.beforeLoad
- エラーハンドリング
route.onError
- 認証チェック
- Route Loading(非同期並列処理)
- コンポーネントの事前ロード
route.component.preload
- データフェッチや初期化処理
route.load
- コンポーネントの事前ロード
例:/mypage というルートを表示しようとした場合
1. URL確認(/mypage)
2. パラメータ検証 (params.parse, validateSearch)
3. beforeLoad 実行 → ここで「ログイン済み?」を確認
- 未ログインなら /login にリダイレクト
- ログイン済みなら次の段階へ
4. コンポーネントの preload 実行(必要に応じてデータ先読み)
5. 最終的にコンポーネントをロードして描画
ポイント
- コンポーネントが読み込まれるより前に、beforeload(認証処理)をすることで、認証が失敗したときも安全
- 親ルートの beforeLoad ⇒ 子ルートの breforeLoad の順で実行されるため、親ルートでログイン確認をして、子ルートで権限チェックをするといった使い分けができる
Redirecting(リダイレクト)
- 必須ではないが、認証フローによってはログインページにリダイレクトさせる必要がある
- リダイレクトさせるには、
beforeLoad()
の中でredirect()
をスローする
認証ガード
export const Route = createFileRoute('/_authenticated')({
beforeLoad: async ({ location }) => {
if (!isAuthenticated()) {
throw redirect({
to: '/login',
search: {
redirect: location.href,
},
});
}
},
});
-
beforeLoad()
の中で、認証されているかチェックする関数を呼び出し、認証されていなければ、throw redirect()
を使って、/login
に強制遷移させている - search param に
location.href
を含めることで、ログイン成功後に元々アクセスしようとしていたページに戻すことができる- ユーザーが
/mypage
にアクセス -
mypage
でログインしてないことを検知 -
/login?redirect=https://example.com/login
に飛ばす - ログイン成功したら
search.redirect
に入っている URL へ戻す
- ユーザーが
ログインハンドラー
const handleLogin = async () => {
// ログイン処理
await login();
// URL から redirect パラメータ取得
const search = router.state.location.search;
if (search.redirect) {
router.history.push(search.redirect);
} else {
router.history.push('/'); // デフォルトはトップへ
}
};
- 正常にログインできた場合、
router.history.push(search.redirect);
を呼び出す - これにより、ユーザーが戻るボタンを押したときに、ログインページに戻るのを防ぐことができる
Non-Redirected Authentication(非リダイレクト認証)
- ログインページにリダイレクトさせずに、同じページ上でログインフォームを表示させることができる
- SPAで画面遷移せずに、モーダルでログインフォームを表示させる
- ログインが完了したら、そのままコンテンツに切り替わる
export const Route = createFileRoute('/_authenticated')({
component: () => {
if (!isAuthenticated()) {
return <Login /> // 未認証なら Login コンポーネントを表示
}
return <Outlet /> // 認証済みなら <Outlet /> で子ルートを描画
},
})
ポイント
-
isAuthenticated()
でログイン済みかチェックし、ログインしていなければ子ルートはレンダリングせずにログインフォームを表示する - ログイン済みであれば、
Outlet
で子ルートをレンダリングする
メリット
- URLが変わらないので、「ログイン後にどこに戻すか?」のロジックが不要
- ページ遷移によるチラつきがない
React context/hooks を使った認証
- 認証フローがReactの context や カスタムhook に依存している場合、
router.context
オプションを使用して、認証状態を TanStackRouter に渡す必要がある
router.context
が必要なのか?
なぜ、- React の hooks はコンポーネントの中でしか呼べない
- beoreLoad で認証状態を使いたい場合、コンポーネント内で hook から取り出した状態を
router.context
に渡す必要がある
認証ルートを保護するために context と hook を使用する例
-
MyRouterContext を定義し、root route をこの context 型付きで定義
import { createRootRouteWithContext } from '@tanstack/react-router' interface MyRouterContext { // useAuthフックのReturnTypeまたはAuthContextの値 auth: AuthState } export const Route = createRootRouteWithContext<MyRouterContext>()({ component: () => <Outlet />, })
-
routerを作成
import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' export const router = createRouter({ routeTree, context: { // 初期値はダミーでOK。実際はコンポーネント内で渡す。 auth: undefined!, }, })
-
App.tsx
でuseAuth()
から取得した情報をrouter.context
に渡すimport { RouterProvider } from '@tanstack/react-router' import { AuthProvider, useAuth } from './auth' import { router } from './router' function InnerApp() { const auth = useAuth() return <RouterProvider router={router} context={{ auth }} /> } function App() { return ( <AuthProvider> <InnerApp /> </AuthProvider> ) }
ポイント
- React コンポーネント内で useAuth() を使って認証状態を取得
- 取得した状態を router.context に注入
- これにより、
boreLoad
の中でcontext.auth
として安全に認証状態が使えるようになる
-
認証ガードを実装する
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"; export const Route = createFileRoute("/mypage")({ beforeLoad: ({ context, location }) => { if (!context.userState.isLoggedIn) { throw redirect({ to: "/login", search: { redirect: location.href }, }); } }, component: RouteComponent, }); function RouteComponent() { return <Outlet />; }
ポイント
-
context.auth.isAuthenticated
を使って認証チェック - 認証されていない場合は
/login
へリダイレクト -
redirect: location.href
で、もともと遷移しようとしていたパスをクエリパラメータで渡す
-
-
/login
クエリパラメータを設定するimport { createFileRoute, redirect } from "@tanstack/react-router"; import LoginPage from "@/components/templates/LoginPage/LoginPage"; type loginSearch = { redirect?: string; }; export const Route = createFileRoute("/login")({ // クエリパラメータのバリデーション validateSearch: (search: Record<string, unknown>): loginSearch => { return { redirect: search.redirect as string, }; }, component: RouteComponent, }); function RouteComponent() { return <LoginPage />; }
- validateSearch を使って、URLで渡ってきたクエリパラメータ(
?redirect=/mypage
)を型チェック&バリデーションチェックする - 成功した場合は型付きオブジェクトとして route.loader や beforeLoad などで安全に参照可能にする
- 失敗した場合は自動的にエラールートに遷移してくれる
- validateSearch を使って、URLで渡ってきたクエリパラメータ(

404ページの設定
Not Found Errors | TanStack Router React Docs
Creating a Router | TanStack Router React Docs
notFoundMode
存在しないページにアクセスしたときに、404ページを表示する設定はnotFoundModeで挙動が変わる。
fuzzy
- デフォルトでは、fuzzyが設定される
- 最も近い親レイアウト(
src/route.tsx
)を維持しつつ、その親レイアウトに設定したnotFoundComponentを表示する - 親ページのナビゲーションや UI を維持したまま「ページが見つからない」という情報を表示したい時に使う
root
- 存在しないページにアクセスしたときは、
src/routes/__root.tsx
に設定したnotFoundComponent
を表示する - 全ての404ページを統一したいときに使う
手順
- configファイルに
notFoundMode
オプションを追加する
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
export function createRouter() {
const router = createTanStackRouter({
routeTree,
notFoundMode: "root", // notFoundModeの設定
});
return router;
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
- 親ルートにnotFoundComponentを設定する
ルートrootに設定する場合
⇒ 存在しないページにアクセスした時は、Headerコンポーネント、Footerコンポーネント、NotFoundコンポーネントがレンダリングされる
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";
import Header from "@/components/templates/Header/Header";
import Footer from "@/components/templates/Footer/Footer";
import NotFound from "@/components/templates/NotFound/NotFound";
export const Route = createRootRoute({
component: () => (
<>
<Header />
<Outlet />
<Footer />
</>
),
notFoundComponent: () => <NotFound />,
});
任意のルートに設定する場合
⇒ store/non-existent-page
のように/store
以下のルートで存在しないページにアクセスしたときは、ルートrouteに設定しているコンポーネントと、MenubarコンポーネントとNotFoundコンポーネントが一緒にレンダリングされる
import { createFileRoute, Outlet } from "@tanstack/react-router";
import Menubar from "@/components/organisms/Menubar/Menubar";
import NotFound from "@/components/templates/NotFound/NotFound";
export const Route = createFileRoute("/store")({
component: RouteComponent,
notFoundComponent: () => <NotFound />,
});
function RouteComponent() {
return (
<>
<Menubar />
<Outlet />
</>
);
}