vue-router について
Vueアプリ開発者向けに、TanStack RouterのVueバインディングを解説するガイドです。ファイルベースのルーティング、型安全なナビゲーション、コンポーザブルを扱い、ローダーは既定でクライアント側で実行されます。
主な機能
- 01ファイルベースのルーティング設定
- 02型安全なリンクとナビゲーション
- 03Vueルーターコンポーザブル
- 04ルートデータと検索パラメーター
- 05コード分割とエラー処理
活用シーン
- 01VueアプリでのTanStack Router設定
- 02型付きパラメーターを使ったルート構築
- 03未保存の変更がある場合の遷移ブロック
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tanstack/ router --skill vue-routerインストール後、Claude Code で/vue-routerを入力して読み込まれたか確認します。
詳細
Vue Router (@tanstack/vue-router)
このスキルは router-core を基盤としています。基本概念については、まず @tanstack/router-core#router-core をお読みください。
このスキルでは、TanStack Router の Vue 固有のバインディング、コンポーネント、コンポーザブル、セットアップについて説明します。
CRITICAL:TanStack Router の型は完全に推論されます。キャストも、推論された値への型注釈も行わないでください。
CRITICAL:TanStack Router はクライアントファーストです。ローダーはデフォルトでサーバーではなく、クライアント上で実行されます。
CRITICAL:ほとんどのコンポーザブルは
Ref<T>を返します。スクリプト内では.value経由でアクセスし、テンプレート内では自動的にアンラップされます。これが React 版との最大の違いです。
CRITICAL:
@tanstack/vue-routerとvue-router(公式の Vue ルーター)を混同しないでください。両者は API が異なる、まったく別のライブラリです。
セットアップ全体:Vite でのファイルベースルーティング
1. 依存関係をインストールする
npm install @tanstack/vue-router
npm install -D @tanstack/router-plugin @vitejs/plugin-vue-jsx
2. Vite プラグインを設定する
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
// MUST come before vue()
tanstackRouter({
target: 'vue',
autoCodeSplitting: true,
}),
vue(),
vueJsx(), // Required for JSX/TSX route files
],
})
3. ルートルートを作成する
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/vue-router'
export const Route = createRootRoute({
component: RootLayout,
})
function RootLayout() {
return (
<>
<nav>
<Link to="/" activeProps={{ class: 'font-bold' }}>
Home
</Link>
<Link to="/about" activeProps={{ class: 'font-bold' }}>
About
</Link>
</nav>
<hr />
<Outlet />
</>
)
}
4. ルートファイルを作成する
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/vue-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome Home</h1>
}
5. ルーターインスタンスを作成して型を登録する
// src/main.tsx
import { createApp } from 'vue'
import { RouterProvider, createRouter } from '@tanstack/vue-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
declare module '@tanstack/vue-router' {
interface Register {
router: typeof router
}
}
const app = createApp(RouterProvider, { router })
app.mount('#root')
コンポーザブルリファレンス
すべてのコンポーザブルは @tanstack/vue-router からインポートします。ほとんどは Ref<T> を返します。スクリプト内では .value 経由でアクセスし、テンプレート内では自動的にアンラップされます。
useRouter() — TRouter を返します(Ref ではありません)
import { useRouter } from '@tanstack/vue-router'
const router = useRouter()
router.invalidate()
useRouterState() — Ref<T> を返します
ルーターの状態変更を購読します。状態全体を公開するため、パフォーマンスコストが発生します。マッチまたはロケーションには、useMatches と useLocation を使用してください。
import { useRouterState } from '@tanstack/vue-router'
const isLoading = useRouterState({ select: (s) => s.isLoading })
// Access: isLoading.value
useNavigate() — 関数を返します(Ref ではありません)
import { useNavigate } from '@tanstack/vue-router'
const navigate = useNavigate()
async function handleSubmit() {
await saveData()
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
useSearch({ from }) — Ref<T> を返します
import { useSearch } from '@tanstack/vue-router'
const search = useSearch({ from: '/products' })
// Access: search.value.page
useParams({ from }) — Ref<T> を返します
import { useParams } from '@tanstack/vue-router'
const params = useParams({ from: '/posts/$postId' })
// Access: params.value.postId
useLoaderData({ from }) — Ref<T> を返します
import { useLoaderData } from '@tanstack/vue-router'
const data = useLoaderData({ from: '/posts/$postId' })
// Access: data.value.post.content
useMatch({ from }) — Ref<T> を返します
import { useMatch } from '@tanstack/vue-router'
const match = useMatch({ from: '/posts/$postId' })
// Access: match.value.loaderData.post.title
その他のコンポーザブル
useMatches()—Ref<Array<Match>>。アクティブなルートマッチすべてuseRouteContext({ from })—Ref<T>。beforeLoadのコンテキストuseBlocker({ shouldBlockFn })— 未保存の変更がある場合にナビゲーションをブロックしますuseCanGoBack()—Ref<boolean>useLocation()—Ref<ParsedLocation>useLoaderDeps({ from })—Ref<T>。ローダーの依存値useLinkProps()—LinkHTMLAttributesを返しますuseMatchRoute()— 関数を返します。この関数を呼び出すとRef<false | Params>が返されます
コンポーネントリファレンス
RouterProvider
import { RouterProvider } from '@tanstack/vue-router'
// In createApp or template
<RouterProvider :router="router" />
Link
アクティブ状態をスコープ付きスロットで扱える、型安全なナビゲーションリンクです。
<Link to="/posts/$postId" :params="{ postId: '42' }">
View Post
</Link>
<Link to="/about">
<template #default="{ isActive }">
<span :class="{ active: isActive }">About</span>
</template>
</Link>
Outlet
マッチした子ルートのコンポーネントをレンダリングします。
Navigate
宣言的なリダイレクトです(onMounted でナビゲーションを実行します)。
Await
遅延データ用の非同期セットアップコンポーネントです。Vue の <Suspense> と組み合わせて使用します。
CatchBoundary
Vue の onErrorCaptured を使用するエラーバウンダリーです。
Html と Body
Vue 固有の SSR シェルコンポーネントです。
function RootComponent() {
return (
<Html>
<head>
<HeadContent />
</head>
<Body>
<Outlet />
<Scripts />
</Body>
</Html>
)
}
ClientOnly
onMounted(ハイドレーション完了後)のみ子要素をレンダリングします。
<ClientOnly fallback={<div>Loading...</div>}>
<BrowserOnlyWidget />
</ClientOnly>
Vue 固有のパターン
createLink によるカスタムリンクコンポーネント
import { createLink } from '@tanstack/vue-router'
import { defineComponent, h } from 'vue'
const StyledLinkComponent = defineComponent({
setup(props, { slots, attrs }) {
return () => h('a', { ...attrs, class: 'styled-link' }, slots.default?.())
},
})
const StyledLink = createLink(StyledLinkComponent)
レンダー関数(h())
@tanstack/vue-router のすべてのコンポーネントは、内部で h() レンダー関数を使用します。ルートコンポーネントでは、SFC テンプレートとレンダー関数のどちらも使用できます。
MyRoute.component.vue に記述する SFC テンプレート(ユーザーコードで最も一般的):
<template>
<div>{{ data.title }}</div>
</template>
<script setup>
import { useLoaderData } from '@tanstack/vue-router'
const data = useLoaderData({ from: '/posts/$postId' })
</script>
ルーターコンテキストを使った認証
import { createRootRouteWithContext } from '@tanstack/vue-router'
const rootRoute = createRootRouteWithContext<{ auth: AuthState }>()({
component: RootComponent,
})
const router = createRouter({
routeTree,
context: { auth: authState },
})
// In a route — access via beforeLoad
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
}
コード分割のための Vue ファイル規約
autoCodeSplitting を使用すると、Vue ルートでは任意で分割ファイル規約を利用できます。これは必須ではなく、単一ファイルの .tsx ルートでも問題ありません。分割ファイルは、ルート設定とコンポーネントを分離する場合に便利です。
myRoute.ts— ルート設定(検索パラメーター、ローダー、beforeLoad)myRoute.component.vue— ルートコンポーネント(遅延読み込み)myRoute.errorComponent.vue— エラーコンポーネント(遅延読み込み)myRoute.notFoundComponent.vue— 見つからない場合のコンポーネント(遅延読み込み)myRoute.lazy.ts— 遅延読み込みされるルートオプション
よくある間違い
1. 重大:スクリプトブロックでの.valueの付け忘れ
コンポーザブルはRef<T>を返します。<script>内では.valueを介してアクセスします。テンプレートでは自動的にアンラップされます。
// WRONG — accessing Ref without .value in script
const params = useParams({ from: '/posts/$postId' })
console.log(params.postId) // undefined!
// CORRECT — use .value
const params = useParams({ from: '/posts/$postId' })
console.log(params.value.postId)
2. 高:vue-router(公式)との混同
@tanstack/vue-routerはvue-routerではありません。vue-routerの<router-view>、<router-link>、useRoute()、useRouter()は使用しないでください。
// WRONG — official vue-router imports
import { useRoute, useRouter } from 'vue-router'
// CORRECT — TanStack Vue Router imports
import { useMatch, useRouter } from '@tanstack/vue-router'
3. 高:beforeLoadまたはloaderでのVueフックの使用
beforeLoadとloaderはコンポーネントのセットアップ関数ではなく、通常の非同期関数です。Vueコンポーザブルは使用できません。代わりに、ルーターコンテキストを介して状態を渡してください。
4. 中:プラグイン対象の誤り
ルータープラグインの設定でtarget: 'vue'を指定する必要があります。デフォルトは'react'です。
関連項目
@tanstack/router-core#router-core— ドメイン固有のパターン(検索パラメーター、データ読み込み、ナビゲーション、認証、SSRなど)に関するすべてのサブスキル