tanstack-start について
TanStack Start は、サーバー描画アプリを開発するための、TanStack Router を基盤としたフルスタック React フレームワークです。ストリーミング SSR、型安全なサーバー関数、ミドルウェア、API ルートを備え、Nitro プリセットでデプロイできます。
主な機能
- 01サーバーサイドレンダリングとストリーミング
- 02型安全なサーバー RPC 関数
- 03合成可能なミドルウェア
- 04ファイルベースの API ルート
- 05Nitro プリセットによるデプロイ
活用シーン
- 01サーバー描画 React アプリの構築
- 02型安全なクライアント・サーバー間呼び出し
- 03ルートへの認証ミドルウェア追加
- 04アプリケーション API エンドポイントの作成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ tanstack-skills/ tanstack-skills --skill tanstack-startインストール後、Claude Code で/tanstack-startを入力して読み込まれたか確認します。
詳細
TanStack Start スキル
概要
TanStack Start は、TanStack Router をベースに構築され、Vite と Nitro(Vinxi 経由)で動作するフルスタック React フレームワークです。サーバーサイドレンダリング、ストリーミング、サーバー関数(RPC)、ミドルウェア、API ルートを提供し、Nitro プリセットを通じてあらゆるプラットフォームにデプロイできます。
パッケージ: @tanstack/react-start
ルータープラグイン: @tanstack/router-plugin
ビルドツール: Vinxi(Vite + Nitro)
ステータス: RC(リリース候補)
RSC サポート: React Server Components のサポートは現在活発に開発中で、後方互換性を保った v1.x の追加機能として提供される予定です。
インストールとプロジェクトのセットアップ
npx @tanstack/cli create my-app
# Or manually:
npm install @tanstack/react-start @tanstack/react-router react react-dom
npm install -D @tanstack/router-plugin typescript vite vite-tsconfig-paths
プロジェクト構成
my-app/
app/
routes/
__root.tsx # Root layout
index.tsx # / route
posts.$postId.tsx # /posts/:postId
api/
users.ts # /api/users API route
client.tsx # Client entry
router.tsx # Router creation
ssr.tsx # SSR entry
routeTree.gen.ts # Auto-generated route tree
app.config.ts # TanStack Start config
tsconfig.json
package.json
設定(app.config.ts)
import { defineConfig } from '@tanstack/react-start/config'
import viteTsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
vite: {
plugins: [
viteTsConfigPaths({ projects: ['./tsconfig.json'] }),
],
},
server: {
preset: 'node-server', // 'vercel' | 'netlify' | 'cloudflare-pages' | etc.
},
tsr: {
appDirectory: './app',
routesDirectory: './app/routes',
generatedRouteTree: './app/routeTree.gen.ts',
},
})
サーバー関数(createServerFn)
サーバー関数は、クライアントとサーバー間の型安全な RPC 呼び出しを実現します。
基本的なサーバー関数
import { createServerFn } from '@tanstack/react-start'
// GET (data fetching, cacheable)
const getUsers = createServerFn()
.handler(async () => {
const users = await db.query.users.findMany()
return users
})
// POST (mutations, side effects)
const createUser = createServerFn({ method: 'POST' })
.validator((data: { name: string; email: string }) => data)
.handler(async ({ data }) => {
const user = await db.insert(users).values(data).returning()
return user
})
Zod による検証
import { z } from 'zod'
const updateUser = createServerFn({ method: 'POST' })
.validator(
z.object({
id: z.string(),
name: z.string().min(1),
email: z.string().email(),
})
)
.handler(async ({ data }) => {
// data is fully typed: { id: string; name: string; email: string }
return await db.update(users).set(data).where(eq(users.id, data.id))
})
ミドルウェア
ミドルウェアの作成
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().handler(async ({ next }) => {
console.log('Request started')
const result = await next()
console.log('Request completed')
return result
})
コンテキストを使った認証ミドルウェア
const authMiddleware = createMiddleware().handler(async ({ next }) => {
const request = getWebRequest()
const session = await getSession(request)
if (!session?.user) {
throw redirect({ to: '/login' })
}
// Pass typed context to handler
return next({ context: { user: session.user } })
})
ミドルウェアの連結
const adminMiddleware = createMiddleware()
.middleware([authMiddleware])
.handler(async ({ next, context }) => {
// context.user is typed from authMiddleware
if (context.user.role !== 'admin') {
throw redirect({ to: '/unauthorized' })
}
return next({ context: { isAdmin: true } })
})
// Usage
const adminAction = createServerFn({ method: 'POST' })
.middleware([adminMiddleware])
.handler(async ({ context }) => {
// context: { user: User; isAdmin: boolean }
return { success: true }
})
API ルート(サーバールート)
// app/routes/api/users.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'
export const APIRoute = createAPIFileRoute('/api/users')({
GET: async ({ request }) => {
const users = await db.query.users.findMany()
return Response.json(users)
},
POST: async ({ request }) => {
const body = await request.json()
const user = await db.insert(users).values(body).returning()
return new Response(JSON.stringify(user), { status: 201 })
},
})
SSR 戦略
ストリーミング SSR(デフォルト)
export const Route = createFileRoute('/dashboard')({
loader: async () => ({
criticalData: await fetchCriticalData(),
deferredData: defer(fetchSlowData()),
}),
component: Dashboard,
})
function Dashboard() {
const { criticalData, deferredData } = Route.useLoaderData()
return (
<div>
<CriticalSection data={criticalData} />
<Suspense fallback={<Loading />}>
<Await promise={deferredData}>
{(data) => <SlowSection data={data} />}
</Await>
</Suspense>
</div>
)
}
デプロイ
対応プラットフォーム(Nitro プリセット)
// app.config.ts
export default defineConfig({
server: {
preset: 'node-server', // Self-hosted Node.js
// preset: 'vercel', // Vercel
// preset: 'netlify', // Netlify
// preset: 'cloudflare-pages', // Cloudflare Pages
// preset: 'aws-lambda', // AWS Lambda
// preset: 'deno-server', // Deno Deploy
// preset: 'bun', // Bun
},
})
ベストプラクティス
- すべてのサーバー関数の入力にバリデーターを使用する - 実行時の安全性と TypeScript の型推論を確保します
- 認証、ロギング、レート制限などの横断的な関心事には、ミドルウェアを組み合わせます
- データ取得には
createServerFnGET を使用します(キャッシュ可能で、事前ロードできます) - ミューテーションや副作用には
createServerFnPOST を使用します - ルートレベルの認証ガードには
beforeLoadを使用します - TTFB の改善には、重要でないデータに
defer()を使用します - 即時ナビゲーションを実現するため、ルーターに
defaultPreload: 'intent'を設定します - サーバー関数を、それを使用するルートと同じ場所に配置します
よくある落とし穴
- サーバー関数はクライアント側の変数をクロージャーに取り込めません(別のバンドルに抽出されるためです)
- サーバー関数から返すデータはシリアライズ可能でなければなりません
- ローダーで
awaitを忘れると、ストリーミングに問題が発生します - サーバー専用のコードをクライアントバンドルにインポートすると、ビルドエラーが発生します
declare module '@tanstack/react-router'を記述し忘れると、型安全性が失われます
コメント
Markdown 対応