Deploy to production

Put your app online: a hosted Supabase project, the web app on Vercel, the environment variables, Supabase Auth settings, the two webhooks, and the phone app's production build.

Going live takes six steps: a hosted Supabase project with your schema, the web app on Vercel, its environment variables, Supabase Auth pointed at your domain, two webhooks, and a test run. Plan an afternoon for the first time. This page is the short version; the full guides are in the repository's docs/going-to-production/ folder, starting with checklist.mdoc, including Cloudflare, Docker and your own server instead of Vercel.

Before you start

You need accounts with:

  • Supabase for the database, sign-in and file storage
  • Vercel to host the web app
  • Stripe or Lemon Squeezy for payments
  • An email provider: Resend, or any SMTP service
  • Your domain, so you can point it at Vercel

1. Create the Supabase project and push the schema

Create a project in the Supabase dashboard and keep its project ref and database password. Then, from the repository root:

SUPABASE_PROJECT_REF=<your-project-ref> pnpm --filter web supabase:deploy

This links the hosted project and runs supabase db push, which applies every migration in apps/web/supabase/migrations/. Check the Table Editor afterwards: you should see accounts, subscriptions, invitations and the other kit tables.

Never push the seed. Don't add --include-seed, and never run apps/web/supabase/seed.sql against a hosted database. It creates test users with published passwords, two of them super admins.

2. Deploy the web app to Vercel

In Vercel, choose Add New Project and import your repository. Set Root Directory to apps/web; leave the framework (Next.js), build command and output directory on their defaults. The repository is a monorepo, so the build fails without the root directory.

The first deploy usually fails because the environment variables aren't there yet. That's expected: add them in the next step and redeploy.

3. Set the environment variables

Put your production values in apps/web/.env.production.local (git ignores it). Run pnpm dev, open the developer tool at http://localhost:3010/variables and switch it to Production: it checks every value, including the ones another setting makes required. Use Copy to paste the result into Vercel (Settings, then Environment Variables), then redeploy.

The ones every app needs:

VariableValue
NEXT_PUBLIC_SITE_URLYour domain with https:// and no trailing slash
NEXT_PUBLIC_PRODUCT_NAMEYour product's name
NEXT_PUBLIC_SUPABASE_URLSupabase: Project Settings, then API
NEXT_PUBLIC_SUPABASE_PUBLIC_KEYThe project's publishable key
SUPABASE_SECRET_KEYThe project's secret key. It skips row-level security, so it stays on the server
SUPABASE_DB_WEBHOOK_SECRETA random string: openssl rand -base64 32
NEXT_PUBLIC_BILLING_PROVIDERstripe or lemon-squeezy
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETFrom Stripe (step 5 gives you the webhook secret)
LEMON_SQUEEZY_SECRET_KEY, LEMON_SQUEEZY_STORE_ID, LEMON_SQUEEZY_SIGNING_SECRETInstead of the Stripe keys, if you use Lemon Squeezy
MAILER_PROVIDER, EMAIL_SENDERresend or nodemailer, and the address your emails come from
RESEND_API_KEYOnly with Resend; the SMTP settings for nodemailer are in docs/going-to-production/production-environment-variables.mdoc
NEXT_PUBLIC_AUTH_OAUTH_PROVIDERSThe social sign-ins you set up, such as google,github, or none. It defaults to google, so set it if you don't use Google

Values that start with NEXT_PUBLIC_ are built into the page code, so changing one needs a redeploy. The build fails on some wrong public values, such as an http:// site URL. Server secrets are checked when they're first used, so a missing Stripe key shows up in the function logs as Please provide the variable STRIPE_SECRET_KEY.

4. Point Supabase Auth at your domain

In the Supabase dashboard:

  1. Authentication, then URL Configuration: set Site URL to https://yourdomain.com and add https://yourdomain.com/auth/callback** to Redirect URLs. Match your domain exactly, including www if you use it.
  2. Authentication, then Email Templates: replace each template with the matching file in apps/web/supabase/templates/ (confirm email, magic link, reset password, invite, change email, one-time code). With Supabase's default templates, a link opened in a different browser than the one that asked for it fails.
  3. Project Settings, then Authentication, then SMTP Settings: enter your email provider's SMTP details. Supabase's built-in sender is only for testing and allows very few emails an hour.
  4. For social sign-in, turn the providers on under Authentication, then Providers, and list the same ones in NEXT_PUBLIC_AUTH_OAUTH_PROVIDERS.

5. Add the two webhooks

Both need your public domain: Vercel preview URLs sit behind a login, so webhooks can't reach them.

Payments. In Stripe, add a webhook endpoint for https://yourdomain.com/api/billing/webhook with these events: checkout.session.completed, checkout.session.async_payment_succeeded, checkout.session.async_payment_failed, customer.subscription.updated, customer.subscription.deleted and invoice.paid. Copy its signing secret into STRIPE_WEBHOOK_SECRET and redeploy. With Lemon Squeezy, point its webhook at the same URL and put its signing secret in LEMON_SQUEEZY_SIGNING_SECRET.

Database. In Supabase, open Database, then Webhooks, and create a hook on the subscriptions table for the DELETE event, sending to https://yourdomain.com/api/db/webhook with the header X-Supabase-Event-Signature set to your SUPABASE_DB_WEBHOOK_SECRET. It cancels the payment subscription when a subscription row is deleted, for example with its account. Webhooks explains both in more detail.

6. Check that it works

  1. Open https://yourdomain.com/api/healthcheck. It should answer {"services":{"database":true}}.
  2. Sign up with a real email address. The confirmation email should arrive from your provider, and its link should sign you in.
  3. Create a team and invite a second address.
  4. Buy a plan with Stripe in test mode first (card 4242 4242 4242 4242). The plan should show on the billing page, and the webhook in Stripe should list the events as delivered.

Then switch Stripe to live keys and a live webhook. On Supabase's free plan, a project with no activity for a week is paused, so move to a paid plan before real customers depend on it.

The phone app (All-in)

The phone app's EXPO_PUBLIC_ values are built into the app when you build it, and the repository only ships development values. Before your first release build, create apps/native/.env.production (or set the same values in EAS):

VariableProduction value
EXPO_PUBLIC_SUPABASE_URLYour hosted Supabase project's URL
EXPO_PUBLIC_SUPABASE_PUBLIC_KEYIts publishable key
EXPO_PUBLIC_API_BASE_URLYour deployed web app, starting with https://
EXPO_PUBLIC_SITE_URLThe same web app, where the terms and privacy links open

Without them the release build crashes on launch. Keep the sign-in settings the same as the web app's. Building and submitting to the App Store and Google Play is covered in docs/native/building-shipping.mdoc and docs/native/app-store-release.mdoc.

After launch

  • Write your privacy policy, terms and cookie policy in apps/web/content/legal/, and have a lawyer check them.
  • Add apps/web/public/.well-known/security.txt so people know where to report a security problem.
  • Set up error monitoring: docs/monitoring/ covers Sentry.
  • Every schema change ships the same way as step 1: see Migrations.
Live demo

Resets hourly

Get the kit