mcpField
T
tanstack

tanstack

github.com/tenequm開発3989 回インストール2 日前に更新
リポジトリ

tanstack について

tanstack は、React アプリ向けの Query、Router、Start をまとめたスキルです。SPA から SSR を使うフルスタックアプリまで、型安全なデータ処理、ナビゲーション、サーバー関数の実装に役立ちます。

主な機能

  1. 01サーバー状態の取得・キャッシュ・更新
  2. 02ファイルベースルーティングと型安全なナビゲーション
  3. 03検索パラメーターの検証とルートデータの読み込み
  4. 04SSR、サーバー関数、ミドルウェア
  5. 05AI/SSE 応答向けのストリーミングクエリ

活用シーン

  1. 01React アプリでの API データのキャッシュ
  2. 02検証済みパラメーターを使うファイルベースルートの構築
  3. 03SSR とサーバー関数を備えたフルスタックアプリの開発

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tenequm/skills --skill tanstack

インストール後、Claude Code で/tanstackを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

CHANGELOG.md
LICENSE.txt
references/
code-splitting.md
data-loading.md
infinite-queries.md
middleware.md
optimistic-updates.md
query-guide.md
query-invalidation.md
query-performance.md
query-typescript.md
router-guide.md
router-ssr.md
routing-patterns.md
search-params.md
server-functions.md
server-routes.md
ssr-modes.md
start-guide.md

詳細

TanStack (Query + Router + Start)

React アプリケーション向けの型安全なライブラリです。Query はサーバー状態(フェッチ、キャッシュ、ミューテーション)を管理します。Router は、検索パラメーターの検証とデータローダーを備えたファイルベースのルーティングを提供します。Start は Router を拡張し、フルスタックアプリ向けの SSR、サーバー関数、ミドルウェアを提供します。

使用する場面

Query - データフェッチ、キャッシュ、ミューテーション、楽観的更新、無限スクロール、AI/SSE レスポンスのストリーミング、tRPC v11 との統合 Router - ファイルベースのルーティング、型安全なナビゲーション、検証済み検索パラメーター、ルートローダー、コード分割、プリロード Start - SSR/SSG、サーバー関数(型安全な RPC)、ミドルウェア、API ルート、Cloudflare/Vercel/Node へのデプロイ

選択の目安:

  • API 呼び出しを行うクライアントのみの SPA -> Router + Query
  • SSR/サーバー関数を使用するフルスタックアプリ -> Start + Query(Start には Router が含まれます)

TanStack Query v5

セットアップ
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
    },
  },
})

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  )
}
クエリ
import { useQuery, queryOptions } from '@tanstack/react-query'

// Reusable query definition (recommended pattern)
const todosQueryOptions = queryOptions({
  queryKey: ['todos'],
  queryFn: async () => {
    const res = await fetch('/api/todos')
    if (!res.ok) throw new Error('Failed to fetch')
    return res.json() as Promise<Todo[]>
  },
})

// In component - full type inference from queryOptions
function TodoList() {
  const { data, isLoading, error } = useQuery(todosQueryOptions)
  if (isLoading) return <Spinner />
  if (error) return <div>Error: {error.message}</div>
  return <ul>{data.map(t => <li key={t.id}>{t.title}</li>)}</ul>
}
ミューテーション
import { useMutation, useQueryClient } from '@tanstack/react-query'

function CreateTodo() {
  const queryClient = useQueryClient()
  const mutation = useMutation({
    mutationFn: (newTodo: { title: string }) =>
      fetch('/api/todos', { method: 'POST', body: JSON.stringify(newTodo) }).then(r => r.json()),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  return (
    <button onClick={() => mutation.mutate({ title: 'New' })}>
      {mutation.isPending ? 'Creating...' : 'Create'}
    </button>
  )
}
主なパターン

クエリキー - キャッシュ管理に使う階層的な配列:

['todos']                          // all todos
['todos', 'list', { page, sort }]  // filtered list
['todo', todoId]                   // single item

依存クエリ - enabled で連鎖させます:

const { data: user } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id) })
const { data: projects } = useQuery({
  queryKey: ['projects', user?.id],
  queryFn: () => fetchProjects(user!.id),
  enabled: !!user?.id,
})

重要なデフォルト値:staleTime: 0、gcTime: 5min、retry: 3、refetchOnWindowFocus: true

Suspense - <Suspense> 境界とともに useSuspenseQuery を使用します

ストリーミングクエリ(実験的)- AI チャット/SSE 向け:

import { experimental_streamedQuery as streamedQuery } from '@tanstack/react-query'

