Launch

Production

What to verify before real users arrive. Deployment gets you live; this page keeps you safe once you are.

Make It Yours

Kit ships with the author's defaults baked in. Change these before launch:

  • apps/web/src/lib/site-config.ts - name, description, production URL. siteConfig.url feeds metadata, sitemap, robots, and llms.txt
  • packages/api/src/auth/auth.ts - oAuthProxy derives productionURL from VERCEL_PROJECT_PRODUCTION_URL; deploying off Vercel means setting that env var (or replacing the derivation)
  • apps/web/src/app/(docs)/docs/layout.tsx - githubUrl points at the upstream repo
  • public/og.jpg - the OpenGraph image referenced in src/app/layout.tsx

Secrets

  • BETTER_AUTH_SECRET - unique per environment, openssl rand -base64 32. Rotating it invalidates existing sessions
  • Separate GitHub OAuth apps for dev and prod; prod callback is https://your-domain.com/api/auth/callback/github
  • SUPABASE_SERVICE_ROLE_KEY bypasses RLS - it must only appear in server code (apps/web/src/lib/supabase-server.ts and the avatar route are the only consumers)
  • Nothing secret in NEXT_PUBLIC_* - those ship to the browser

The Authorization Model

Two layers, know both:

  1. RLS deny-by-default - every table calls .enableRLS() with no policies, so the anon key gets nothing through PostgREST
  2. tRPC procedures - the real authorization. protectedProcedure requires a session; org-scoped routers verify membership per call (ensureOrganizationAccess in todo-router.ts)

On a hosted Supabase project, also disable the Data API (Settings → API) - local dev already has it off in packages/db/supabase/config.toml. And if you regenerate the auth schema, re-add .enableRLS() to every table.

What Kit Doesn't Ship

Honest gaps. Each is a deliberate "add when you need it":

  • Email delivery - set RESEND_API_KEY (and EMAIL_FROM) in production. Password reset and organization invite emails send through Resend's REST API (packages/api/src/email/send-email.ts); without the key they log to the server console instead of sending
  • Rate limiting - better-auth's built-in limiter covers auth routes (in-memory, so per-instance on serverless), but tRPC procedures and /api/chat have none. Add Vercel WAF rules or a middleware backed by a shared store
  • Billing - Stripe via @better-auth/stripe. Set STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, and STRIPE_PRO_PRICE_ID, then point a Stripe webhook at /api/auth/stripe/webhook (events: checkout.session.completed, customer.subscription.created/updated/deleted). The webhook keeps the subscription table in sync; dashboard/[slug]/billing handles checkout and the billing portal. Subscriptions are org-scoped — only owners/admins can manage them
  • Email verification - verification emails send on signup. Uncomment requireEmailVerification in packages/api/src/auth/auth.ts to block unverified logins
  • Background jobs - no queue. Long work either fits in a request (the chat route caps at maxDuration = 30) or needs Inngest/Trigger.dev/QStash

Database Safety

  • drizzle-kit push has no down migrations. Back up before schema changes: pg_dump $POSTGRES_URL > backup.sql
  • Prefer add-column → backfill → drop-column over destructive changes on live data
  • POSTGRES_URL must be the transaction pooler (:6543); the client already sets prepare: false for it

Cost Guards

The AI chat proxies every message to a model through the AI Gateway. Before opening it to the public, set a spending cap on the gateway key - it's the only per-request variable cost in the stack.

CI Gate

.github/workflows/ci.yml runs on every PR and push to main:

pnpm typecheck && pnpm lint && pnpm format && pnpm test && pnpm build

Green CI is the merge bar. Note apps/web/next.config.js sets typescript.ignoreBuildErrors: true - type safety is enforced by the typecheck step, not the build, so don't skip it.

Launch Checklist

  • site-config.ts (name, description, Twitter), githubUrl, og.jpg updated
  • Unique BETTER_AUTH_SECRET; separate GitHub OAuth apps
  • Production schema pushed; avatars bucket created; Data API disabled (Settings → API)
  • RESEND_API_KEY + EMAIL_FROM set (verification, password reset, invite emails)
  • Stripe keys set + webhook pointed at /api/auth/stripe/webhook
  • Spending cap on AI_GATEWAY_API_KEY
  • Uptime monitor on /api/health
  • Error tracking installed (Monitoring)
  • Database backup taken; restore path tested once

Next Steps

  1. Monitoring - Know when it breaks
  2. Analytics - Know how it's used

On this page