トップ ブログ 時代はTanStack!? TanStack製フレームワーク TanStack Start を触ってみた!

はじめに

みなさん TanStack 製のライブラリは使用していますか?

など、様々な人気のライブラリが TanStack から提供されています。

そんな TanStack が、React 向けの新しいメタフレームワークとして出してきたのが TanStack Start です。

この記事では、先月(2025/11/18)ニューヨークで行われたReact Summit US の中で紹介されていた Tanner Linsley 氏の 「TanStack Start 1.0 - A New Full Stack Framework for React and Friends」 のセッション内容をベースに、実際に TanStack Start を触ってみた感想を交えつつ、その特徴や使い方を紹介していきます。

TanStack Start とは何か?

セッション内での説明を整理すると、TanStack Start は以下のようなフレームワークです。

  • React ベースの フルスタックフレームワーク
  • 次のようなアプリケーションを 1 つのモデルで扱える
    • 動的なサーバサイド Web アプリ
    • フルスタックな SPA
    • 完全クライアントサイド SPA
    • SSG / 静的サイト
  • 他の TanStack 製ライブラリと同様に TypeScript で書かれている
  • 最初から「型安全」を前提に設計が組まれており、API 層だけでなくルーティングや URL、Link コンポーネント、サーバファンクションなどアプリ全体のつながりを型で守ることにこだわっている

特に印象的だったのは、いわゆる「型注釈を書きまくって頑張る TypeScript」ではなく、 「型注釈を書かなくても、勝手により強力な型安全性が効いている TypeScript」 を目指しているという点で、開発者が「型を気にする時間」ではなく「機能をつくる時間」を取り戻せるようにしたい、というメッセージが強調されていました。

以降のセクションでは、この「型安全ファースト」な設計が実際のコードやデモの流れの中でどのように現れてくるのかを見ていきます。

セッションの流れに沿って TanStack Start を紹介する

ここからは、実際のセッションの流れに沿ってトピックを紹介しながら、各ポイントで「自分で触ってみた感想」も差し込む形で書いていきます。

1. 「TypeScript で書かれている」と「型安全に設計されている」は別物

セッションで語られていたこと

スピーカーは最初に、こんな問題提起をしていました。

  • このフレームワークは TypeScript で書かれているだけでなく、最初から型安全に設計されている
  • 「TypeScript で書かれていること」と「型安全であること」は別物である

多くの TypeScript プロジェクトでは、次のような状況になりがちです。

  • 型注釈がコードのあちこちに散らばる
  • 書くのは面倒なので anyas でごまかしがち
  • 必須ではないので、サボろうと思えばサボれてしまう
  • 結果として、どこまでが型で守られているのか分からない状態になっていく

登壇者はこれを、

  • バグへの一本道
  • 開発者の燃え尽き
  • そして最終的にはユーザー体験への悪影響

とまで言っていて、かなり強い問題意識を持っていることが伝わりました。

一方で TanStack Start では、

  • API の設計段階から型推論を最大限に活かすよう緻密に設計されている
  • フレームワーク側で型安全性を強く担保してくれる
  • その結果、「自分で型注釈を書きまくらなくても」コード全体が自然と型安全になる

という方向を徹底して追求しています。

スピーカーは、これを 「型を意識しなくても、勝手に安全になっている状態」(型安全ファースト) と表現していました。
フレームワーク側がこの前提を強く担保してくれることで、

  • 新機能の追加
  • バグ修正
  • リファクタリング
  • アプリ全体の方向転換

といった大きめの変更でも、型システムを信頼しながら安心して進められる、そこがまさに、TanStack Start が他のフレームワークと比べたときの大きな特徴のひとつだと語っていました。

実際のコード比較

ここで、型安全性の違いをイメージしやすいように、簡単なコード例を示します。
下は、TypeScript の一般的な実装例です。

export function loader({
  params: { postId },
}: LoaderArgs<{ params: { postId: string } }>) {
  return fetchPost({ data: postId })
}

