Local Development
Daily workflows, debugging, and conventions for building with Kit.
Development Workflow
Starting Your Session
-
Start the database:
pnpm db:start -
Start dev servers:
pnpm dev -
Open your tools:
- Web app: http://localhost:3000
- Supabase Studio: http://localhost:54323
- Mobile: Expo Go or a simulator
Daily Commands
# Start development
pnpm dev
# Run one app
pnpm dev:web # Web only
pnpm dev:mobile # Mobile only
pnpm dev:extension # Extension only
pnpm dev:desktop # Desktop only
# Database
pnpm db:reset # Reset local database
pnpm db:push # Push schema changes
# Code quality
pnpm lint # oxlint
pnpm typecheck # TypeScript
pnpm format # oxfmt
pnpm test # VitestMonorepo Structure
apps/
├── web/ # Next.js web app
├── mobile/ # Expo mobile app
├── extension/ # Chrome extension (WXT)
└── desktop/ # Electron desktop app
packages/
├── api/ # tRPC routers + better-auth
├── db/ # Drizzle schema + Supabase config
└── ui/ # Shared React componentsEvery platform shares the API, auth, and database packages. Write business logic once.
Working with the Database
Schema Changes
- Modify schema in
packages/db/src/drizzle-schema.ts - Push changes:
pnpm db:push - Verify in Supabase Studio or Drizzle Studio (
pnpm -F db studio)
No migration files - drizzle-kit push diffs the schema straight against the database.
Adding a Table
// packages/db/src/drizzle-schema.ts
export const post = pgTable("post", (t) => ({
id: t.uuid().notNull().primaryKey().defaultRandom(),
organizationId: t
.text()
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
title: t.text().notNull(),
createdAt: t.timestamp({ withTimezone: true }).notNull().defaultNow(),
})).enableRLS();Always .enableRLS() - authorization lives in tRPC, RLS blocks direct PostgREST access.
API Development
Adding Routes
Add a feature folder in packages/api/src/ with a router and Zod schema:
// packages/api/src/post/post-router.ts
export const postRouter = createTRPCRouter({
list: protectedProcedure.input(postSlugInput).query(async ({ ctx, input }) => {
const organization = await ensureOrganizationAccess(ctx, input.slug);
return ctx.db.query.post.findMany({
where: (p, { eq }) => eq(p.organizationId, organization.id),
});
}),
});Register in packages/api/src/root-router.ts. See the API guide for the full pattern.
Using Routes in Components
import { useSuspenseQuery } from "@tanstack/react-query";
import { useTRPC } from "@/trpc/react";
const trpc = useTRPC();
const { data } = useSuspenseQuery(trpc.todo.list.queryOptions({ slug }));Testing Routes
Routers have Vitest tests next to the code (*-router.test.ts):
pnpm -F @repo/api testFrontend Development
Web App (Next.js)
apps/web/src/app/
├── (auth)/ # Login, register, password reset
├── (dashboard)/ # Protected org dashboard
├── (marketing)/ # Public landing page
├── (docs)/ # These docs
└── api/ # Route handlers (auth, trpc, chat)Mobile App (Expo)
apps/mobile/src/
├── app/ # Expo Router screens
└── utils/ # auth, tRPC client, base-urlAdding a Feature
- Schema - table in
packages/db - API - router in
packages/api - UI - components in
packages/uiif shared - Screens - per app
- Test - router tests + manual pass on each platform
UI Components
Using Shared Components
Import explicit paths:
import { Button } from "@repo/ui/components/button";
import { cn } from "@repo/ui/lib/utils";
<Button variant="outline" onClick={handleClick}>
Save Changes
</Button>Adding Components
Pull from the shadcn registry (base-vega style):
cd packages/ui && pnpm dlx shadcn@latest add <component>Or hand-write in packages/ui/src/components/ - kebab-case filenames.
Code Quality
Kit uses the oxc toolchain - fast, zero-config-ish:
- oxlint - Linting
- oxfmt - Formatting
- TypeScript - Type checking
- Turbo - Cached builds
# Check everything
pnpm lint && pnpm typecheck
# Fix what's fixable
pnpm lint:fix && pnpm format:fixDebugging
Web
- Browser DevTools + React DevTools
- tRPC logs requests in dev (loggerLink)
- TanStack Query Devtools ships in the tree
Mobile
- Expo DevTools and device logs
- Console output in the Metro terminal
API / Database
- Server logs in the
pnpm devterminal - Supabase Studio (http://localhost:54323) for data
pnpm -F db studiofor Drizzle Studio
Common Issues
TypeScript errors
pnpm build && pnpm typecheckDatabase connection issues
pnpm db:stop && pnpm db:start
cat .env # POSTGRES_URL set?Build failures
pnpm clean && pnpm install && pnpm buildEnvironment Variables
One root .env file. Apps and packages load it via dotenv -e ../../.env. Turborepo declares the allow-list in turbo.json (globalEnv).
POSTGRES_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
BETTER_AUTH_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
AI_GATEWAY_API_KEY=...
NEXT_PUBLIC_SUPABASE_URL=...
SUPABASE_SERVICE_ROLE_KEY=...Production values live in your deployment platform, plus .env.production.local for pnpm db:push-remote.
Next Steps
- Explore the architecture - Folder structure
- Understand the API - tRPC guide
- Learn the auth flow - better-auth setup