const { data: chunks } = useQuery(queryOptions({
  queryKey: ['chat', sessionId],
  queryFn: streamedQuery({ streamFn: () => fetchChatStream(sessionId), refetchMode: 'reset' }),
}))
DevTools
pnpm add @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
// Add inside QueryClientProvider
<ReactQueryDevtools initialIsOpen={false} />
Query の詳細
  • query-guide.md - すべてのパターンを網羅した Query の完全なリファレンス
  • infinite-queries.md - useInfiniteQuery、ページネーション、仮想スクロール
  • optimistic-updates.md - 楽観的 UI、ロールバック、取り消し
  • query-performance.md - staleTime の調整、重複排除、プリフェッチ
  • query-invalidation.md - キャッシュ無効化の戦略、フィルター、述語
  • query-typescript.md - 型推論、ジェネリクス、カスタムフック

TanStack Router v1

セットアップ(Vite)
pnpm add @tanstack/react-router @tanstack/router-plugin
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({ autoCodeSplitting: true }),
    react(),
  ],
})
// src/router.ts
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export const router = createRouter({ routeTree, defaultPreload: 'intent' })

declare module '@tanstack/react-router' {
  interface Register { router: typeof router }
}
ファイルベースのルーティング

src/routes/ 内のファイルからルート設定が自動生成されます:

規約用途例
__root.tsxルートルート(常にレンダリング)src/routes/__root.tsx
index.tsxインデックスルートsrc/routes/index.tsx -> /
$param動的セグメントposts.$postId.tsx -> /posts/:id
_prefixパスレスレイアウト_layout.tsx が子をラップ
(folder)ルートグループ(URL なし)(auth)/login.tsx -> /login
型安全なナビゲーション
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>

// Active styling
<Link to="/posts" activeProps={{ className: 'font-bold' }}>Posts</Link>

// Imperative
const navigate = useNavigate({ from: '/posts' })
navigate({ to: '/posts/$postId', params: { postId: post.id } })

Link とフックには常に from を指定してください。これにより型が絞り込まれ、TS のパフォーマンスが向上します。

検索パラメーター
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'

const searchSchema = z.object({
  page: fallback(z.number(), 1).default(1),
  sort: fallback(z.enum(['newest', 'oldest']), 'newest').default('newest'),
})

export const Route = createFileRoute('/products')({
  validateSearch: zodValidator(searchSchema),
  component: () => {
    const { page, sort } = Route.useSearch()
    // Writing
    return <Link from={Route.fullPath} search={prev => ({ ...prev, page: prev.page + 1 })}>Next</Link>
  },
})

Zod アダプター(Zod v3)の fallback(...).default(...) を使用してください。通常の .catch() は型情報の喪失を引き起こします。Zod v4 ではアダプターが不要になり、スキーマを validateSearch に直接渡せます。また、.catch() でも型推論が維持されます。

データの読み込み
export const Route = createFileRoute('/posts')({
  // loaderDeps: only extract what loader needs (not full search)
  loaderDeps: ({ search: { page } }) => ({ page }),
  loader: ({ deps: { page } }) => fetchPosts({ page }),
  pendingComponent: () => <Spinner />,
  component: () => {
    const posts = Route.useLoaderData()
    return <PostList posts={posts} />
  },
})
ルートコンテキスト(依存性注入)
// __root.tsx
interface RouterContext { queryClient: QueryClient }
export const Route = createRootRouteWithContext<RouterContext>()({ component: Root })

// router.ts
const router = createRouter({ routeTree, context: { queryClient } })

// Child route - queryClient available in loader
export const Route = createFileRoute('/posts')({
  loader: ({ context: { queryClient } }) =>
    queryClient.ensureQueryData(postsQueryOptions()),
})
Router の詳細
  • router-guide.md - すべてのパターンを網羅した Router の完全なリファレンス
  • search-params.md - カスタムシリアライズ、Standard Schema、パラメーターの共有
  • data-loading.md - 遅延読み込み、ストリーミング SSR、shouldReload
  • routing-patterns.md - 仮想ルート、ルートマスキング、ナビゲーションのブロック
  • code-splitting.md - 自動/手動の分割戦略
  • router-ssr.md - SSR のセットアップ、ストリーミング、ハイドレーション

TanStack Start

SSR、サーバー関数、ミドルウェアで Router を拡張するフルスタックフレームワークです。1.0 より前の段階ですが、API は安定しており、機能は一通り揃っていて、1.0 に向けて準備中です。React Server Components は実験的な機能として利用できます。tanstackStart({ rsc: { enabled: true } }) + @vitejs/plugin-rsc でオプトインしてください(React 19、Vite 7+ が必要です)。デフォルトのバンドラーは Vite ですが、Rsbuild もサポートされています。