export const errorComponent = PostErrorComponent

export const notFoundComponent = ({
  params: { postId },
}: NotFoundComponentProps<{ params: { postId: string } }>) => {
  return ...
}

export function PostComponent() {
  const { postId } = useParams<{ postId: string }>()
  const post = useLoaderData<ReturnType<typeof loader>>()

  return ...
}

下は上記のコードを TanStack Startを使用した場合の実装例です。

export const Route = createFileRoute('/posts/$postId')({
  loader: ({ params: { postId } }) => 
    // 本来は: ({ params: { postId } }: LoaderArgs<{ params: { postId: string } }>)
    // 本来は: postId: string が明示的に必要
    fetchPost({ data: postId }),

  errorComponent: PostErrorComponent,
  // 本来は: export const errorComponent = PostErrorComponent

  component: PostComponent,
  // 本来は: export function PostComponent() { ... }

  notFoundComponent: () => {
    // 本来は: (props: NotFoundComponentProps<{ params: { postId: string } }>)
    // 本来は: props.params.postId から取得

    const { postId } = Route.useParams()
    // 本来は: const { postId } = useParams<{ postId: string }>()

    return ...
  },
})

function PostComponent() {
  const post = Route.useLoaderData()
  // 本来は: const post = useLoaderData<ReturnType<typeof loader>>()

  return ...
}

TanStack Start の例では、一般的な TypeScript のコード例と比べると型注釈が少なく、コード量も少ないことがわかります。ただし、フレームワーク側で型安全性が担保されているため、一般的な TypeScript のコード例よりも安全に動作します。

2. クライアントファーストなフルスタック:SPA のメンタルモデルを守る

セッションで語られていたこと

ここ数年、Next.js App Router や RSC(React Server Components)などの流れもあり、React の世界は「サーバ中心」に大きく振れています。

スピーカーはこれについて、

  • メンタルモデルを大きく変えることをフレームワーク側から要求される
  • 4〜5 年前には「簡単だ」と感じていたことが、むしろ難しくなっている
  • 「アプリを作ってユーザーに届けたい」という観点では、そこまで恩恵を感じないこともある

といった不満・違和感を話していました。

そこで TanStack Start が採用しているのが、「クライアントファーストなフルスタック」 という立ち位置です。

  • 最初の 1 リクエストだけ SSR
  • 2回目以降は SPA として振る舞う
  • ここ 10 年で育ってきた SPA のメンタルモデルを基本的には崩さない

という、ある意味で「古き良き SPA + SSR」のモデルに近い考え方です。

そのうえで、必要なときにだけ SSR / API ルート / サーバファンクションなどのサーバ機能を織り込める というのが、他フレームワークと比べたときの大きな違いになっています。

この設計を支えているのが TanStack Router であり、TanStack Start の「9 割は Router」と説明されていました。

3. TanStack Router が支える型安全なルーティング体験

セッションで語られていたこと

TanStack Start の「心臓部」が TanStack Router です。

TanStack Router は、以下のような特徴を持っています。

  • 最初から 型安全性を前提に 設計されている
  • ルート定義/URL パラメータ/検索パラメータ/リンクなどが、すべて型で守られる
  • ファイルベースルーティング・コードベースルーティング・設定ベースルーティングをすべてサポート
  • 他の meta-framework(Next, Remix など)の上でも、Router 単体で採用可能

Start はこの Router の上に、

  • SSR
  • ストリーミング
  • API ルート
  • サーバファンクション
  • ミドルウェア
  • 透過的なリクエスト・レスポンスライフサイクル

といったサーバ側の機能を重ねることで、フルスタックフレームワークとしての顔を持たせています。

4. CLI デモとローカルでのプロジェクト作成

ここからは、セッション内で行われていた CLI デモをなぞりつつ、
同じ操作をローカルでも試してみた内容を併せて紹介します。

