mcpField
B
bun-tanstack-start

bun-tanstack-start

github.com/secondsky開発240418 回インストール5 週間前に更新
リポジトリ

bun-tanstack-start について

Bun で TanStack Start のフルスタック React プロジェクトを動かす開発者向けガイドです。ルーティング、サーバー関数、API、デプロイ、依存関係の安全な導入方法を扱います。

主な機能

  1. 01Bun でのプロジェクト作成と起動
  2. 02Bun サーバープリセットの設定
  3. 03ファイルベースと動的ルーティング
  4. 04サーバー関数と API ルート
  5. 05Bun ビルドと Docker デプロイ
  6. 06依存関係の導入時のセキュリティ

活用シーン

  1. 01TanStack Start アプリの構築
  2. 02ルート用データの読み込み
  3. 03サーバー側からのデータベースアクセス
  4. 04Bun アプリのビルドとデプロイ

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/secondsky/claude-skills --skill bun-tanstack-start

インストール後、Claude Code で/bun-tanstack-startを入力して読み込まれたか確認します。

詳細

Bun TanStack Start

Bun で TanStack Start(フルスタック React フレームワーク)を実行します。

クイックスタート

# Create new TanStack Start project
bunx create-tanstack-start@latest my-app
cd my-app

# Install dependencies
bun install

# Development
bun run dev

# Build
bun run build

# Preview
bun run start

安全なインストール

bunx create-tanstack-start のようなスキャフォールディングツールは、リモートコードをダウンロードして実行します。実行する前に、サプライチェーンセキュリティのベストプラクティスに従ってください。

  • インストール後スクリプトをブロックする — Bun はデフォルトでこれらを無効にします。package.json の trustedDependencies を使って、特定のパッケージを許可してください。
  • クールダウン期間を設ける — bunfig.toml の minimumReleaseAge を設定し、新しいバージョンがリリースされてから 7 日間待つようにしてください。
  • インストール前に監査する — socket package score npm <pkg> を実行するか、socket npm install <pkg> を使ってパッケージを確認してください。

Socket CLI の統合、クールダウンの設定、ロックファイルの検証、CI による適用を含むセキュリティ設定の詳細については、dependency-upgrade スキルを読み込んでください。

プロジェクトのセットアップ

package.json
{
  "scripts": {
    "dev": "vinxi dev",
    "build": "vinxi build",
    "start": "vinxi start"
  },
  "dependencies": {
    "@tanstack/react-router": "^1.170.18",
    "@tanstack/start": "^1.120.0",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "vinxi": "^0.5.10"
  }
}
app.config.ts
import { defineConfig } from "@tanstack/start/config";

export default defineConfig({
  server: {
    preset: "bun",
  },
});

ファイルベースのルーティング

app/
├── routes/
│   ├── __root.tsx       # Root layout
│   ├── index.tsx        # /
│   ├── about.tsx        # /about
│   ├── users/
│   │   ├── index.tsx    # /users
│   │   └── $userId.tsx  # /users/:userId
│   └── api/
│       └── users.ts     # /api/users
└── client.tsx

ルートコンポーネント

基本的なルート
// app/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/")({
  component: Home,
});

function Home() {
  return <h1>Welcome Home</h1>;
}
ローダー付きルート
// app/routes/users/index.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/users/")({
  loader: async () => {
    const response = await fetch("/api/users");
    return response.json();
  },
  component: Users,
});

