TanStackとは?Queryだけではない18ライブラリの全体像と選び方を解説

Posted at 2026 年 09 月 10 日

TanStackとは?Queryだけではない18ライブラリの全体像と選び方を解説

package.json@tanstack/react-queryが入っているプロジェクトは多いと思う。ところが公式サイトを開くと、Router、Start、Table、Form、DB、AIと知らない名前が並んでいる。どれが自分に必要で、どれは当面無視していいのか。ここで手が止まる。

TanStackは単一のライブラリではない。2026年9月時点で18本のライブラリからなる群で、そのすべてがヘッドレス・フレームワーク非依存・型推論ファーストという同じ三原則で作られている。だから一本の使い方を掴めば、残りの読み方も見当がつく。

いま全体像を押さえておく価値はある。State of React 2025で、TanStack Queryの利用経験は68.1%に達した。6月のJSNationでは、TanStack StartとTanStack AIが2026 Open Source Awardsを受賞したと公式ブログが伝えている。「Queryのライブラリ」から「選択肢の一群」へと、立ち位置が変わりつつある。

この記事では三原則を押さえたうえで、主要ライブラリの現在地を一つずつ見ていく。バージョンとダウンロード数は、2026年9月10日にnpmレジストリから取得した実測値である。

TanStackの全体像(18のライブラリ群)

TanStackはTanner Linsley氏を中心としたチームが開発するオープンソースライブラリ群の総称で、TanStack.jsという名前のパッケージはない。Reactのデータ取得ライブラリとして知られるReact Query(現TanStack Query)が出発点で、そこから同じ思想の実装が横に広がった。

主要なものを表にする。

ライブラリ

最新版

週間DL

役割

Query

5.102.8

約5,540万

サーバー状態の取得・キャッシュ・同期

Virtual

3.14.11

約2,090万

大量行の仮想スクロール

Router

1.170.34

約1,770万

型安全なファイルベースルーティング

Table

9.2.4

約1,390万

ヘッドレスなテーブルロジック

Start

1.168.51

約1,230万

フルスタックReactフレームワーク(RC)

Form

1.33.5

約264万

フォーム状態とバリデーション

DB

0.3.7

約68万

リアクティブなクライアントストア

AI

0.54.0(react版 0.24.1)

LLM向けの型安全なSDK(RC)

このほかにも部品がある。状態まわりはStore(軽量な状態ストア)とPacer(debounce・throttle・レート制限・キューイング・バッチング)。UI寄りはCharts、Hotkeys、Markdown、Highlight。開発支援は統合Devtools、CLI、Config、Intent。数の多さに面食らうかもしれないが、これは次に述べる三原則を満たす部品を一つずつ切り出していった結果として、いまの本数になっている。

TanStackを貫く3つの設計思想

ヘッドレス:UIを持たず、ロジックだけを返す

TanStack Tableは<DataTable />を提供しない。ソート・フィルタ・ページング・列の状態管理といったロジックだけを渡し、<table>要素は開発者が書く。見た目の自由度が上がる代償として初期のコード量は増えるが、この設計には副産物がある。Reactを全面採用できない環境にコアだけを持ち込める点だ。

@tanstack/table-core@tanstack/query-coreはReactに依存しない(前者の依存は@tanstack/storeのみ、後者は依存ゼロ)。素のDOMで組まれた既存の管理画面に、一覧部分だけ高機能なテーブルを載せる。そういう部分導入が構造上は成り立つ計算になる。

フレームワーク非依存:coreと薄いアダプタ

各ライブラリは素のTypeScriptで書かれた*-coreパッケージを中心に持ち、@tanstack/react-*vue-*solid-*angular-*svelte-*がその上に薄く被る。アダプタが主に担うのはフレームワーク固有の購読と再描画の橋渡しで、ロジックはcoreに集約されている。

ただし揃うアダプタはライブラリごとに違う。QueryのAngular版は@tanstack/angular-query-experimentalという実験的な位置づけだし、Svelte版は本体と別のバージョン体系で進んでいる。

型推論ファースト:ジェネリクスを手で書かない

TanStackのAPIは、開発者が型引数を書かなくても型が通るように設計されている。RouterでvalidateSearchにスキーマを渡せば、その型がloaderにもコンポーネントにも流れていく。ここが後述するRouterの中核でもある。

