A React starter for building polished demos quickly. It runs TanStack Start on Cloudflare Workers with React 19, TanStack Router and Query, shadcn/ui, Tailwind CSS v4, Drizzle ORM on Cloudflare D1, Better Auth email and password sign-in, a TanStack Form example, and an optional streaming AI chat example.
- React 19 with TypeScript
- TanStack Start with file-based routing, deployed natively to Cloudflare Workers
- TanStack Query for data fetching and caching
- Server functions for type-safe server logic
- Drizzle ORM, drizzle-zod, and Cloudflare D1 (SQLite)
- Better Auth with email and password sign-in and a protected route example
- Vite Plus with shared Tractorbeam Oxlint and Oxfmt configuration
- Tailwind CSS v4 and shadcn/ui
- TanStack Form with shadcn/ui fields and Zod validation
- Optional AI SDK chat example using Anthropic Claude and Streamdown
- Node.js 24.11 or newer within Node 24
- pnpm 11.22 or newer
- A Cloudflare account, only for deploying. Local development runs D1 in Miniflare with no account.
- An Anthropic API key if you want to run the chat example
git clone https://github.com/tractorbeamai/quickstart.git
cd quickstart
pnpm installCreate the local D1 database, seed it, and start the app. The first pnpm dev creates .dev.vars with a generated BETTER_AUTH_SECRET; add ANTHROPIC_API_KEY there if you want to use the chat example.
pnpm db:migrate
pnpm db:seed
pnpm devOpen http://localhost:3000, then go to /login to create an account.
The Deploy to Cloudflare button above copies this repository to your GitHub or GitLab account, creates the Worker and its D1 database, and sets up Workers Builds so every push deploys and every pull request gets a Preview. It asks for nothing.
To deploy from your machine instead, run pnpm run deploy (pnpm deploy is a pnpm built-in). It builds, applies migrations, and deploys, creating the D1 database and generating BETTER_AUTH_SECRET the first time; an existing secret is never replaced. Set CLOUDFLARE_ACCOUNT_ID to choose the account if your login has several.
pnpm wrangler login
pnpm run deploy
pnpm wrangler secret put ANTHROPIC_API_KEY # optional, for the chat exampleTo connect an existing Worker yourself, go to Workers & Pages → quickstart → Settings → Builds → Connect in the Cloudflare dashboard. The Worker name must match name in wrangler.jsonc. Use these settings:
| Setting | Value |
|---|---|
| Build command | pnpm build |
| Deploy command | pnpm run deploy |
| Preview command | pnpm run deploy:preview |
| Build variable | PNPM_VERSION = 11.22.0 (the build image ships 10.x) |
The deploy script skips its own build under Workers Builds, so each push builds once. Runtime secrets live in Settings → Variables & Secrets; build variables aren't visible to the Worker.
Each branch gets a Workers Preview, a production-like URL that Workers Builds comments on the pull request. Previews bind DB to a shared quickstart-preview database (the previews block in wrangler.jsonc, plus wrangler.preview-migrations.jsonc), never production. Keep the Preview command as pnpm run deploy:preview, not plain wrangler preview: it migrates that database and gives each new Preview its own BETTER_AUTH_SECRET. Run it yourself to preview the current branch.
Better Auth is configured in src/lib/auth.ts and mounted at /api/auth/* by src/routes/api/auth/$.ts. Its tables live in src/db/auth-schema.ts, which was generated by the Better Auth CLI. Regenerate it if you add plugins that need new tables.
The root route loads the session in beforeLoad through a cached TanStack Query, so every route can read context.session without a server call on each navigation. src/routes/_app/example/account.tsx shows how to protect a route by redirecting to /login. Sign in and sign up with authClient from src/lib/auth-client.ts, then call useSessionChange() from src/hooks/use-session-change.ts to refresh the session and navigate; useSignOut() does both for sign-out.
src/
├── components/
│ ├── ui/ # shadcn/ui components managed by the CLI
│ ├── app-sidebar.tsx # shadcn sidebar-07 block; nav items live here
│ └── nav-*.tsx # Sidebar sections and user menu
├── db/ # Drizzle client, app and auth schema, seed data
├── lib/ # Auth, environment validation, and utilities
├── routes/
│ ├── __root.tsx # Root route; loads the session
│ ├── _app.tsx # Sidebar layout for every page except /login
│ ├── _app/ # Home page and the account, chat, form, posts, and REST examples
│ ├── login.tsx # Sign in and sign up
│ ├── api/auth/$.ts # Better Auth handler
│ └── example/api.chat.ts # Chat example's streaming API route
├── server/ # Server functions
├── routeTree.gen.ts # Generated route tree; do not edit
└── styles.css # Global styles and Tailwind configuration
migrations/ # SQL migrations applied to D1 by Wrangler
wrangler.jsonc # Worker name, D1 binding, compatibility settings
worker-configuration.d.ts # Generated binding types; run `pnpm cf-typegen`
pnpm dev # start the development server
pnpm build # create a production build
pnpm run deploy # build, migrate the remote D1 database, and deploy
pnpm run deploy:preview # build, migrate the preview D1 database, and deploy a Preview
pnpm test # run tests in workerd against a migrated D1
pnpm verify # check + test; the gate agents must pass before finishing
pnpm check # run formatting, lint, and type checks
pnpm format # format the repository with Oxfmt
pnpm lint # lint the repository with Oxlint
pnpm typecheck # run TypeScript checks
pnpm lint:knip # find unused code and dependencies
pnpm cf-typegen # regenerate binding types after editing wrangler.jsonc
pnpm db:push # generate a migration and apply it to local D1
pnpm db:generate # generate a migration from the Drizzle schema
pnpm db:migrate # apply migrations to local D1
pnpm db:migrate:remote # apply migrations to the deployed D1 database
pnpm db:migrate:preview # apply migrations to the shared Preview D1 database
pnpm db:seed # seed example posts into local D1The chat example is isolated from the posts, REST, database, and routing examples.
-
Delete the chat routes and first-party shadcn chat components:
rm src/routes/_app/example/chat.tsx src/routes/example/api.chat.ts rm src/components/ui/bubble.tsx rm src/components/ui/message.tsx rm src/components/ui/message-scroller.tsx
-
Remove the
Chatitem fromnavMaininsrc/components/app-sidebar.tsx. -
Remove
ANTHROPIC_API_KEYfromsrc/lib/env-server.ts,.dev.vars, and.dev.vars.example, and delete the deployed secret withpnpm wrangler secret delete ANTHROPIC_API_KEY. -
Delete the Streamdown
@sourceline fromsrc/styles.cssand removestreamdownfromssr.noExternalinvite.config.ts. If the array is then empty, remove the entiressrblock. -
Remove the chat packages:
pnpm remove @ai-sdk/anthropic @ai-sdk/react @shadcn/react ai streamdown
-
Regenerate the route tree and verify the result:
pnpm build pnpm check
src/routeTree.gen.ts is generated automatically during the build and should not be edited by hand.
Create a file in src/routes/_app/ to render inside the sidebar layout (or directly in src/routes/ for a full-screen page like /login), export a file route, and add it to navMain in src/components/app-sidebar.tsx:
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/_app/about")({
component: AboutPage,
});
function AboutPage() {
return <div>About</div>;
}Use Tailwind classes and cn() for conditional class merging:
import { cn } from "@/lib/utils";
<div className={cn("rounded-lg border p-4", isActive && "border-primary")} />;