セッションで紹介されていた CLI デモ

セッションでは、TanStack CLI を使って以下のようにプロジェクトを作成していました。

npm create @tanstack/start@latest
# または
pnpm create @tanstack/start@latest

対話形式で、

  • プロジェクト名
  • Tailwind を使うかどうか
  • デプロイ先
  • アドオンを有効にするか

といった項目を選んでいき、最小構成のプロジェクトを生成していました。

実際にローカルでやってみた

npm create @tanstack/start@latest

> npx
> create-start

┌  Let's configure your TanStack Start application
│
◆  What would you like to name your project?
│  -
│
◆  Would you like to use Tailwind CSS?
│  ● Yes / ○ No
│
◆  Select toolchain
│  ● None
│  ○ Biome
│  ○ ESLint
│
◆  Select deployment adapter
│  ○ Cloudflare
│  ○ Netlify
│  ● Nitro (agnostic)
│
◆  What add-ons would you like for your project?
│  ◻ Neon
│  ◻ WorkOS (Add WorkOS authentication to your application.)
│  ◻ Clerk
│  ◻ Convex
│  ◻ Sentry
│  ◻ Prisma
│  ◻ Strapi
│  ◻ Compiler
│  ◻ DB
│  ◻ Drizzle
│  ◻ Form
│  ◻ MCP
│  ◻ oRPC
│  ◻ Shadcn
│  ◻ T3Env
│  ◻ Table
│  ◻ tRPC
│  ◻ Store
│  ◻ Storybook (Integrate Storybook into your application.)
│  ◻ Query
│
◆  Would you like any examples?
│  ◻ TanStack Chat (A chat example that uses TanStack Start and TanStack Store. Features chat with Anthropic Sonnet, chat history and custom prompts.)
└

その後、以下のように dev サーバを起動します。

cd <project-name>
npm run dev

ブラウザで http://localhost:3000にアクセスすると、トップページが表示されます。

5. SSR / SPA / Data-only:ルート単位で選べるレンダリングモード

セッションで語られていたこと

デモでは、次の 3 つのレンダリングモードが紹介されていました。

1. フル SSR(デフォルト)

  • 最初のリクエストでサーバが HTML を描画
  • その後は SPA 的にクライアントで遷移

