Deployment
Ship the Next.js template to production with Vercel or Docker.
The template builds in Next.js standalone output mode and ships with a multi-stage Dockerfile. This page covers the quality gate, both deployment targets, and a pre-launch checklist.
Quality gate
Run before every deploy:
pnpm lint
pnpm buildpnpm build produces the standalone server used by Docker.
Build-time vs. runtime configuration
NEXT_PUBLIC_* variables are inlined at build time and cannot be changed
by the running container — they must be present when pnpm build runs, and
changing them requires a rebuild. Server-only secrets (DATABASE_URL,
BETTER_AUTH_SECRET, CREEM_API_KEY, RESEND_API_KEY, etc.) are read at
runtime. Plan your pipeline so public IDs (e.g. NEXT_PUBLIC_CREEM_PRODUCT_*)
are set at build, not just at runtime.
Vercel
- Import the repository in Vercel.
- Add the environment variables — build-time
NEXT_PUBLIC_*and runtime secrets alike. - Provision a hosted PostgreSQL with pooling and set
DATABASE_URL. - Set
BETTER_AUTH_URLto your production domain and register OAuth callbacks.
Vercel's filesystem is ephemeral. The local image upload writes to
public/uploads, which does not persist on Vercel. Move uploads to object
storage before deploying upload-dependent features — see
Storage.
Docker / self-hosting
next.config.mjs sets output: "standalone", and the Dockerfile is a
multi-stage build:
- Base image
node:22-alpine, with pnpm via Corepack. depsinstalls with--frozen-lockfile;builderrunspnpm build.runnerruns as a non-root user (nextjs, uid1001) and copiespublic,.next/standalone, and.next/static.- Exposes port
3000, setsHOSTNAME=0.0.0.0, and startsnode server.js.
docker build -t soar-next .
docker run -p 3000:3000 --env-file .env soar-nextProvide runtime secrets via --env-file/orchestrator secrets, and make sure
build-time NEXT_PUBLIC_* values are available during docker build.
Pre-launch checklist
- Database — hosted PostgreSQL with pooling; a real migration workflow
(not just
drizzle-kit push— see Database). - Better Auth — production
BETTER_AUTH_URLand a fresh, secretBETTER_AUTH_SECRET. - OAuth — production callback URLs registered for GitHub/Google.
- Creem — live server index, live product IDs, and the webhook
(
/api/payment/notify/creem) registered withCREEM_WEBHOOK_SECRET. - Resend — verified sending domain; correct
fromEmail/supportEmail. - Site/metadata — set
NEXT_PUBLIC_SITE_URLto your domain and update the title/description metadata insrc/app/[locale]/layout.tsx(see Site Config). - AI keys —
OPENAI_API_KEY/REPLICATE_API_TOKENif those features are enabled. - Storage — durable object storage configured before enabling uploads.
Before you call it done
- Confirm webhook lifecycle coverage (cancel/refund/expire) if you charge users.
- Set up database backups, log aggregation, analytics, and error reporting.
- Plan secret rotation.
Smoke test
After deploying, verify each surface end to end:
- Auth: register, verify email, log in, OAuth, reset password, logout.
- Email: verification and contact messages arrive.
- Payment: checkout creates an order; webhook marks it
success. - Dashboard: protected routes redirect when logged out.
- Content: docs, blog, and legal pages render.
- Locale:
/(English) and/zhroutes both work. - AI: chat and media generation respond (if enabled).
- Uploads: image upload works against durable storage (if enabled).
