9.5 KiB
AGENTS.md - AI Coding Agent Guidelines
This document provides comprehensive guidelines for AI coding agents working in this TanStack Start fullstack starter codebase.
Project Overview
- Framework: TanStack Start (React SSR framework with file-based routing)
- Runtime: Bun
- Language: TypeScript (strict mode, ESNext)
- Styling: Tailwind CSS v4
- Database: PostgreSQL with Drizzle ORM
- State Management: TanStack Query
- Routing: TanStack Router (file-based)
- RPC: ORPC (type-safe RPC with contract-first design)
- Build Tool: Vite
- Linter/Formatter: Biome
Build, Lint, and Test Commands
Development
bun dev # Start development server
bun db:studio # Open Drizzle Studio for database management
Building
bun build # Build for production (outputs to .output/)
bun compile # Compile to standalone executable (out/server)
bun serve # Preview production build
Code Quality
bun typecheck # Run TypeScript compiler (tsc -b)
bun fix # Run Biome linter and formatter (auto-fix issues)
biome check . # Check without auto-fix
biome format --write . # Format code only
Database
bun db:generate # Generate migration files from schema
bun db:migrate # Run migrations
bun db:push # Push schema changes directly (dev only)
Testing
Note: No test framework is currently configured. When adding tests:
- Use Vitest or Bun's built-in test runner
- Run single test file:
bun test path/to/test.ts - Run specific test:
bun test -t "test name pattern"
Code Style Guidelines
Formatting (Biome)
Indentation: 2 spaces (not tabs)
Line Endings: LF (Unix-style)
Quotes: Single quotes for strings
Semicolons: As needed (ASI - automatic semicolon insertion)
Arrow Parens: Always use parentheses (x) => x
Example:
const myFunc = (value: string) => {
return value.toUpperCase()
}
Import Organization
Imports are auto-organized by Biome. Order:
- External dependencies
- Internal imports using
@/*alias - Type imports (use
typekeyword when importing only types)
Example:
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { db } from '@/db'
import type { ReactNode } from 'react'
TypeScript
Strict Mode: Enabled with additional strictness flags
strict: truenoUncheckedIndexedAccess: true- Array/object indexing returnsT | undefinednoImplicitOverride: truenoFallthroughCasesInSwitch: true
Module Resolution: bundler mode with verbatimModuleSyntax
- Always use
.ts/.tsxextensions in imports - Use
@/*path alias forsrc/*
Type Annotations:
- Always annotate function parameters and return types for public APIs
- Prefer explicit types over
any - Use
typefor object shapes,interfacefor extendable contracts - Use
Readonly<T>for immutable props
Example:
function getTodos(): Promise<Todo[]> {
return db.query.todoTable.findMany()
}
type Props = Readonly<{
children: ReactNode
}>
Naming Conventions
- Files: kebab-case for utilities, PascalCase for components
utils.ts,todo.tsx,NotFound.tsx
- Routes: Use TanStack Router conventions
routes/index.tsx→/routes/todo.tsx→/todoroutes/__root.tsx→ Root layout
- Components: PascalCase function declarations
- Functions: camelCase
- Constants: UPPER_SNAKE_CASE for true constants, camelCase for config objects
- Types/Interfaces: PascalCase
React Patterns
Components: Use arrow functions (enforced by Biome rule useArrowFunction)
const MyComponent = ({ title }: { title: string }) => {
return <div>{title}</div>
}
Server Functions: Use TanStack Start's createServerFn
const getTodos = createServerFn({ method: 'GET' }).handler(async () => {
const todos = await db.query.todoTable.findMany()
return todos
})
Routing: Use createFileRoute for route definitions
export const Route = createFileRoute('/todo')({
component: Todo,
})
Data Fetching: Use TanStack Query hooks
useSuspenseQueryfor guaranteed datauseQuerywhen data might be optional
Props: No direct prop mutations (enforced by noReactPropAssignments)
Database Schema (Drizzle)
- Define schemas in
src/db/schema/*.ts - Export from
src/db/schema/index.ts - Use PostgreSQL types from
drizzle-orm/pg-core - Use
uuidv7()for primary keys (requires PostgreSQL extension) - Always include
createdAtandupdatedAttimestamps
Example:
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
import { sql } from 'drizzle-orm'
export const myTable = pgTable('my_table', {
id: uuid('id').primaryKey().default(sql`uuidv7()`),
name: text('name').notNull(),
createdAt: timestamp('created_at', { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdateFn(() => new Date()),
})
Environment Variables
- Use
@t3-oss/env-corefor type-safe env validation - Define schema in
src/env.ts - Server vars: No prefix
- Client vars: Must have
VITE_prefix - Always validate with Zod schemas
Error Handling
- Use try-catch for async operations
- Throw errors with descriptive messages
- Prefer Result types or error boundaries for user-facing errors
- Log errors appropriately (avoid logging sensitive data)
Styling (Tailwind CSS)
- Use Tailwind v4 utility classes
- Import styles via
@/styles.css?url - Prefer composition over custom CSS
- Use responsive modifiers:
sm:,md:,lg: - Use Chinese text for UI when appropriate (as seen in codebase)
File Structure
src/
├── components/ # Reusable React components
├── db/
│ ├── schema/ # Drizzle schema definitions
│ └── index.ts # Database instance
├── lib/ # Utility functions
├── orpc/ # ORPC (RPC layer)
│ ├── contracts/ # Contract definitions (input/output schemas)
│ ├── handlers/ # Server-side procedure implementations
│ ├── middlewares/ # Middleware (e.g., DB provider)
│ ├── contract.ts # Contract aggregation
│ ├── router.ts # Router composition
│ ├── server.ts # Server instance
│ ├── client.ts # Isomorphic client
│ └── types.ts # Type utilities
├── routes/ # TanStack Router file-based routes
│ ├── __root.tsx # Root layout
│ ├── index.tsx # Home page
│ └── api/rpc.$.ts # ORPC HTTP endpoint
├── env.ts # Environment variable validation
├── index.ts # Application entry point
├── router.tsx # Router configuration
├── routeTree.gen.ts # Auto-generated (DO NOT EDIT)
└── styles.css # Global styles
Important Notes
- DO NOT edit
src/routeTree.gen.ts- it's auto-generated - DO NOT commit
.envfiles - use.env.examplefor templates - DO run
bun fixbefore committing to ensure code quality - DO use the
@/*path alias instead of relative imports - DO leverage React Compiler (babel-plugin-react-compiler) - avoid manual memoization
Git Workflow
- Make changes following the style guidelines above
- Run
bun fixto auto-format and lint - Run
bun typecheckto ensure type safety - Test changes locally with
bun dev - Commit with clear, descriptive messages
Common Patterns
Adding a New Route
- Create
src/routes/my-route.tsx - Export route with
createFileRoute - Route tree auto-updates on save
Adding Database Table
- Create schema in
src/db/schema/my-table.ts - Export from
src/db/schema/index.ts - Run
bun db:generateto create migration - Run
bun db:migrateto apply migration
Creating Server Function
const myServerFn = createServerFn({ method: 'POST' })
.validator((data) => mySchema.parse(data))
.handler(async ({ data }) => {
// Server-side logic here
return result
})
Creating ORPC Procedures
Step 1: Define Contract (src/orpc/contracts/my-feature.ts)
import { oc } from '@orpc/contract'
import { z } from 'zod'
export const myContract = {
get: oc.input(z.object({ id: z.uuid() })).output(mySchema),
create: oc.input(createSchema).output(mySchema),
}
Step 2: Implement Handler (src/orpc/handlers/my-feature.ts)
import { os } from '@/orpc/server'
import { dbProvider } from '@/orpc/middlewares'
export const get = os.myFeature.get
.use(dbProvider)
.handler(async ({ context, input }) => {
const item = await context.db.query.myTable.findFirst(...)
return item
})
Step 3: Register in Contract & Router
// src/orpc/contract.ts
export const contract = {
myFeature: myContract,
}
// src/orpc/router.ts
import * as myFeature from './handlers/my-feature'
export const router = os.router({ myFeature })
Step 4: Use in Component
import { orpc } from '@/orpc'
const query = useSuspenseQuery(orpc.myFeature.get.queryOptions({ id }))
const mutation = useMutation(orpc.myFeature.create.mutationOptions())
Last Updated: 2026-01-18 Project Version: Based on package.json dependencies