Deployment
Going live is the most rewarding moment in a project — and the most error-prone if you skip steps. This skill is the linear walkthrough plus the pre-launch checklist.
When to use this
- The user wants to deploy a project for the first time.
- The user has deployed before but is hitting build failures.
- The user wants to add a custom domain.
- The user is about to "go live" and needs a checklist.
- The user is configuring environment variables.
Stack-aware
This skill works for two Next.js hosting paths:
- Stack A (Vercel — serverless): Built by the Next.js team. Free hobby tier, 30-second deploys, instant preview URLs per PR, edge runtime, automatic HTTPS. Best for marketing sites, SaaS, anything that fits inside 60-second function timeouts. Pick this by default.
- Stack B (Render — long-running): Real Node servers, no serverless timeouts. Great for AI features, websockets, cron jobs in-process, anything that needs to run for minutes. Cheap at small scale, more flexible for indie projects.
Both are battle-tested. The Vercel walkthrough is the main path; the Render walkthrough is below it. Pick one and don't switch unless you have to.
The opinionated path: Vercel for Next.js
For Next.js projects, Vercel is the right answer. It's free for hobby
use, deploys in 30 seconds, gives you a real *.vercel.app URL, and
handles HTTPS, CDN, edge functions, and previews automatically.
For other frameworks, the alternatives:
- SvelteKit / Astro / Vue → Vercel, Netlify, or Cloudflare Pages
- Remix → Vercel, Netlify, or Cloudflare Workers
- Static sites → Cloudflare Pages (fastest, free)
- Backends with persistent connections → Railway or Render
- Edge-first apps → Cloudflare Workers
The first deploy
Step 1 — Push to GitHub
gh repo create my-app --private --source=. --remote=origin --push
# OR manually:
# Create repo on github.com, then:
git remote add origin git@github.com:USERNAME/REPO.git
git push -u origin main
Step 2 — Connect to Vercel
- Go to vercel.com → Sign in with GitHub
- Click Add New → Project
- Pick the repo from the list
- Vercel auto-detects Next.js — leave the build settings alone
- Click Deploy
In about 60 seconds you have a live URL like my-app-xyz.vercel.app.
Step 3 — Add environment variables
If your app needs env vars (API keys, database URLs):
- Vercel → Project → Settings → Environment Variables
- Add each variable from your local
.env.local - Choose which environments to apply to (Production / Preview / Development)
- Redeploy — env var changes don't take effect until next deploy
Step 4 — Verify the live site
- Visit the live URL in an incognito window
- Open DevTools → Console: any errors?
- Open DevTools → Network: any failed requests?
- Click through every main user flow
- Test on actual mobile (not just DevTools device toolbar)
The pre-launch checklist
Run through this BEFORE sharing the URL with anyone.
Functionality
- All pages load without errors
- All buttons and links go to the right place
- All forms submit successfully
- Authentication works end-to-end (sign up, sign in, sign out, reset password)
- Payments work end-to-end (test mode if Stripe)
- Email sending works (verification, password reset, notifications)
- Search and filters return correct results
- All API integrations return data
- Error states display correctly (404, 500, network failures)
Performance
- Lighthouse Performance score ≥ 90
- LCP < 2.5s
- CLS < 0.1
- Images use
next/imagewith explicit width/height - No render-blocking scripts
- Fonts preloaded via
next/font
SEO
- Every page has a unique
<title>and<meta description> - Open Graph tags set (og:title, og:description, og:image)
- Twitter Card tags set
-
robots.txtandsitemap.xmlpresent - Canonical URLs set
- Structured data (JSON-LD) on relevant pages
Mobile
- Layout works at 375px without horizontal scroll
- Tap targets ≥ 44px
- Touch interactions work (no hover-only)
- Forms use the right keyboard types
- Tested on real iOS Safari AND real Chrome on Android
Security
- No API keys in client-side code
- Server-only env vars don't have
NEXT_PUBLIC_prefix - Auth-protected routes redirect when not signed in
- Database has Row Level Security enabled (Supabase)
- Forms validate on the server, not just the client
- Webhook endpoints verify signatures
- Rate limiting on public mutation endpoints
- Content Security Policy headers configured
Legal
- Privacy Policy page exists
- Terms of Service page exists
- Cookie banner if you use tracking cookies
- GDPR compliance if you have EU users
- Footer links to legal pages
Analytics & monitoring
- Vercel Analytics or alternative installed
- Error monitoring set up (Sentry recommended)
- Uptime monitoring (Better Uptime, UptimeRobot, or Vercel's)
- Custom domain configured (see below)
The alternate path: Render.com [Stack B]
For Next.js apps that need long-running functions (AI features that take 30+ seconds), websockets, in-process cron jobs, or just a non-serverless Node server, Render is the right answer. It runs your app as a real Node process, so there are no serverless timeouts and no cold starts.
When to pick Render over Vercel
| You need... | Pick |
|---|---|
| Marketing site, SaaS, anything fitting a 60-second function | Vercel |
| AI feature with 30s+ response times (no streaming) | Render |
| Websockets, server-sent events, long polling | Render |
| Cron jobs that run inside your app process | Render |
| The cheapest hobby tier ($0 vs Vercel's $0) | Tie |
| The cheapest production tier ($7 vs $20) | Render |
| Edge global routing | Vercel |
| Preview deployments per PR (built-in) | Vercel (Render needs setup) |
Step 1 — Push to GitHub
Same as Vercel.
Step 2 — Create the render.yaml
At your project root, commit a render.yaml with:
services:
- type: web
name: my-app
runtime: node
buildCommand: npm install && npm test && npm run build
startCommand: npm start
envVars:
- key: NODE_ENV
value: production
- key: NEXT_PUBLIC_SUPABASE_URL
sync: false # set in dashboard, don't commit
- key: SUPABASE_SERVICE_ROLE_KEY
sync: false
- key: OPENAI_API_KEY
sync: false
healthCheckPath: /
autoDeploy: true
The buildCommand running tests before build is a free CI gate — if tests
fail, the deploy fails. No separate CI/CD needed for solo projects.
Step 3 — Connect to Render
- Go to render.com → Sign in with GitHub
- New → Blueprint → Connect the repo
- Render reads
render.yamland sets up the service - Add the env vars in the dashboard (the
sync: falseones) - Click "Apply"
First deploy takes ~3 minutes. After that, every push to main deploys
automatically.
Step 4 — Configure custom domain
- Render → Service → Settings → Custom Domains → Add Custom Domain
- Add the DNS records Render gives you at your registrar (CNAME for
www, ALIAS or ANAME for the apex) - SSL is automatic via Let's Encrypt
- Wait for DNS propagation (minutes to hours)
Step 5 — Add cron jobs (optional)
Unlike Vercel, Render lets you run cron jobs as separate services that share your codebase:
services:
- type: web
name: my-app
# ... web service above
- type: cron
name: nightly-cleanup
runtime: node
schedule: "0 2 * * *" # 2am UTC daily
buildCommand: npm install && npm run build
startCommand: npx tsx scripts/nightly-cleanup.ts
The cron service runs the script on schedule and exits. Pay only for the seconds it ran. Much simpler than wiring up Trigger.dev or Inngest for basic scheduled tasks.
Render gotchas to know
- Cold spin-down on the free tier: free services sleep after 15 min of inactivity. First request after sleep takes ~30 seconds. Upgrade to the $7/mo Starter tier to keep it always-on.
- Single-region by default: Render runs in one region (Oregon, Frankfurt, Singapore, etc.). For global sites, put Cloudflare in front for caching.
- No automatic preview deployments unless you enable Pull Request previews in the service settings.
- Build minutes count toward your limit — keep
buildCommandlean. Skip tests in the build if they're slow (move to GitHub Actions instead). - Slow disk on the free tier — for image-heavy apps, use object storage (Supabase Storage, R2) instead of writing to local disk.
Add a custom domain
Step 1 — Buy the domain
Use Cloudflare, Namecheap, or Porkbun. Cheap, no upselling, no spam.
Step 2 — Connect to Vercel
- Vercel → Project → Settings → Domains
- Type your domain (e.g.,
mysite.com) - Vercel shows the DNS records you need to add
Step 3 — Add DNS records at your registrar
Vercel will tell you the exact values:
- An A record for the root (
@) pointing to Vercel's IP - A CNAME for
wwwpointing tocname.vercel-dns.com
If you use Cloudflare for DNS, set the proxy to DNS only (gray cloud), not orange.
Step 4 — Wait
DNS propagates in minutes to hours. Vercel emails you when it's live. SSL is automatic.
Step 5 — Update OAuth redirects
If you have social login, add the new domain to:
- Clerk dashboard → Domains
- Supabase → Auth → URL Configuration
- Each OAuth provider (Google, GitHub) → allowed redirect URIs
Without this, sign-in will fail in production.
Environment variables
Three rules:
- Server-only secrets must NOT have
NEXT_PUBLIC_prefix.STRIPE_SECRET_KEY, notNEXT_PUBLIC_STRIPE_SECRET_KEY. - Public env vars (visible in the browser) must have
NEXT_PUBLIC_prefix. - Different values per environment. Use Vercel's environment scoping (Production / Preview / Development) so test data doesn't leak into prod.
Common env vars:
# Public (browser)
NEXT_PUBLIC_URL=https://yoursite.com
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=ey...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
# Server only
DATABASE_URL=postgres://...
SUPABASE_SERVICE_ROLE_KEY=ey...
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
RESEND_API_KEY=re_...
CLERK_SECRET_KEY=sk_live_...
Common deploy failures and fixes
Build fails: "module not found"
Cause: A package is in devDependencies but used at runtime.
Fix: Move it to dependencies and redeploy.
Build fails: "type error"
Cause: TypeScript errors that didn't show in dev (next dev is more lenient than next build).
Fix: Run npm run build locally to reproduce, then fix.
Build succeeds but page is blank
Cause: Missing environment variables in production.
Fix: Check Vercel → Logs for undefined errors. Add the missing vars and redeploy.
Image doesn't load
Cause: External image domain not whitelisted.
Fix: Add to next.config.ts → images.remotePatterns.
API route returns 500
Cause: Missing env var, database connection failure, or unhandled error. Fix: Check Vercel → Project → Logs (click on the function call to see the stack trace).
Function timeout
Cause: Vercel functions time out at 10s (Hobby) / 60s (Pro). Database queries or external APIs taking too long. Fix: Move slow work to a background job (Trigger.dev, Inngest), or upgrade to Pro.
Stripe webhooks not firing
Cause: Webhook endpoint not registered in Stripe dashboard, or wrong signing secret. Fix: dashboard.stripe.com → Developers → Webhooks → Add endpoint pointing at the prod URL.
Monitoring after launch
The first 24 hours are critical. Watch:
- Vercel Analytics — traffic, top pages, referrers
- Sentry — errors as they happen
- Stripe dashboard (if applicable) — payments succeeding
- Search Console (after 1-2 days) — Google indexing your pages
If something breaks, rollback is one click in Vercel: Deployments → click an older one → "Promote to Production".
What good looks like
- The live URL works in incognito with no errors
- The first deploy was the only manual one — every push to
mainsince has deployed automatically - Every PR opens a unique preview deployment for review
- The custom domain has SSL (lock icon) and resolves to the latest deploy
- Lighthouse passes 90+ on Performance, Accessibility, Best Practices, SEO
- Errors flow to Sentry within seconds
- You can roll back in one click
Common mistakes to avoid
- Skipping the env var step. Locally works, prod is broken because keys are missing.
- Hardcoding URLs. Use
NEXT_PUBLIC_URLenv var, notlocalhost:3000. - Forgetting OAuth redirects on the prod domain. Sign-in with Google fails until you add it.
- Not testing in incognito. Your browser cache hides bugs.
- Skipping the mobile check. A surprising number of bugs only show on small screens.
- Deploying on Friday afternoon. If something breaks, you're working all weekend.
- No staging environment. Use Vercel preview deployments — every PR gets a unique URL.
- Manual env var changes without redeploying. Changes don't take effect until the next deploy.
Going deeper
- Deploy Your Site: https://www.codebooks.ai/deploy
- Custom Domains: https://www.codebooks.ai/custom-domains
- Environment Variables: https://www.codebooks.ai/env-variables
- Performance & Speed: https://www.codebooks.ai/performance
