Deployment & CI/CD

The bar: you can take a Next.js app from a commit on your laptop to a live production URL that other people depend on, with a pipeline that lints, type-checks, tests, builds, and deploys itself every time you push.

Writing code is half the job. The other half is getting it in front of users without breaking what already works. Shipping to production is where the rubber meets the road, and automation is what separates people who ship reliably from people who ship on a wing and a prayer. You set the pipeline up once, then it protects you on every commit for the life of the project.

This module wires up the full path: a hosting platform that understands Next.js, a workflow engine that runs your checks, and the gates that stop bad code from reaching real users. When you finish, you own the machine that turns a green PR into a live deploy.

12.1 Vercel Deployment

Objective: deploy Next.js applications to Vercel and drive the platform confidently.

Vercel is the platform built by the team behind Next.js, so it understands the framework natively — connect a Git repo and it detects the build, runs it, and serves the output on a CDN. The core mental model is that every branch and every pull request gets its own preview deployment at a unique URL, while your default branch maps to the production deployment. That preview-per-branch flow is the single biggest reason to use it: reviewers click a link and see the actual running app, not a screenshot.

Configuration lives partly in vercel.json and partly in the dashboard. The part that bites people most is environment variables. Vercel scopes them by environment (Development, Preview, Production), and a variable set for Production simply is not there in Preview unless you add it. A build that works locally and dies in the cloud is almost always a missing or mis-scoped env var. Secrets never belong in the repo — they go in the platform.

Beyond the basics, Vercel gives you custom domains with automatic TLS, and edge functions and middleware that run close to the user for things like auth checks, redirects, and A/B routing before a request ever hits your app. Reach for the edge when latency matters; keep heavy logic in normal server code.

  • Drill: connect a repo and ship a first deploy; inspect the auto-detected build settings
  • Drill: set an env var scoped to Preview vs Production and confirm which build sees it
  • Drill: open a PR and visit its preview URL; promote a build to production
  • Drill: attach a custom domain and verify TLS
  • Drill: write a middleware.ts that redirects or rewrites based on the request

12.2 GitHub Actions

Objective: automate build, test, and delivery workflows with GitHub Actions.

GitHub Actions runs YAML workflows stored in .github/workflows/. A workflow is a set of jobs, each job is a sequence of steps, and steps either run shell commands or call a reusable action from the marketplace (like actions/checkout or actions/setup-node). The structure is simple once you see it: triggers at the top, jobs below, steps inside each job.

Workflows fire on triggers. The three you will use constantly are push, pull_request, and schedule (cron). Pull-request triggers are what power CI on every proposed change; schedule is for nightly jobs and housekeeping. The common beginner mistake is running the full suite on every event including irrelevant ones, which burns minutes and slows feedback — scope your triggers.

Two features earn their keep fast. Secrets management keeps tokens out of the repo: reference ${{ secrets.MY_TOKEN }} and GitHub injects it at runtime, masked in logs. Caching (actions/cache, or the cache option in setup-node) reuses your installed dependencies between runs so you are not reinstalling node_modules from scratch every time. Matrix builds let one job fan out across several versions or OSes — the same test suite on Node 18 and 20, for example — without duplicating the YAML.

  • Drill: author a workflow triggered on push and pull_request
  • Drill: add a step that consumes ${{ secrets.TOKEN }} and confirm it is masked in logs
  • Drill: cache dependencies with actions/cache and measure the time saved
  • Drill: define a strategy.matrix across two Node versions
  • Drill: pull a reusable action from the marketplace instead of hand-rolling a step

12.3 CI Pipeline

Objective: build a continuous integration pipeline that blocks broken code before merge.

Continuous integration means every change is automatically verified before it joins the main line. A solid CI pipeline runs four checks in order: lint for style and correctness, type checking with the TypeScript compiler, the test suite, and a full build verification to prove the app actually compiles for production. The curriculum names ESLint as the linter; know that many modern repos — Vets Who Code included — use Biome instead, but the pipeline shape is identical: a lint step, a typecheck step, a test step, a build step.

Code coverage reporting tells you how much of your code the tests actually exercise. Wire your test runner to emit a coverage report in CI so you can see gaps and, optionally, fail the build when coverage drops below a threshold. Coverage is a signal, not a scoreboard — chasing 100% is a trap, but a sudden drop is worth a look.

None of this protects anyone unless the results are enforced. Status checks are the pass/fail results each job reports back to the pull request, and branch protection rules let you require those checks to pass before merge is allowed. Without branch protection, CI is just advice people ignore under deadline. With it, red means the merge button is disabled — full stop.

  • Drill: add lint, typecheck, test, and build steps as CI jobs
  • Drill: emit a coverage report and read it in the run summary
  • Drill: mark the CI jobs as required status checks
  • Drill: enable branch protection so a red check blocks merge

12.4 CD Pipeline

Objective: implement continuous deployment with safe promotion and fast recovery.

Continuous deployment picks up where CI leaves off: once checks are green, the change flows toward production automatically. Automatic preview deployments give every PR a live environment for review; production deployment gates are the guardrails — required checks, approvals, or manual promotion — that decide when a build is allowed to become the live version. The point is that promotion is deliberate and earned, not accidental.

Things break anyway, so plan for it. A rollback strategy is your answer to "the deploy is bad" — on Vercel that can be as simple as promoting the previous known-good deployment back to production. Blue-green deployments take this further: you run two production environments and switch traffic from the old ("blue") to the new ("green") only after the new one is verified, so a bad release is a traffic switch away from reversal instead of a scramble.

Feature flags decouple deploying code from releasing a feature — you ship the code dark, then flip it on for some or all users when you are ready, and flip it off instantly if it misbehaves. Finally, none of this is done at deploy time: post-deployment monitoring (errors, latency, logs) is how you learn a release is bad before your users tell you. Ship, watch, be ready to reverse.

  • Drill: confirm PRs auto-deploy previews and only default-branch merges hit production
  • Drill: roll back by promoting a prior deployment to production
  • Drill: gate a feature behind a flag and toggle it without redeploying
  • Drill: watch error and latency signals on a fresh deploy and define your reverse trigger

Hands-On Lab: Pipeline Setup

Objective: stand up a complete, working CI/CD pipeline for a Next.js application end to end.

What you ship

A Next.js repo with a real pipeline in place:

  • A GitHub Actions workflow in .github/workflows/ triggered on push and pull_request.
  • Jobs for linting, type checking, and running the test suite — each reporting a status check.
  • Automatic Vercel deployments: preview URLs on every PR, production on merge to the default branch.
  • Branch protection rules requiring the CI checks to pass before anyone can merge.

Prove it works by opening a PR that fails a check, watching the merge button lock, fixing it, and watching the same PR go green and deploy.

On the VWC codebase

The Vets Who Code production app runs this pipeline shape for real, with two swaps from the generic curriculum: Biome does the lint-and-format step instead of ESLint/Prettier, and the project is npm-only (yarn/pnpm/bun are blocked), so your setup-node and cache steps must use npm. Everything you build here — real software deployed to a live platform that serves thousands of veterans — is the same discipline that keeps that app shippable on every commit.