セットアップ
npx @tanstack/cli@latest create   # or use TanStack Builder: https://tanstack.com/builder
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    tanstackStart(),
    viteReact(), // MUST come after tanstackStart()
  ],
})
サーバー関数

型安全な RPC です。サーバーコードはビルド時にクライアントバンドルから切り離されます。

import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'

// GET - no input
export const getUsers = createServerFn({ method: 'GET' })
  .handler(async () => db.users.findMany())

// POST - validated input
export const createUser = createServerFn({ method: 'POST' })
  .validator(z.object({ name: z.string(), email: z.string().email() }))
  .handler(async ({ data }) => db.users.create(data))

// Call from loader
export const Route = createFileRoute('/users')({
  loader: () => getUsers(),
  component: () => {
    const users = Route.useLoaderData()
    return <UserList users={users} />
  },
})

重要:ローダーはアイソモーフィックであり、サーバーとクライアントの両方で実行されます。ローダーにシークレットを含めないでください。代わりに createServerFn() を使用します。

ミドルウェア
import { createMiddleware } from '@tanstack/react-start'

const authMiddleware = createMiddleware({ type: 'function' })
  .server(async ({ next }) => {
    const user = await getCurrentUser()
    if (!user) throw redirect({ to: '/login' })
    return next({ context: { user } })
  })

const getProfile = createServerFn()
  .middleware([authMiddleware])
  .handler(async ({ context }) => context.user) // typed

src/start.ts によるグローバルミドルウェア:

export const startInstance = createStart(() => ({
  requestMiddleware: [logger],    // all requests
  functionMiddleware: [auth],     // all server functions
}))

CSRF:Start は、src/start.ts が存在しない場合に限り、サーバー関数に createCsrfMiddleware() を自動でインストールします。src/start.ts を作成したら、明示的に再度追加してください。そうしないと、GET 以外のサーバー関数で同一オリジン保護が失われます:

requestMiddleware: [createCsrfMiddleware({ filter: (ctx) => ctx.handlerType === 'serverFn' }), logger]
SSR モード
モード用途
true(デフォルト)SEO、パフォーマンス
falseブラウザーのみの機能
'data-only'ダッシュボード(データはサーバー、レンダリングはクライアント)

SPA モード:vite.config.ts で tanstackStart({ spa: { enabled: true } })

デプロイ
  • 公式パートナー:Cloudflare Workers(@cloudflare/vite-plugin)、Netlify(@netlify/vite-plugin-tanstack-start)、Railway
  • Node/Vercel/Bun/Docker:Nitro 経由
  • 静的:tanstackStart({ prerender: { enabled: true, crawlLinks: true } })
Start の詳細解説
  • start-guide.md - すべてのパターンを網羅した Start の完全なリファレンス
  • server-functions.md - ストリーミング、FormData、プログレッシブエンハンスメント
  • middleware.md - sendContext、カスタム fetch、グローバル設定
  • ssr-modes.md - 選択的 SSR、shellComponent、フォールバックレンダリング
  • server-routes.md - 動的パラメーター、ワイルドカード、パスレスレイアウト

ベストプラクティス

  1. 再利用可能で型安全なクエリ定義には queryOptions() ファクトリーを使用します
  2. クエリーキーを階層的に構成します - ['entity', 'action', { filters }]
  3. データの種類ごとに staleTime を設定します - 静的:Infinity、動的:0、中程度:5min
  4. zodValidator と fallback().default() を使用して、検索パラメーターを必ず Zod で検証します
  5. ナビゲーションには from を指定します - 型の範囲が絞られ、ルートの不一致を検出できます
  6. DI にはルートコンテキストを使用します - createRootRouteWithContext を介して QueryClient や認証情報を渡します
  7. 体感パフォーマンス向上のため、グローバルに defaultPreload: 'intent' を設定します
  8. データ境界で認証を強制します - プライベートデータを読み書きするサーバー関数、サーバールート、または API エンドポイント内で認可します。beforeLoad/ルートガードは UX のためのものであり、セキュリティ境界ではありません。isomorphic loader にシークレットを含めないでください - createServerFn() を使用します
  9. ミドルウェアは階層的に組み合わせます - グローバル -> ルート -> 関数
  10. SEO 対策として、コンテンツを含むすべてのルートで head() を使用します(タイトル、説明、OG タグ)

リソース

コメント