TanStack Query:サーバー状態という区分

Queryの功績は機能そのものより、クライアント状態とサーバー状態を別物として扱う考え方を定着させた点にある。サーバーのデータはこちらのものではないので、いつ古くなるかわからない。だからキャッシュには鮮度と再取得のルールが必要になる、という発想だ。

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'

function TodoList() {
  const queryClient = useQueryClient()

  const { data, isPending, error } = useQuery({
    queryKey: ['todos'],
    queryFn: async () => {
      const res = await fetch('/api/todos')
      if (!res.ok) throw new Error('failed to fetch todos')
      return res.json()
    },
    staleTime: 30_000,
  })

  const addTodo = useMutation({
    mutationFn: (text: string) =>
      fetch('/api/todos', { method: 'POST', body: JSON.stringify({ text }) }),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] })
    },
  })

  if (isPending) return <p>読み込み中</p>
  if (error) return <p>{error.message}</p>

  return (
    <>
      <ul>{data.map((t) => <li key={t.id}>{t.text}</li>)}</ul>
      <button onClick={() => addTodo.mutate('新しいタスク')}>追加</button>
    </>
  )
}

queryKeyでキャッシュを識別し、staleTimeで鮮度を決め、更新後はinvalidateQueriesで無効化する。この3つの操作でだいたいの要件が片付く。週間5,540万ダウンロードという数字は、この抽象が当たったことの表れだろう。

TanStack Router:URLを型付きの状態として扱う

React Routerとの大きな違いは、クエリパラメータを文字列の袋ではなく検証済みの状態として扱う点にある。スキーマを宣言すると、その型がloaderの依存とコンポーネントの両方に伝わる。

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

