Getting Started

Deployment

Getting it onto a real domain, with a hosted database and the migrations applied.

The build output is Nitro, so the app is not tied to a host. Vercel is the default because that is where it is tested; everything below works the same anywhere Nitro has a preset.

The order that matters

Deploying the app before the database exists is the usual way to lose an afternoon. Do it in this order:

Create the hosted Supabase project

Note the project ref, the URL, the anon key and the service-role key.

Push the schema

pnpm supabase link --project-ref <your-ref>
pnpm supabase db push

db push applies the migrations in supabase/migrations/ in order. It does not run the seed — a production database should not contain the demo tenant.

Set the environment variables

Whatever your host calls them. pnpm doctor reads the same manifest the setup wizard writes, so run it locally against your production values first if you want the mistakes to surface on your machine.

Deploy

pnpm build     # Nitro output in .output/

Environment variables

Everything is off unless its flag is set, so a first deploy needs only the Supabase keys. Add subsystems once the thing is up.

VariableNotes
SUPABASE_URLFrom the hosted project
SUPABASE_KEYThe anon/publishable key
SUPABASE_SERVICE_ROLE_KEYServer-only. Never expose it to the client.
NUXT_SUPABASE_SECRET_KEYServer-only
NUXT_PUBLIC_SITE_URLYour canonical origin — used for SEO, sitemap and email links
NUXT_PUBLIC_SITE_URL is easy to forget and fails quietly: emails go out with links to example.com and canonicals point at the wrong origin. Set it before you enable notifications.

The full subsystem list, with which ones need a third-party account, is on the configuration page.

Auth redirect URLs

Supabase rejects OAuth callbacks and password-reset links that point anywhere it was not told about. In the hosted dashboard, under Authentication → URL Configuration, set the site URL to your origin and add your callback to the redirect allow-list. Locally this is handled by supabase/config.toml; hosted, it is not read.

Choosing a host

HostPresetNotes
Vercelauto-detectedThe default. @vercel/analytics and speed-insights no-op elsewhere.
Cloudflarecloudflare-pagesThis documentation site runs on it.
Netlifynetlify
Node / Dockernode-server.output/server/index.mjs and a process manager

Set one with NITRO_PRESET, or in nuxt.config.ts under nitro.preset.

Migrations after the first deploy

Never edit a migration that has been applied to a real database — add a new one. After changing the schema, regenerate the types or the build fails:

pnpm supabase migration new add_something
# edit the file, then:
pnpm supabase db push
pnpm db:types

pnpm db:types regenerates shared/types/database.types.ts from the local database, so keep local and hosted on the same migrations — that is the point of never editing history.

Webhooks

Two subsystems need the hosted project to call back into your deployment, so they cannot be configured until the app has a public URL:

  • Notifications — a database webhook on notifications INSERT pointing at /api/hooks/notification-email, with your secret header.
  • Billing — the Polar webhook pointing at /api/hooks/billing.

Both are CSRF-exempt by route rule and verified by secret in the handler instead, so the secret is the only thing standing between them and the public. Treat it accordingly.