tanstack-start
TanStack Startを使ってCloudflare Workers上にフルスタックアプリをゼロから構築します。SSR・ファイルベースルーティング・サーバー関数・D1+Drizzle・better-auth・Tailwind v4+shadcn/uiに対応しており、ユーザーがTanStack Startに言及した場合や、SSR付きCloudflareアプリのスキャフォールド、React 19+Cloudflare Workersのファイルベースルーティング構成を求めた際に自動で起動します。テンプレートリポジトリは使用せず、プロジェクトごとにすべてのファイルをClaudeが新規生成します。
description の原文を見る
Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per project.
SKILL.md 本文
Cloudflare 上の TanStack Start
ゼロから完全なフルスタックアプリを構築します。Claude がすべてのファイルを生成します — テンプレートのクローンもスカフォルドコマンドも不要です。
スタック: Cloudflare Workers 上の TanStack Start v1(SSR、ファイルベースのルーティング、Nitro 経由のサーバー関数); React 19 + Tailwind v4 + shadcn/ui; D1 + Drizzle; better-auth(Google OAuth + メール/パスワード)。
プロジェクトファイルツリー
PROJECT_NAME/
├── src/
│ ├── routes/
│ │ ├── __root.tsx # ルートレイアウト(HTML シェル、テーマ、CSS インポート)
│ │ ├── index.tsx # ランディングページ / 認証リダイレクト
│ │ ├── login.tsx # ログインページ
│ │ ├── register.tsx # 登録ページ
│ │ ├── _authed.tsx # 認証ガードレイアウトルート
│ │ ├── _authed/
│ │ │ ├── dashboard.tsx # ダッシュボード(スタットカード付き)
│ │ │ ├── items.tsx # アイテムリスト(テーブル)
│ │ │ ├── items.$id.tsx # アイテム編集
│ │ │ └── items.new.tsx # アイテム作成
│ │ └── api/
│ │ └── auth/
│ │ └── $.ts # better-auth API キャッチオール
│ ├── components/
│ │ ├── ui/ # shadcn/ui コンポーネント(自動インストール)
│ │ ├── app-sidebar.tsx # ナビゲーションサイドバー
│ │ ├── theme-toggle.tsx # ライト/ダーク/システム切り替え
│ │ ├── user-nav.tsx # ユーザードロップダウンメニュー
│ │ └── stat-card.tsx # ダッシュボードスタットカード
│ ├── db/
│ │ ├── schema.ts # Drizzle スキーマ(全テーブル)
│ │ └── index.ts # Drizzle クライアントファクトリ
│ ├── lib/
│ │ ├── auth.server.ts # better-auth サーバー設定
│ │ ├── auth.client.ts # better-auth React フック
│ │ └── utils.ts # shadcn/ui 用 cn() ヘルパー
│ ├── server/
│ │ └── functions.ts # サーバー関数(CRUD、認証チェック)
│ ├── styles/
│ │ └── app.css # Tailwind v4 + shadcn/ui CSS 変数
│ ├── router.tsx # TanStack Router 設定
│ ├── client.tsx # クライアントエントリー(hydrateRoot)
│ ├── ssr.tsx # SSR エントリー
│ └── routeTree.gen.ts # 自動生成ルートツリー(編集禁止)
├── drizzle/ # 生成されたマイグレーション
├── public/ # 静的アセット(favicon など)
├── vite.config.ts
├── wrangler.jsonc
├── drizzle.config.ts
├── tsconfig.json
├── package.json
├── .dev.vars # ローカル環境変数(コミット対象外)
└── .gitignore
依存関係
ランタイム:
{
"react": "^19.0.0",
"react-dom": "^19.0.0",
"@tanstack/react-router": "^1.120.0",
"@tanstack/react-start": "^1.120.0",
"drizzle-orm": "^0.38.0",
"better-auth": "^1.2.0",
"zod": "^3.24.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"tailwind-merge": "^3.0.0",
"lucide-react": "^0.480.0"
}
開発環境:
{
"@cloudflare/vite-plugin": "^1.0.0",
"@tailwindcss/vite": "^4.0.0",
"@vitejs/plugin-react": "^4.4.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.0",
"drizzle-kit": "^0.30.0",
"wrangler": "^4.0.0",
"tw-animate-css": "^1.2.0"
}
スクリプト:
{
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"deploy": "wrangler deploy",
"db:generate": "drizzle-kit generate",
"db:migrate:local": "wrangler d1 migrations apply PROJECT_NAME-db --local",
"db:migrate:remote": "wrangler d1 migrations apply PROJECT_NAME-db --remote"
}
ワークフロー
ステップ 1: プロジェクト情報を収集
| 必須 | オプション |
|---|---|
| プロジェクト名(ケバブケース) | Google OAuth 認証情報 |
| 1 行の説明 | カスタムドメイン |
| Cloudflare アカウント | R2 ストレージが必要? |
| 認証方法: Google OAuth、メール/パスワード、または両方 | 管理者メール |
ステップ 2: プロジェクトを初期化
プロジェクトディレクトリとすべての設定ファイルをゼロから作成します。
vite.config.ts — プラグインの順序が重要です。Cloudflare は最初にする必要があります:
import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";
import { tanstackStart } from "@tanstack/react-start/plugin/vite";
import tailwindcss from "@tailwindcss/vite";
import viteReact from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
cloudflare({ viteEnvironment: { name: "ssr" } }),
tailwindcss(),
tanstackStart(),
viteReact(),
],
});
wrangler.jsonc:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "PROJECT_NAME",
"compatibility_date": "2025-04-01",
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry",
"account_id": "ACCOUNT_ID",
"d1_databases": [
{
"binding": "DB",
"database_name": "PROJECT_NAME-db",
"database_id": "DATABASE_ID",
"migrations_dir": "drizzle"
}
]
}
重要なポイント: main は必ず "@tanstack/react-start/server-entry" である必要があります(Nitro サーバーエントリー)。nodejs_compat を使用します(node_compat ではなく)。インタラクティブなプロンプトを避けるために account_id を追加します。
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"paths": { "@/*": ["./src/*"] },
"types": ["@cloudflare/workers-types/2023-07-01"]
},
"include": ["src/**/*", "vite.config.ts"]
}
.dev.vars — openssl rand -hex 32 で BETTER_AUTH_SECRET を生成します:
BETTER_AUTH_SECRET=<generated-hex-32>
BETTER_AUTH_URL=http://localhost:3000
TRUSTED_ORIGINS=http://localhost:3000
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
.gitignore — node_modules、.wrangler、dist、.output、.dev.vars、.vinxi、.DS_Store
その後、インストールして D1 データベースを作成します:
cd PROJECT_NAME && pnpm install
npx wrangler d1 create PROJECT_NAME-db
# database_id を wrangler.jsonc の d1_databases バインディングにコピーします
ステップ 3: データベーススキーマ
src/db/schema.ts — すべてのテーブル。better-auth には以下が必要です: users、sessions、accounts、verifications。CRUD デモ用にアプリケーションテーブル(例: items)を追加します。
D1 固有のルール:
- タイムスタンプに
integerを使用します(Unix エポック)、Date オブジェクトではなく - 主キーに
textを使用します(nanoid/cuid2)、オートインクリメントではなく - バインドされたパラメータを 1 クエリあたり 100 以下に保ちます(大量挿入はバッチ処理)
- D1 では外部キーは常に ON です
src/db/index.ts — Drizzle クライアントファクトリ:
import { drizzle } from "drizzle-orm/d1";
import { env } from "cloudflare:workers";
import * as schema from "./schema";
export function getDb() {
return drizzle(env.DB, { schema });
}
重大: import { env } from "cloudflare:workers" を使用します — process.env ではなく。Drizzle クライアントを各サーバー関数内(リクエストごと)で作成します。モジュールレベルでではなく。
drizzle.config.ts:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./drizzle",
dialect: "sqlite",
});
初期マイグレーションを生成して適用します:
pnpm db:generate
pnpm db:migrate:local
ステップ 4: 認証を設定
src/lib/auth.server.ts — サーバーサイド better-auth:
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/d1";
import { env } from "cloudflare:workers";
import * as schema from "../db/schema";
export function getAuth() {
const db = drizzle(env.DB, { schema });
return betterAuth({
database: drizzleAdapter(db, { provider: "sqlite" }),
secret: env.BETTER_AUTH_SECRET,
baseURL: env.BETTER_AUTH_URL,
trustedOrigins: env.TRUSTED_ORIGINS?.split(",") ?? [],
emailAndPassword: { enabled: true },
socialProviders: {
// 認証情報が提供されている場合は Google OAuth を追加
},
});
}
重大: getAuth() はリクエストごと(ハンドラー/ローダー内)に呼び出す必要があります。モジュールレベルではなく。
src/lib/auth.client.ts — クライアントサイド認証フック:
import { createAuthClient } from "better-auth/react";
export const { useSession, signIn, signOut, signUp } = createAuthClient();
src/routes/api/auth/$.ts — better-auth 用 API キャッチオール:
import { createAPIFileRoute } from "@tanstack/react-start/api";
import { getAuth } from "../../../lib/auth.server";
export const APIRoute = createAPIFileRoute("/api/auth/$")({
GET: ({ request }) => getAuth().handler(request),
POST: ({ request }) => getAuth().handler(request),
});
重大: 認証は API ルート(createAPIFileRoute)を使用する必要があります。サーバー関数(createServerFn)ではなく。better-auth は直接のリクエスト/レスポンスアクセスが必要です。
ステップ 5: サーバー関数
コアパターン — ハンドラー内に常に DB クライアントを作成します:
import { createServerFn } from "@tanstack/react-start";
import { getDb } from "../db";
export const getItems = createServerFn({ method: "GET" }).handler(async () => {
const db = getDb();
return db.select().from(items).all();
});
Zod での入力検証:
export const createItem = createServerFn({ method: "POST" })
.inputValidator(
z.object({
name: z.string().min(1),
description: z.string().optional(),
})
)
.handler(async ({ data }) => {
const db = getDb();
const id = crypto.randomUUID();
await db.insert(items).values({ id, ...data, createdAt: Date.now() });
return { id };
});
保護されたサーバー関数 — 認証をチェックし、未認証の場合はリダイレクトをスロー:
import { redirect } from "@tanstack/react-router";
import { getAuth } from "../lib/auth.server";
async function requireSession(request?: Request) {
const auth = getAuth();
const session = await auth.api.getSession({
headers: request?.headers ?? new Headers(),
});
if (!session) {
throw redirect({ to: "/login" });
}
return session;
}
export const getSessionFn = createServerFn({ method: "GET" }).handler(
async ({ request }) => {
const auth = getAuth();
return auth.api.getSession({ headers: request.headers });
}
);
export const getItems = createServerFn({ method: "GET" }).handler(
async ({ request }) => {
const session = await requireSession(request);
const db = getDb();
return db.select().from(items).where(eq(items.userId, session.user.id)).all();
}
);
ルートローダーパターン — ルート loader のサーバー関数:
export const Route = createFileRoute("/_authed/items")({
loader: () => getItems(),
component: ItemsPage,
});
function ItemsPage() {
const items = Route.useLoaderData();
return <div>{items.map((item) => <div key={item.id}>{item.name}</div>)}</div>;
}
認証ガード(_authed.tsx)— beforeLoad を使用:
export const Route = createFileRoute("/_authed")({
beforeLoad: async () => {
const session = await getSessionFn();
if (!session) {
throw redirect({ to: "/login" });
}
return { session };
},
});
子ルートは Route.useRouteContext() でセッションにアクセスします。
ミューテーション + 無効化 — ミューテーション後、ローダーを再取得するためにルーターを無効化:
function CreateItemForm() {
const router = useRouter();
const handleSubmit = async (data: NewItem) => {
await createItem({ data });
router.invalidate();
router.navigate({ to: "/items" });
};
return <form onSubmit={...}>...</form>;
}
型安全性 — サーバー関数の入出力型に Drizzle の InferSelectModel / InferInsertModel を使用します。認証失敗の場合は常に throw redirect() を使用します — エラーレスポンスではなく。
ステップ 6: アプリシェル + テーマ
src/routes/__root.tsx — @tanstack/react-router の <HeadContent /> と <Scripts /> を含む完全な HTML ドキュメント、<html> に suppressHydrationWarning(SSR + テーマ用)、フラッシュを防ぐためのインラインテーマ初期化スクリプト、グローバル CSS インポート。
src/styles/app.css — @import "tailwindcss"(v4 構文)+ :root と .dark の shadcn/ui CSS 変数。セマンティックトークンのみ。
src/router.tsx:
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
export function createRouter() {
return createTanStackRouter({ routeTree });
}
declare module "@tanstack/react-router" {
interface Register {
router: ReturnType<typeof createRouter>;
}
}
src/client.tsx + src/ssr.tsx — 標準的な TanStack Start エントリーボイラープレート。
shadcn/ui をインストール:
pnpm dlx shadcn@latest init --defaults
pnpm dlx shadcn@latest add button card input label sidebar table dropdown-menu form separator sheet
テーマ切り替え — 3 段階(light → dark → system → light)、localStorage 保持、<html> の .dark クラス。JS のみシステム環境設定検出。CSS @media (prefers-color-scheme) クエリはなし。
コンポーネント in src/components/: app-sidebar.tsx、theme-toggle.tsx、user-nav.tsx、stat-card.tsx。
ステップ 7: CRUD サーバー関数
| 関数 | メソッド | 目的 |
|---|---|---|
getItems | GET | 現在のユーザーのすべてのアイテムを一覧表示 |
getItem | GET | ID で単一アイテムを取得 |
createItem | POST | 新しいアイテムを作成 |
updateItem | POST | 既存アイテムを更新 |
deleteItem | POST | ID でアイテムを削除 |
各サーバー関数: (1) 認証セッションを取得、(2) getDb() 経由でリクエストごと Drizzle クライアントを作成、(3) DB 操作を実行、(4) 型付きデータを返す。ルートローダーは GET 関数を呼び出し。ミューテーションは POST 関数を呼び出してから router.invalidate() を実行。
ステップ 8: ローカルで検証
pnpm dev
- アプリが http://localhost:3000 で読み込まれる
- 新しいアカウントを登録(メール/パスワード)
- ログインとログアウトが機能する
- ダッシュボードがスタットカード付きで読み込まれる
- アイテムの作成、一覧表示、編集、削除
- テーマ切り替えが light -> dark -> system をサイクル
- サイドバーがモバイルで折りたたまれる
- コンソールエラーなし
ステップ 9: 本番環境にデプロイ
デプロイ前のチェックリスト — デプロイ実行前に検証します:
-
wrangler.jsoncに正しいaccount_idがある;mainは"@tanstack/react-start/server-entry"である;compatibility_flagsにnodejs_compatがある - D1 データベースが作成済みで
database_idが設定されている -
.dev.varsがgitignored; ソース内にハードコードされたシークレットがない
本番環境シークレットを設定:
openssl rand -hex 32 | npx wrangler secret put BETTER_AUTH_SECRET
echo "https://PROJECT.SUBDOMAIN.workers.dev" | npx wrangler secret put BETTER_AUTH_URL
echo "http://localhost:3000,https://PROJECT.SUBDOMAIN.workers.dev" | npx wrangler secret put TRUSTED_ORIGINS
# Google OAuth(オプション)
echo "your-client-id" | npx wrangler secret put GOOGLE_CLIENT_ID
echo "your-client-secret" | npx wrangler secret put GOOGLE_CLIENT_SECRET
Google OAuth を使用する場合、Google Cloud Console で本番リダイレクト URI を追加します: https://PROJECT.SUBDOMAIN.workers.dev/api/auth/callback/google。
マイグレーションしてデプロイ:
pnpm db:migrate:remote
pnpm build && npx wrangler deploy
最初のデプロイ後、BETTER_AUTH_URL を実際の Worker URL に更新してから再度デプロイします。
検証: 本番環境 URL でアプリが読み込まれ、認証が機能し、CRUD が機能し、テーマが保持される。
カスタムドメイン(オプション): Cloudflare ダッシュボード → Workers → トリガー → カスタムドメイン。BETTER_AUTH_URL + TRUSTED_ORIGINS シークレット + Google OAuth リダイレクト URI を新しいドメインに更新。再度デプロイします。
よくある問題
| 症状 | 原因 | 修正 |
|---|---|---|
env が undefined | モジュールレベルでアクセス | リクエストハンドラー内でのみ import { env } from "cloudflare:workers" を使用 |
| D1 データベースが見つからない | バインディング不一致 | wrangler.jsonc の d1_databases バインディング名がコードと一致するか確認 |
| 認証リダイレクトループ | URL 不一致 | BETTER_AUTH_URL は実際の URL と完全に一致する必要があります(プロトコル + ドメイン、末尾のスラッシュなし) |
| 認証が黙って失敗 | オリジンが足りない | TRUSTED_ORIGINS シークレットをすべての有効な URL で設定します(カンマ区切り) |
| スタイルが読み込まれない | プラグインが足りない | @tailwindcss/vite プラグインが vite.config.ts にあるか確認 |
| SSR ハイドレーション不一致 | テーマフラッシュ | <html> 要素に suppressHydrationWarning を追加 |
| Cloudflare でビルドが失敗 | 設定不良 | wrangler.jsonc の nodejs_compat フラグと main フィールドを確認 |
| シークレットが反映されない | 再度デプロイなし | wrangler secret put は再度デプロイしません — その後 npx wrangler deploy を実行 |
| 認証エンドポイントが 404 を返す | ルート型が間違っている | better-auth に createServerFn ではなく createAPIFileRoute(API ルート)を使用 |
| "redirect_uri_mismatch" | URI が足りない | Google Cloud Console OAuth リダイレクト URI に本番環境 URL を追加 |
| 不可解な Vite エラー | プラグイン順序 | 順序は必ず: cloudflare() -> tailwindcss() -> tanstackStart() -> viteReact() |
| "Table not found" 500s | マイグレーション欠落 | デプロイ前に pnpm db:migrate:remote を実行 |
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- jezweb
- リポジトリ
- jezweb/claude-skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/jezweb/claude-skills / ライセンス: MIT
関連スキル
agent-browser
AI エージェント向けのブラウザ自動化 CLI です。ウェブサイトとの対話が必要な場合に使用します。ページ遷移、フォーム入力、ボタンクリック、スクリーンショット取得、データ抽出、ウェブアプリのテスト、ブラウザ操作の自動化など、あらゆるブラウザタスクに対応できます。「ウェブサイトを開く」「フォームに記入する」「ボタンをクリックする」「スクリーンショットを取得する」「ページからデータを抽出する」「このウェブアプリをテストする」「サイトにログインする」「ブラウザ操作を自動化する」といった要求や、プログラマティックなウェブ操作が必要なタスクで起動します。
anyskill
AnySkill — あなたのプライベート・スキルクラウド。GitHubを基盤としたリポジトリからエージェントスキルを管理、同期、動的にロードできます。自然言語でクラウドスキルを検索し、オンデマンドでプロンプトを自動ロード、カスタムスキルのアップロードと共有、スキルバンドルの一括インストールが可能です。OpenClaw、Antigravity、Claude Code、Cursorに対応しています。
engram
AIエージェント向けの永続的なメモリシステムです。バグ修正、意思決定、発見、設定変更の後はmem_saveを使用してください。ユーザーが「覚えている」「記憶している」と言及した場合、または以前のセッションと重複する作業を開始する際はmem_searchを使用します。セッション終了前にmem_session_summaryを使用して、コンテキストを保持してください。
skyvern
AI駆動のブラウザ自動化により、任意のウェブサイトを自動化できます。フォーム入力、データ抽出、ファイルダウンロード、ログイン、複数ステップのワークフロー実行など、ユーザーがウェブサイトと連携する必要があるときに使用します。Skyvernは、LLMとコンピュータビジョンを活用して、未知のサイトも自動操作可能です。Python SDK、TypeScript SDK、REST API、MCPサーバー、またはCLIを通じて統合できます。
pinchbench
PinchBenchベンチマークを実行して、OpenClawエージェントの実世界タスクにおけるパフォーマンスを評価できます。モデルの機能テスト、モデル間の比較、ベンチマーク結果のリーダーボード提出、またはOpenClawのセットアップがカレンダー、メール、リサーチ、コーディング、複数ステップのワークフローにどの程度対応しているかを確認する際に使用します。
openui
OpenUIとOpenUI Langを使用してジェネレーティブUIアプリを構築できます。これらはLLM生成インターフェースのためのトークン効率的なオープン標準です。OpenUI、@openuidev、ジェネレーティブUI、LLMからのストリーミングUI、AI向けコンポーネントライブラリ、またはjson-render/A2UIの置き換えについて述べる際に使用します。スキャフォルディング、defineComponent、システムプロンプト、Renderer、およびOpenUI Lang出力のデバッグに対応しています。