function Users() {
  const users = Route.useLoaderData();

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
動的ルート
// app/routes/users/$userId.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/users/$userId")({
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.userId}`);
    return response.json();
  },
  component: UserDetail,
});

function UserDetail() {
  const user = Route.useLoaderData();
  const { userId } = Route.useParams();

  return (
    <div>
      <h1>{user.name}</h1>
      <p>User ID: {userId}</p>
    </div>
  );
}

サーバー関数

サーバー関数の定義
// app/routes/users/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/start";
import { Database } from "bun:sqlite";

const getUsers = createServerFn("GET", async () => {
  const db = new Database("data.sqlite");
  const users = db.query("SELECT * FROM users").all();
  db.close();
  return users;
});

const createUser = createServerFn("POST", async (name: string) => {
  const db = new Database("data.sqlite");
  db.run("INSERT INTO users (name) VALUES (?)", [name]);
  db.close();
  return { success: true };
});

export const Route = createFileRoute("/users/")({
  loader: () => getUsers(),
  component: Users,
});

function Users() {
  const users = Route.useLoaderData();

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const formData = new FormData(e.currentTarget);
    const name = formData.get("name") as string;
    await createUser(name);
    // Refetch or update state
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input name="name" placeholder="Name" />
        <button type="submit">Add User</button>
      </form>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
コンテキスト付きサーバー関数
import { createServerFn } from "@tanstack/start";
import { getWebRequest } from "@tanstack/start/server";

const getSession = createServerFn("GET", async () => {
  const request = getWebRequest();
  const cookies = request.headers.get("Cookie");
  // Parse and validate session
  return { userId: "123", role: "admin" };
});

const protectedAction = createServerFn("POST", async (data: any) => {
  const session = await getSession();

  if (session.role !== "admin") {
    throw new Error("Unauthorized");
  }

  // Perform action
  return { success: true };
});

API ルート

// app/routes/api/users.ts
import { createAPIFileRoute } from "@tanstack/start/api";
import { Database } from "bun:sqlite";

export const Route = createAPIFileRoute("/api/users")({
  GET: async ({ request }) => {
    const db = new Database("data.sqlite");
    const users = db.query("SELECT * FROM users").all();
    db.close();

    return Response.json(users);
  },

  POST: async ({ request }) => {
    const { name } = await request.json();

    const db = new Database("data.sqlite");
    db.run("INSERT INTO users (name) VALUES (?)", [name]);
    db.close();

    return Response.json({ success: true });
  },
});

ルートレイアウト

// app/routes/__root.tsx
import { createRootRoute, Link, Outlet } from "@tanstack/react-router";

export const Route = createRootRoute({
  component: Root,
});

function Root() {
  return (
    <html>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>My App</title>
      </head>
      <body>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/users">Users</Link>
          <Link to="/about">About</Link>
        </nav>
        <main>
          <Outlet />
        </main>
      </body>
    </html>
  );
}

エラー処理

// app/routes/users/$userId.tsx
export const Route = createFileRoute("/users/$userId")({
  loader: async ({ params }) => {
    const response = await fetch(`/api/users/${params.userId}`);
    if (!response.ok) {
      throw new Error("User not found");
    }
    return response.json();
  },
  errorComponent: ({ error }) => (
    <div>
      <h1>Error</h1>
      <p>{error.message}</p>
    </div>
  ),
  pendingComponent: () => <div>Loading...</div>,
  component: UserDetail,
});

検索パラメーター

// app/routes/users/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

const searchSchema = z.object({
  page: z.number().default(1),
  limit: z.number().default(10),
  search: z.string().optional(),
});

export const Route = createFileRoute("/users/")({
  validateSearch: searchSchema,
  loader: async ({ search }) => {
    const { page, limit, search: query } = search;
    // Fetch with pagination
    return fetchUsers({ page, limit, query });
  },
  component: Users,
});

デプロイ

Bun 向けのビルド
NITRO_PRESET=bun bun run build
bun .output/server/index.mjs
Docker
FROM oven/bun:1 AS builder

WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build

FROM oven/bun:1

WORKDIR /app
COPY --from=builder /app/.output ./output

EXPOSE 3000

CMD ["bun", ".output/server/index.mjs"]

よくあるエラー

エラー原因解決方法
Cannot find bun:sqliteプリセットが正しくありませんserver.preset: "bun" を設定します
Server function failedネットワークエラー関数の定義を確認します
Route not foundファイル名ルートファイルの場所を確認します
Hydration mismatchサーバーとクライアントの差異ローダーのデータを確認します

リファレンスを読み込むタイミング

次の場合は references/router-api.md を読み込んでください。

  • 高度なルーティングパターン
  • ルートガード
  • ネストされたレイアウト

次の場合は references/forms.md を読み込んでください。

  • フォーム処理
  • ミューテーション
  • 楽観的更新

コメント