const productSearchSchema = z.object({
  page: z.number().default(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest'),
})

export const Route = createFileRoute('/shop/products')({
  validateSearch: productSearchSchema,
  loaderDeps: ({ search }) => ({
    page: search.page,
    filter: search.filter,
    sort: search.sort,
  }),
  loader: async ({ deps }) => fetchProducts(deps),
  component: ProductList,
})

function ProductList() {
  const search = Route.useSearch()      // ProductSearch型が推論される
  const { products } = Route.useLoaderData()
  return <Grid items={products} page={search.page} />
}

loaderDepsで宣言した値が変わったときだけloaderが再実行される。ページネーションや絞り込みの状態をURLに預けられるので、リロードしても共有しても同じ画面が復元される。この「URL as state」がRouterの売りだ。

TanStack Start:Routerの上に載るフルスタック層

TanStack StartはRouterを土台にしたフルスタックReactフレームワークで、ビルドはViteまたはRsbuild。型安全なサーバー関数、サーバールート、ミドルウェアを備え、ドキュメント全体のSSRとストリーミングにも対応する。React Server Componentsは実験的サポートの段階だ。

中核はサーバー関数になる。クライアントとサーバーの間に型のついたRPCを引く仕組みで、APIルートを手で切らずに済む。

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

export const createUser = createServerFn({ method: 'POST' })
  .validator(z.object({ name: z.string().min(1), age: z.number().min(0) }))
  .handler(async ({ data }) => {
    // ここはサーバーだけで実行される。DBクライアントを直接触ってよい
    return { message: `created ${data.name}` }
  })
import { useServerFn } from '@tanstack/react-start'

function SignUp() {
  const createUserFn = useServerFn(createUser)
  const onSubmit = async () => {
    const res = await createUserFn({ data: { name: 'DX', age: 30 } })
    console.log(res.message)
  }
  return <button onClick={onSubmit}>登録</button>
}

バージョン表記には注意がいる。v1.0 Release Candidateのアナウンスは2025年9月。以後「機能は完成しAPIは安定」という扱いのままRC表記が続き、パッケージのバージョンだけが1.168まで進んだ。安定版の宣言を待っている状態がちょうど1年続いている、と読むのが正確だ。とはいえ週間1,230万ダウンロードという規模に達しており、実運用に入らないほど不安定というわけでもない。

Next.jsとの比較で言えば、Startの強みはViteベースの開発体験と、隠れた挙動の少なさにある。反面、実績の厚み、エコシステムの広さ、画像最適化のような周辺機能ではNext.jsが優位で、経験者の見つけやすさも同じだろう。新規かつチームが小さい案件ならStart、長期運用と人員の入れ替えを見込むならNext.js、という切り分けが現実的だと思う。

TanStack Table v9:v8からのAPI変更

Tableは9系が安定版になり、v8から書き方が変わった。v8ではgetCoreRowModel()などのrow modelを個別に渡していたが、v9ではtableFeatures()で使う機能を宣言し、useTableに渡す。

import { tableFeatures, useTable } from '@tanstack/react-table'
import type { ColumnDef } from '@tanstack/react-table'

type Person = { firstName: string; lastName: string; age: number }

const features = tableFeatures({})   // 使う機能をここで宣言する

const columns: Array<ColumnDef<typeof features, Person>> = [
  { accessorKey: 'firstName', header: '姓' },
  { accessorKey: 'lastName', header: '名' },
  { accessorKey: 'age', header: '年齢' },
]

export function PersonTable({ data }: { data: Array<Person> }) {
  const table = useTable({ features, columns, data })

  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((hg) => (
          <tr key={hg.id}>
            {hg.headers.map((header) => (
              <th key={header.id}>
                {!header.isPlaceholder && <table.FlexRender header={header} />}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map((row) => (
          <tr key={row.id}>
            {row.getAllCells().map((cell) => (
              <td key={cell.id}><table.FlexRender cell={cell} /></td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  )
}

columnsの型引数にtypeof featuresが入っている点に注目したい。宣言していない機能のAPIは型レベルで見えなくなる。v8のプロジェクトを持っているなら、この読み替えが移行時の主な作業になる。

Form・Virtual・Pacer・Store:必要になったら足す部品

ここから先は、必要になったときだけ導入する部品群だ。

FormはuseFormとフィールド単位の購読で、フォーム全体の再レンダリングを避ける設計になっている。バリデーションはフィールドに直接書ける。

import { useForm } from '@tanstack/react-form'

function AgeForm() {
  const form = useForm({
    defaultValues: { username: '', age: 0 },
    onSubmit: ({ value }) => console.log(value),
  })

  return (
    <form.Field
      name="age"
      validators={{
        onChange: ({ value }) => (value >= 13 ? undefined : '13歳以上を入力してください'),
      }}
      children={(field) => (
        <>
          <input
            type="number"
            value={field.state.value}
            onBlur={field.handleBlur}
            onChange={(e) => field.handleChange(e.target.valueAsNumber)}
          />
          {!field.state.meta.isValid && <em>{field.state.meta.errors.join(', ')}</em>}
        </>
      )}
    />
  )
}

Virtualは行数が増えて描画が重くなる場面で、実際にDOMを作る要素を絞る仕組みだ。Tableと組み合わせる場面が多い。Pacerはdebounce・throttle・レート制限・キューイング・バッチングの5つをフックとして提供する。検索入力とAPI呼び出しの間に挟むと自前実装を消せる。Storeはシグナルに近い軽量ストアで、Form・Table・Pacerが内部の状態管理に使っている(Queryは依存していない)。Formはv2がalpha、Pacerはbeta、Storeは0.11系で、このあたりは今後の変更幅がやや大きい。

TanStack DBとTanStack AI:最前線の2つ

TanStack DB

DBは「APIのためのリアクティブなクライアントストア」を名乗る。データを正規化したコレクションに置き、そこに対してクエリを投げる。特徴はdifferential dataflowによるライブクエリだ。クエリ全体を再実行せず差分だけを更新する仕組みで、公式は大きなデータセットでもサブミリ秒で結果が返るとしている(測定条件は公開されていない)。

import { collectionOptions, eq, useDbClient, useLiveQuery } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
import type { QueryClient } from '@tanstack/query-core'

const todoCollection = collectionOptions('todos', (client) =>
  queryCollectionOptions({
    id: 'todos',
    queryKey: ['todos'],
    queryClient: client.requireDependency<QueryClient>('queryClient'),
    queryFn: async () => (await fetch('/api/todos')).json(),
    getKey: (item) => item.id,
    onUpdate: async ({ transaction }) => {
      const { original, modified } = transaction.mutations[0]
      await fetch(`/api/todos/${original.id}`, {
        method: 'PUT',
        body: JSON.stringify(modified),
      })
    },
  }),
)

function Todos() {
  const todos = useDbClient().collection(todoCollection)

  const { data } = useLiveQuery({
    query: (q) =>
      q.from({ todo: todoCollection })
        .where(({ todo }) => eq(todo.completed, false))
        .orderBy(({ todo }) => todo.createdAt, 'desc'),
  })

  // 楽観的更新。失敗すれば自動でロールバックする
  const toggle = (todo) => todos.update(todo.id, (draft) => { draft.completed = !draft.completed })

  return <ul>{data.map((t) => <li key={t.id} onClick={() => toggle(t)}>{t.text}</li>)}</ul>
}

狙いは、画面ごとに専用エンドポイントを増やしていく設計からの脱出だ。汎用的なコレクションをクライアントに持たせ、画面固有の絞り込みはローカルのクエリで行う。ウォーターフォールも消える。ただし0.3系でAPIはまだ変わりうるので、本番投入は要件と時期を選ぶべきだろう。

TanStack AI

AIはLLMアプリ向けの型安全なSDKで、OpenAI・Anthropic・Gemini・Ollamaなど15以上のプロバイダアダプタを持つ。Zodによるスキーマ推論、ストリーミング、そしてサーバーとクライアントの双方で自動実行されるisomorphic toolsが特徴だ。TanStack Startだけでなく、Next.js・Remix・Expo・Expressでも動く。

// server: /api/chat
import { chat, chatParamsFromRequest, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'

export async function POST(request: Request) {
  const { messages, threadId, runId } = await chatParamsFromRequest(request)
  const stream = chat({ adapter: openaiText('gpt-5.6'), messages, threadId, runId })
  return toServerSentEventsResponse(stream)
}
// client
import { useChat, fetchServerSentEvents } from '@tanstack/ai-react'

function Chat() {
  const { messages, sendMessage, isLoading, stop } = useChat({
    connection: fetchServerSentEvents('/api/chat'),
  })
  // messages[i].parts を type で分岐して描画する
}

モデル名は執筆時点のもので、指定は環境に合わせて読み替えてほしい。RC段階ではあるが、パッケージの更新頻度は高い。shadcn/uiにも@shadcn/helpers/tanstack-aiが入った。UIコンポーネント集ではなく、モデルもAPIキーも使わずに会話のストリーミングを再現するテスト・デモ用のヘルパーだが、周辺が動き始めているサインではある。

導入判断:どこから始めるか

公式のステータス表記とバージョンを踏まえ、筆者の判断で成熟度を整理するとこうなる。

段階

ライブラリ

判断

枯れている

Query / Table / Virtual

迷わず入れてよい

安定

Router / Form

新規案件なら第一候補

RC

Start / AI

新規で条件が合えば可、要件次第

実験的

DB / Store / Pacer / Charts

検証・小規模から

現実的な入り方は決まっている。Queryを入れ、それだけで回るならそこで止める。URL状態の管理が破綻し始めたらRouter、テーブルの要件が膨らんだらTable、フォームの再レンダリングが重くなったらForm。痛みが出た箇所に対応する部品を足していけば、三原則で作られている以上どれも同じ手触りで導入できる。

まとめ

  • TanStackは単一ライブラリではなく、2026年9月時点で18本からなるライブラリ群である
  • 全体がヘッドレス・フレームワーク非依存・型推論ファーストという三原則で作られており、一本覚えれば他も読める
  • Query・Table・Virtualはすでに枯れており、Router・Formも安定圏に入った
  • Startは週間1,230万ダウンロードの規模に達しながら、RC表記のまま1年が経過している
  • DBとAIは設計として面白いが、まだ検証段階と見るのが妥当
  • 導入は全部入れではなく、いま自分のプロジェクトで一番痛んでいる箇所を1つ選ぶところから

参考リンク


DevpediaCode編集部

DevpediaCodeはWeb、AIなどプログラムに関する最新ITテーマの情報を発信するメディアです。

お問合せ下記のURLからお願いします。

https://devpediacode.com/contact