home / skills / secondsky / claude-skills / bun-tanstack-start

bun-tanstack-start skill

/plugins/bun/skills/bun-tanstack-start

This skill helps you scaffold and run TanStack Start projects on Bun, including routing, server functions, and deployment with Vinxi.

npx playbooks add skill secondsky/claude-skills --skill bun-tanstack-start

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.8 KB
---
name: Bun TanStack Start
description: TanStack Start full-stack React framework with Bun runtime. Use for TanStack Router, server functions, vinxi, or encountering SSR, build, preset errors.
---

# Bun TanStack Start

Run TanStack Start (full-stack React framework) with Bun.

## Quick Start

```bash
# 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
```

## Project Setup

### package.json

```json
{
  "scripts": {
    "dev": "vinxi dev",
    "build": "vinxi build",
    "start": "vinxi start"
  },
  "dependencies": {
    "@tanstack/react-router": "^1.139.0",
    "@tanstack/start": "^1.120.0",
    "react": "^19.2.0",
    "react-dom": "^19.2.0",
    "vinxi": "^0.5.10"
  }
}
```

### app.config.ts

```typescript
import { defineConfig } from "@tanstack/start/config";

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

## File-Based Routing

```
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
```

## Route Components

### Basic Route

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

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

function Home() {
  return <h1>Welcome Home</h1>;
}
```

### Route with Loader

```tsx
// 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>
  );
}
```

### Dynamic Routes

```tsx
// 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>
  );
}
```

## Server Functions

### Define Server Function

```tsx
// 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>
  );
}
```

### Server Function with Context

```tsx
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 Routes

```typescript
// 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 });
  },
});
```

## Root Layout

```tsx
// 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>
  );
}
```

## Error Handling

```tsx
// 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,
});
```

## Search Params

```tsx
// 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,
});
```

## Deployment

### Build for Bun

```bash
NITRO_PRESET=bun bun run build
bun .output/server/index.mjs
```

### Docker

```dockerfile
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"]
```

## Common Errors

| Error | Cause | Fix |
|-------|-------|-----|
| `Cannot find bun:sqlite` | Wrong preset | Set `server.preset: "bun"` |
| `Server function failed` | Network error | Check function definition |
| `Route not found` | File naming | Check route file location |
| `Hydration mismatch` | Server/client diff | Check loader data |

## When to Load References

Load `references/router-api.md` when:
- Advanced routing patterns
- Route guards
- Nested layouts

Load `references/forms.md` when:
- Form handling
- Mutations
- Optimistic updates

Overview

This skill helps you run and troubleshoot TanStack Start full-stack React projects on the Bun runtime. It provides a production-ready setup with Vinxi scripts, file-based routing patterns, server functions, API routes, and deployment guidance for Bun and Docker. Use it to bootstrap projects, resolve common SSR/build/preset errors, and follow conventions for server-side features like bun:sqlite and createServerFn.

How this skill works

The skill inspects project config (app.config.ts, package.json) and the typical TanStack Start file layout to verify Bun preset, Vinxi scripts, and dependency versions. It guides you through route definitions, loaders, dynamic params, server functions, and API file routes, and highlights runtime and build steps for Bun and Docker. It also surfaces common error causes and recommended fixes for hydration, routing, and server-function failures.

When to use it

  • Bootstrapping a TanStack Start app to run on Bun
  • Implementing file-based routes, loaders, or dynamic params
  • Creating server functions that use bun:sqlite or request context
  • Debugging SSR, hydration, or build issues specific to Bun/presets
  • Preparing production builds or Docker images for Bun deployment

Best practices

  • Set server.preset to "bun" in app.config.ts to enable Bun runtime features
  • Keep Vinxi scripts in package.json: dev (vinxi dev), build (vinxi build), start (vinxi start)
  • Use createFileRoute/createAPIFileRoute consistently and match file names to routes for predictable routing
  • Prefer createServerFn for server actions and use getWebRequest for request context and cookies
  • Validate search params and inputs (e.g., zod) to avoid loader/runtime type mismatches
  • Close Bun database handles (bun:sqlite) after queries to prevent locks or leaks

Example use cases

  • Create a users list with loader fetching /api/users and a POST server function to add users using bun:sqlite
  • Build nested routes and dynamic user detail pages with file-based routing (app/routes/users/$userId.tsx)
  • Add guarded server actions that check session via getWebRequest and throw on unauthorized access
  • Fix hydration mismatches by aligning loader data shape between server and client
  • Build and run a production Docker image using oven/bun and the compiled .output server

FAQ

What causes "Cannot find bun:sqlite"?

Usually the server preset is not set to "bun" in app.config.ts; ensure server.preset = "bun" so Bun builtin imports resolve.

How do I debug server function failures?

Check network and runtime errors, verify createServerFn signatures, confirm the server has access to required resources (databases, files), and inspect server logs during vinxi build/start.