Deployment
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.
| Variable | Notes |
|---|---|
SUPABASE_URL | From the hosted project |
SUPABASE_KEY | The anon/publishable key |
SUPABASE_SERVICE_ROLE_KEY | Server-only. Never expose it to the client. |
NUXT_SUPABASE_SECRET_KEY | Server-only |
NUXT_PUBLIC_SITE_URL | Your 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
| Host | Preset | Notes |
|---|---|---|
| Vercel | auto-detected | The default. @vercel/analytics and speed-insights no-op elsewhere. |
| Cloudflare | cloudflare-pages | This documentation site runs on it. |
| Netlify | netlify | |
| Node / Docker | node-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
notificationsINSERT 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.