Overview

Local Development

Daily workflows, debugging, and conventions for building with Kit.

Development Workflow

Starting Your Session

  1. Start the database:

    pnpm db:start
  2. Start dev servers:

    pnpm dev
  3. Open your tools:

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            # Vitest

Monorepo 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 components

Every platform shares the API, auth, and database packages. Write business logic once.

Working with the Database

Schema Changes

  1. Modify schema in packages/db/src/drizzle-schema.ts
  2. Push changes:
    pnpm db:push
  3. 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 test

Frontend 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-url

Adding a Feature

  1. Schema - table in packages/db
  2. API - router in packages/api
  3. UI - components in packages/ui if shared
  4. Screens - per app
  5. 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:fix

Debugging

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 dev terminal
  • Supabase Studio (http://localhost:54323) for data
  • pnpm -F db studio for Drizzle Studio

Common Issues

TypeScript errors

pnpm build && pnpm typecheck

Database connection issues

pnpm db:stop && pnpm db:start
cat .env   # POSTGRES_URL set?

Build failures

pnpm clean && pnpm install && pnpm build

Environment 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

  1. Explore the architecture - Folder structure
  2. Understand the API - tRPC guide
  3. Learn the auth flow - better-auth setup

On this page