---
name: codebooks-deployment
description: "Use this skill whenever the user wants to deploy a project, set up a custom domain, configure environment variables, or work through a pre-launch checklist. Walk them through the Vercel-first path step by step and don't skip verification."
---

# 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
```bash
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
1. Go to vercel.com → Sign in with GitHub
2. Click **Add New** → **Project**
3. Pick the repo from the list
4. Vercel auto-detects Next.js — leave the build settings alone
5. 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):
1. Vercel → Project → Settings → Environment Variables
2. Add each variable from your local `.env.local`
3. Choose which environments to apply to (Production / Preview / Development)
4. **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/image` with 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.txt` and `sitemap.xml` present
- [ ] 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:

```yaml
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
1. Go to render.com → Sign in with GitHub
2. New → Blueprint → Connect the repo
3. Render reads `render.yaml` and sets up the service
4. Add the env vars in the dashboard (the `sync: false` ones)
5. Click "Apply"

First deploy takes ~3 minutes. After that, every push to `main` deploys
automatically.

### Step 4 — Configure custom domain
1. Render → Service → Settings → Custom Domains → Add Custom Domain
2. Add the DNS records Render gives you at your registrar (CNAME for `www`,
   ALIAS or ANAME for the apex)
3. SSL is automatic via Let's Encrypt
4. 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:

```yaml
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 `buildCommand` lean. 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
1. Vercel → Project → Settings → Domains
2. Type your domain (e.g., `mysite.com`)
3. 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 `www` pointing to `cname.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:**
1. **Server-only secrets** must NOT have `NEXT_PUBLIC_` prefix. `STRIPE_SECRET_KEY`, not `NEXT_PUBLIC_STRIPE_SECRET_KEY`.
2. **Public env vars** (visible in the browser) must have `NEXT_PUBLIC_` prefix.
3. **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 `main` since 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_URL` env var, not `localhost: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


---

This skill is part of the **CodeBooks Vibe Coding Skills Library**.
Browse all skills, install guides, and the source chapters at
https://www.codebooks.ai/skills