2. SPAモード(ssr: false

  • サーバでは一切レンダリングしない
  • 純粋なクライアントサイドルートとして扱う

3. Data-onlyモード(ssr: 'data-only'

  • データの取得はサーバで行い、結果をストリーミングでクライアントへ送る
  • コンポーネントの描画自体はクライアントのみで行う

特に Data-only モードはユニークで、SSR は行うが、HTML のレンダリングはサーバでは行わず、クライアント側で描画する、という中間的なモードです。

実際の実装例

dev サーバでは3つのモードの実装例と表示例が用意されていました。

1. フル SSR(デフォルト)

export const Route = createFileRoute('/demo/start/ssr/full-ssr')({
component: RouteComponent,
loader: async () => await getPunkSongs(),
})

2. SPA モード(ssr: false

export const Route = createFileRoute('/demo/start/ssr/spa-mode')({
  ssr: false,
  component: RouteComponent,
})

3. Data-only モード(ssr: 'data-only'

export const Route = createFileRoute('/demo/start/ssr/data-only')({
  ssr: 'data-only',
  component: RouteComponent,
  loader: async () => await getPunkSongs(),
})

違いは createFileRoute のオプション ssr の値だけで切り替えられることがわかります。 オプションを変えるだけで簡単にレンダリングモードを切り替えられるのはわかりやすく、便利だと感じました。

6. Isomorphic コードとサーバファンクション

セッションで語られていたこと

TanStack Start で最も重要な考え方のひとつが、

「すべてのコードはデフォルトで isomorphic(サーバとクライアントの両方で実行される)」

という前提です。

isomorphic とは、書いたコードは、デフォルトでは SSR 時にはサーバで、以降のナビゲーションではクライアントでも実行されるということです。

一方で、「これはサーバだけで動かしたい」という処理は、サーバファンクションとして定義します。

  • サーバファンクション内のコードはサーバでのみ実行
  • クライアントから呼び出した場合は RPC 的にサーバへリクエストが飛ぶ

というイメージです。

実際のサーバファンクション実装例

dev サーバには Todo アプリを例としたサーバファンクションの実装例が載っていました。 サーバでしか動かしたくない処理を、createServerFn でサーバ専用の関数として切り出しているのがポイントです。

const TODOS_FILE = 'todos.json'

async function readTodos() {
  return JSON.parse(
    await fs.promises.readFile(TODOS_FILE, 'utf-8').catch(() =>
      JSON.stringify(
        [
          { id: 1, name: 'Get groceries' },
          { id: 2, name: 'Buy a new phone' },
        ],
        null,
        2,
      ),
    ),
  )
}

const getTodos = createServerFn({ method: 'GET' }).handler(async () => {
  return await readTodos()
})

const addTodo = createServerFn({ method: 'POST' })
  .inputValidator((d: string) => d)
  .handler(async ({ data }) => {
    const todos = await readTodos()
    todos.push({ id: todos.length + 1, name: data })
    await fs.promises.writeFile(TODOS_FILE, JSON.stringify(todos, null, 2))
    return todos
  })

ここでは、readTodos が純粋なユーティリティ関数としてサーバ側で JSON ファイルを読み書きし、getTodos / addTodo がそれぞれ GET / POST のサーバファンクションとして公開されています。この時点ではまだ React コンポーネントは登場せず、「サーバ専用のロジック」をサーバファンクションに閉じ込めておき、後からクライアント側のコンポーネントから RPC 的に呼び出す、という構成になっていました。

7. 型安全な URL 状態管理の強み

セッションで語られていたこと

デモでは、routes/about.tsx にルートを追加し、validateSearch で search パラメータの型を定義したうえで、Link からそのルートへ遷移する一連の流れが紹介されていました。存在しないルートや不正な search を書いた瞬間に TypeScript がエラーで止めてくれる様子が、TanStack Start の「型安全ファースト」な設計をよく表していたのが印象的でした。

ルートファイルを作るだけでボイラープレートが自動生成

routes/about.tsx のように新しいルートファイルを作ると、TanStack Start が自動で雛形コードを埋めてくれます。

  • 開発サーバがファイルシステムを監視している
  • 空のルートファイルができると、自動的に createFileRoute などを含んだコードを書く
  • さらにルートパスを変更すると、ファイル内のパス文字列も自動で更新される

という挙動です。

下が実際に自動で生成されるコードです。

about.tsx

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

export const Route = createFileRoute('/about')({
  component: RouteComponent,
})

function RouteComponent() {
  return <div>Hello "/about"</div>
}

このように、最初から型情報込みのルート定義が自動生成されることで、開発者は自分で細かい型注釈を書かなくても、型安全なパターンの上から開発を始められる点が「型安全ファースト」の設計思想をよく表していると感じました。

Link の to で存在するルートだけが選べる

Link コンポーネントを使うとき、to に指定できるのは「存在するルートだけ」です。

  • 存在しないパスを書くとコンパイルエラー
  • VSCode の補完で、定義済みのルートだけが候補に出てくる

例えば、以下の to= には定義済みのルートパスだけしか指定できず、/about 以外の存在しないパスを書こうとするとコンパイルエラーになります。

<Link to="/about">About</Link>

search パラメータのスキーマを定義すると、Link 側にも反映される

about ルート側で、

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

export const Route = createFileRoute('/about')({
  component: RouteComponent,
  // 本来であれば、ここはバリデータ(Zod など)を使いたいが、デモでは簡単のために型アサーションで代用
  validateSearch: (d) => d as { name: string },
})

function RouteComponent() {
  const { name } = Route.useSearch()
  return <div>Hello "/about" {name}!</div>
}

のように validateSearch を定義すると、Link 側でも name パラメータが必須になります。

<Link to="/about" search={{ name: 'John' }}>
  About
</Link>

登壇者は、大規模 SaaS 開発で URL に大量の状態を持っていた経験から、

「URL 状態管理をちゃんと型で守れるのは、本当に効く」

とかなり強調していました。

8. Q&A から見えた実務的なポイント

セッション後半では Q&A が行われ、実務で気になりそうなポイントに触れていました。

TanStack Start で SSG(静的サイト生成)はできるか?

  • Yes(できる)

Mono repo との相性

  • 巨大な Mono repo でも基本的には問題ないが、「フロントエンドのルートを複数パッケージに分割している」ケースはやや難易度が高い
  • Router の型がプロジェクト全体に「遍在」する設計のため、TS Config が分かれていると扱いが難しい
  • 改善のためのトリック・ハックを検討中とのこと

trpc / Hono / Nest との連携

  • trpc は SSR、Data-only モードとも相性が良い
  • バックエンドフレームワーク(Hono / Nest など)との組み合わせ方は 2 パターン:
    • Start の中に catch-all ハンドラを置いて、既存フレームワークにフォワード
    • 逆に、アプリ全体を既存フレームワークで駆動しておいて、一部のルートでだけ Start を使う
  • trpc は内部で TanStack Query(React Query)を使っており、Start とも自然に統合できる

既存アプリからの移行難易度

  • Remix / React Router → 驚くほど簡単
  • Next.js Pages Router → 比較的簡単
  • Next.js App Router → サーバコンポーネント前提の構造を「ほどいて」、ローダーに寄せる作業が必要

公式には移行ガイドも用意されていて、Cursor などの AI コード変換ツールでほぼ一括変換した、という話も紹介されていました。

大規模コードベースでのパフォーマンス

  • 数千〜数万コンポーネント/ルートでもパフォーマンスは良好
  • 「きめ細かなサブスクリプション」(fine-grained subscriptions)により、必要な部分だけ再レンダリングする設計
  • 型安全性も 1 万ルート規模でテストしており、問題なく動くよう最適化されている

まとめ

ここまで、カンファレンスのセッション内容をなぞりつつ、ところどころ実際に触ってみた内容を交えて TanStack Start を紹介してきました。

特に、「型注釈をこちらでガチガチに書かなくても、フレームワーク側の設計で自然と型安全になる」という 型安全ファースト な思想は、想像以上に魅力的でした。型安全さが効いているのは API やサーバファンクションといったバックエンド寄りの部分にとどまらず、ルート定義や URL、Link コンポーネント、search パラメータなどアプリ全体のつながりを通して一貫して担保されている点も印象的です。

セッション内では主に Linksearch パラメータまわりのデモにフォーカスしていましたが、実際にはルート定義やサーバファンクション、データフェッチ周りなど、他の API にもどこまでこの思想が貫かれているのかは、ぜひ自分の手で触って確かめてみたいところです。

また、実際にプロジェクトを create-start した際に選択したアドオン(Query や tRPC など)に対応するドキュメントやサンプルが、そのまま dev サーバ上から参照できるのも便利だと感じました。環境構築直後から「この構成なら、どう組み合わせていけばいいか」のイメージが掴みやすくなります。

次の個人開発では、実際に TanStack Start を使って小さめのアプリを 1 本作り、その過程やハマりどころを含めた「実践編」の記事を、あらためて Qiita に書いてみようと思います。

最後まで見ていただきありがとうございます。

リンク集

関連OSS

  • React
    サポート対象

    React

    リアクト。Facebookが作成したMVCモデルのView領域を担当するJavascriptのUIライブラリです。

お気軽にお問い合わせください
オープンソースに関するさまざまな課題、OpenStandiaがまるごと解決します。
下記コンテンツも
あわせてご確認ください。