TanStack AI × Claude Codeでチャットアプリを作成してみた
みなさん、AIを活用したチャットアプリケーションを開発したことはありますか?
最近ではVercel AI SDKなどを利用して開発する方も多いかと思いますが、TanStackチームが提供するTanStack AIを使うと、型安全かつフレームワーク非依存なAIチャットアプリケーションを簡単に構築することができます。
さらに、今回はAnthropicが提供するClaude Codeを活用し、AIにコードの生成・修正を任せながら開発を進めました。
本記事では、TanStack AIとClaude Codeを組み合わせてチャットアプリを作成した手順やポイントについて紹介します。
TanStack AIとは
TanStack AIは、TanStack QueryやTanStack Routerなどを開発しているTanStackチームが提供する、AIアプリケーション構築のためのSDKです。
主な特徴について以下にまとめます。
| 特徴 | 説明 |
|---|---|
| 型安全性 | TypeScript + Zodによる型推論をサポート |
| フレームワーク非依存 | React、SolidJS、バニラJSなどで利用可能 |
| プロバイダー非依存 | OpenAI、Anthropic、Geminiなど複数のLLMプロバイダーに対応 |
| ストリーミング対応 | SSE(Server-Sent Events)やHTTPストリームによるリアルタイムレスポンス |
| Isomorphic Tools |
toolDefinition()で一度定義し、.server()や.client()で実装可能 |
| Approval Flow | ツール実行の承認フローを組み込み可能 |
TanStack AIのエコシステムは主に以下のパッケージで構成されています。
| パッケージ | 役割 |
|---|---|
@tanstack/ai |
コアライブラリ(chat関数、toolDefinitionなど) |
@tanstack/ai-react |
React向けフック(useChatなど) |
@tanstack/ai-anthropic |
Anthropicアダプター |
使用技術
今回作成したチャットアプリで使用した技術スタックは以下の通りです。
| カテゴリ | 技術 |
|---|---|
| フレームワーク | React |
| 言語 | TypeScript |
| ビルドツール | Vite |
| AIライブラリ | TanStack AI(@tanstack/ai、@tanstack/ai-react、@tanstack/ai-anthropic) |
| スタイリング | TailwindCSS |
| AIコーディング | Claude Code |
手順1: 事前準備
Claude APIキーの発行
今回はAnthropicのClaude APIを利用するため、事前にAPIキーの発行が必要です。
以下のサイトにアクセスし、アカウント作成・APIキーの発行を行ってください。
APIキーの発行手順は以下の通りです。
- 上記URLにアクセスし、Anthropicアカウントでログイン(アカウントがない場合は新規作成)
- ダッシュボードの「API Keys」セクションに移動
- 「Create Key」をクリックしてAPIキーを生成
- 生成されたAPIキーをコピーして安全な場所に保管
手順2: プロジェクトのセットアップ
Vite + Reactでプロジェクトを作成する
まず、Viteを利用してReact + TypeScriptのプロジェクトを作成します。
npm create vite@latest tanstack-chat-app -- --template react-ts
cd tanstack-chat-app
npm install
TanStack AIのパッケージをインストールする
TanStack AIの必要なパッケージをインストールします。
今回はAnthropicのClaude APIを利用するため、@tanstack/ai-anthropicもインストールします。
npm install @tanstack/ai @tanstack/ai-react @tanstack/ai-anthropic
環境変数の設定
プロジェクトのルートに.envファイルを作成し、手順1で発行したAPIキーを設定します。
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxx
プロジェクトの構成は以下のようになります。
tanstack-chat-app/
├── public/
├── src/
│ ├── components/ # UIコンポーネント
│ ├── App.tsx # メインアプリケーション
│ ├── main.tsx # エントリーポイント
│ └── index.css # グローバルスタイル
├── .env # 環境変数(APIキー)
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
└── postcss.config.js
手順3: Claude Codeでの開発フローについて
Claude Codeを利用した開発の流れ
Claude Codeを利用した開発では、以下のような流れで進めました。
1. やりたいことを自然言語で伝える
ターミナル上でClaude Codeを起動し、やりたいことを伝えます。
claude
> TanStack AIの@tanstack/ai-reactのuseChatフックを使って、チャットUIコンポーネントを作成してください。
> メッセージの送信、ストリーミングレスポンスの表示、ローディング状態の管理を含めてください。
2. 生成されたコードの確認と修正を繰り返す
Claude Codeが生成したコードを確認し、必要に応じて追加の指示を行います。
> チャットのメッセージ表示部分にスクロールの自動追従を追加してください。
> また、送信ボタンはローディング中に無効化してください。
3. エラーが出たらClaude Codeにデバッグを依頼する
エラーが発生した際もClaude Codeに解決を依頼することで、スムーズに開発を進めることができます。
> TypeScriptのエラーが出ています。useChatの型定義を確認して修正してください。
Claude Codeで開発する際のポイント
Claude Codeを効率的に使うためのポイントをいくつか紹介します。
- 具体的に指示を出す: 「チャットアプリを作って」ではなく、使用するライブラリやフック名、UIの詳細を具体的に伝えると良い結果が得られます。
- 段階的に開発を進める: 一度に全ての機能を依頼するのではなく、UIの構築 → API連携 → スタイリングのように段階的に進めると精度が高くなります。
-
CLAUDE.mdを活用する: プロジェクトのルートに
CLAUDE.mdファイルを配置しておくと、Claude Codeがプロジェクトのコンテキストを理解した上でコードを生成してくれます。
手順4: チャットUIの実装
サーバーサイド: チャットAPIの作成
今回はVite + Reactの構成のため、バックエンド側は別途Express等のサーバーを立てるか、あるいはAPIルートを用意する形になります。
TanStack AIのchat関数とtoServerSentEventsResponseを利用して、ストリーミングレスポンスを返すAPIを作成します。
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: anthropicText("claude-sonnet-4-20250514"),
messages,
});
return toServerSentEventsResponse(stream);
}
chat関数に渡す主なオプションは以下の通りです。
| オプション | 説明 |
|---|---|
adapter |
LLMプロバイダーのアダプター(anthropicTextなど) |
messages |
チャットのメッセージ履歴 |
tools |
AIが利用可能なツールの定義 |
systemPrompts |
システムプロンプト |
conversationId |
会話の識別子 |
クライアントサイド: useChatフックの利用
TanStack AIの@tanstack/ai-reactパッケージが提供するuseChatフックを使うことで、チャットのステート管理やストリーミング処理を簡潔に実装することができます。
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (input.trim() && !isLoading) {
sendMessage(input);
setInput("");
}
};
return (
<div className="flex flex-col h-screen">
{/* メッセージ表示エリア */}
<div className="flex-1 overflow-y-auto p-4">
{messages.map((message) => (
<div
key={message.id}
className={`mb-4 ${
message.role === "assistant"
? "text-blue-600"
: "text-gray-800"
}`}
>
<div className="font-semibold mb-1">
{message.role === "assistant" ? "AI" : "あなた"}
</div>
{message.parts.map((part, idx) => {
if (part.type === "text") {
return <span key={idx}>{part.content}</span>;
}
return null;
})}
</div>
))}
</div>
{/* 入力フォーム */}
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="メッセージを入力..."
disabled={isLoading}
className="flex-1 border rounded-lg px-4 py-2"
/>
<button
type="submit"
disabled={isLoading}
className="bg-blue-500 text-white px-6 py-2 rounded-lg disabled:opacity-50"
>
送信
</button>
</div>
</form>
</div>
);
}
useChatフックが返す主な値は以下の通りです。
| プロパティ | 説明 |
|---|---|
messages |
チャットのメッセージ一覧(UIMessage[]) |
sendMessage |
メッセージを送信する関数 |
isLoading |
レスポンス待ちかどうかのフラグ |
error |
エラー情報 |
stop |
ストリーミングを停止する関数 |
clear |
メッセージをクリアする関数 |
メッセージの表示について
TanStack AIのメッセージはpartsという配列で構造化されています。
partsにはtext(テキスト)、thinking(思考過程)、tool-call(ツール呼び出し)などの種類があり、それぞれに応じた表示を行うことができます。
{message.parts.map((part, idx) => {
if (part.type === "thinking") {
return (
<div key={idx} className="text-sm text-gray-500 italic">
💭 {part.content}
</div>
);
}
if (part.type === "text") {
return <span key={idx}>{part.content}</span>;
}
if (part.type === "tool-call") {
return (
<div key={idx} className="bg-gray-100 p-2 rounded">
🔧 ツール呼び出し: {part.name}
</div>
);
}
return null;
})}
手順5: ストリーミングの設定
TanStack AIでは、SSE(Server-Sent Events)を使ったストリーミングが標準でサポートされています。
fetchServerSentEventsを利用することで、サーバーからのストリーミングレスポンスをリアルタイムで受け取り、メッセージの表示に反映させることができます。
const { messages, sendMessage, isLoading } = useChat({
connection: fetchServerSentEvents("/api/chat"),
onChunk: (chunk) => {
// チャンクごとの処理(デバッグなど)
console.log("Received chunk:", chunk);
},
onFinish: (message) => {
// ストリーミング完了時の処理
console.log("Stream finished:", message);
},
});
ストリーミングのイベントタイプは以下のような種類があります。
| イベントタイプ | 説明 |
|---|---|
TEXT_MESSAGE_START |
テキストメッセージの開始 |
TEXT_MESSAGE_CONTENT |
テキストコンテンツのストリーミング |
TEXT_MESSAGE_END |
テキストメッセージの終了 |
TOOL_CALL_START |
ツール呼び出しの開始 |
TOOL_CALL_ARGS |
ツール呼び出しの引数 |
TOOL_CALL_END |
ツール呼び出しの終了 |
RUN_FINISHED |
実行完了 |
RUN_ERROR |
エラー発生 |
TanStack AIを使ってみた感想
useChatフックの使いやすさ
useChatフックがメッセージのステート管理、ストリーミング処理、ローディング状態の管理をすべて行ってくれるため、チャットUIの実装が非常にシンプルになりました。
Vercel AI SDKのuseChatと使用感が近いため、移行もしやすいと感じました。
プロバイダーの切り替えが容易
アダプターを変更するだけでLLMプロバイダーを切り替えることができるため、AnthropicからOpenAIやGeminiへの変更がとても簡単です。
アプリケーションのロジックを書き直す必要がないのは大きなメリットだと感じました。
型安全性
TypeScript + Zodによる型推論がしっかりしているため、ツール定義やメッセージの型が自動的に推論されます。
開発中に型エラーで問題に気付くことができるのは安心感があります。
まとめ
TanStack AIは、型安全でプロバイダー非依存なAIアプリケーション開発を可能にする新しいSDKです。
useChatフックを利用することで、ストリーミング対応のチャットUIを少ないコードで実装することができました。
また、Claude Codeを組み合わせることで開発のスピードが大幅に向上し、TanStack AIの新しいAPIにもスムーズにキャッチアップすることができました。
まだalpha版ではありますが、TanStackエコシステムとの統合やIsomorphic Toolsなどの独自機能は非常に魅力的です。
今後のアップデートに期待しつつ、ぜひ皆さんもTanStack AI × Claude Codeでの開発を試してみてはいかがでしょうか。
さらに詳しく知りたい方は以下のリソースを参照してみてください。
最後まで読んでいただきありがとうございました!
Discussion