Deploying
The template builds with output: "standalone" so the .next/standalone/
directory is a self-contained Node app. Three production paths are
documented; pick whichever you prefer.
Vercel (recommended)
The path of least resistance. Vercel detects Next.js automatically.
- Push your fork to GitHub.
- Import the repo at vercel.com/new.
- In Environment Variables, paste the same six core vars from
.env.local. AddNEXT_PUBLIC_APP_URL=<your-vercel-url>. - Click Deploy.
Vercel runs bun install && bun run build automatically because of
the bun.lock file. The first deploy is usually under 2 minutes.
Stripe webhook on Vercel
After the first deploy, point your Stripe webhook at:
https://<your-vercel-url>.vercel.app/api/stripe/webhookSet the signing secret in Stripe → Developers → Webhooks → "Reveal".
Paste it into Vercel env as STRIPE_WEBHOOK_SECRET. Trigger a
redeploy.
Fly.io
Faster cold-start than serverless and a permissive free tier.
fly launch --image node:22-alpine
fly secrets set NEXT_PUBLIC_SUPABASE_URL=... \
SUPABASE_SERVICE_ROLE_KEY=... \
STRIPE_SECRET_KEY=... \
STRIPE_WEBHOOK_SECRET=...
fly deployThe shipped Dockerfile is multi-stage and uses a non-root user. It
copies only the standalone bundle plus public/ and .next/static/.
Docker (any host)
docker build -t multilang-landing .
docker run -p 3000:3000 \
-e NEXT_PUBLIC_SUPABASE_URL=... \
-e DATABASE_URL=... \
multilang-landingThe build is reproducible — the lockfile pins every dep, the Dockerfile pins the Node tag.
Database
Two paths:
- Supabase Cloud — the recommended default. Click through, copy
the connection URL, run
supabase db push. - Self-hosted Postgres — works fine. Set
DATABASE_URLto your own connection string. RLS policies still apply; the app uses the service-role key only on the server.
Pre-deploy checklist
-
bun run verifyclean -
bun run test:e2e:smokegreen -
bun run verify:rls(against the target database) green - Stripe webhook URL pointed at the production deploy
- DNS A/AAAA + verified email sender for Resend
- Cookie policy + privacy policy + terms reviewed for your jurisdiction
- OG image regenerated for the production URL
- CHANGELOG.md updated with the release version
Going live
After your first deploy, run bun run seed:demo against production
only if you want a public demo. Otherwise sign up your real first
user via the deployed /register page; that user becomes the admin
once you set their role manually:
update auth.users
set raw_user_meta_data = raw_user_meta_data || jsonb_build_object('role', 'admin')
where email = 'you@example.com';Refresh — you should now see the Admin CTA in the header.