What You'll Build
You will build a complete production pipeline for a Next.js application — a continuous integration and continuous deployment workflow that takes a code change from commit to live deployment automatically, with every Module 5 production concern wired in along the way. The pipeline will, on each change, install dependencies, validate that required environment variables are present, run the fast unit and component tests, build the application, run the end-to-end tests against the build, and then deploy — to a managed platform or a self-hosted target — with monitoring and structured logging active in the deployed app. By the end you will have a workflow where pushing a change runs the full quality gate and, if it passes, ships the change, so deployment is a routine, verified, reversible event rather than a manual, anxious one.
This is the capstone of the production module because it integrates everything Module 5 covered into the single workflow that ties them together: configuration discipline gates the build, the testing strategy forms the quality gate, the deployment model is the pipeline's target, and monitoring and logging make the deployed result observable. The central idea you will practise is that production readiness is not a checklist you run once but an automated pipeline that enforces the checks on every change — so a regression cannot reach users without first failing a test, a misconfigured deployment fails fast on a missing variable rather than in front of users, and a bad deploy can be rolled back. Building this pipeline is what turns the individual production practices into a system that makes shipping safe by default, which is exactly the difference between an application that happens to be deployed and one that is genuinely operated as a product.
Prerequisites
- A working Next.js application in a Git repository connected to a CI/CD provider (e.g. a hosted CI service or your platform's built-in pipeline).
- Understanding of environment variables and the discipline of keeping secrets out of the build and validating required ones.
- A test suite with fast unit/component tests and end-to-end tests, and knowledge of running them in CI against a test database.
- Familiarity with the production build, the build report, and the chosen deployment model (managed platform or self-hosted).
- Awareness of monitoring, structured logging, and how observability is instrumented in the application.
- Knowledge that secrets are supplied at runtime from the platform or host secret store, never baked into the build.
Setup & Project Structure
Plan the pipeline as an ordered sequence of stages where each stage is a gate that must pass before the next runs, so a failure stops the pipeline before anything reaches users. The stages flow from cheapest-and-fastest to most-expensive, so quick failures are caught early: install, validate configuration, run fast unit tests, build, run end-to-end tests against the build, then deploy. Laying this out as an ordered gate sequence first makes the central principle concrete — that the pipeline's job is to refuse to deploy anything that fails any check, in the order that surfaces failures soonest.
# CI/CD pipeline structure (conceptual, e.g. a workflow file):
# .github/workflows/ci.yml (or your provider's equivalent)
#
# on: push / pull_request
# jobs:
# pipeline:
# steps:
# 1. checkout code
# 2. install dependencies (npm ci)
# 3. validate required env vars present (fail fast if missing)
# 4. run unit + component tests (Vitest) \u2190 fast gate
# 5. build the app (next build) + read the report
# 6. run end-to-end tests (Playwright) vs a test DB \u2190 integration gate
# 7. deploy (managed platform or self-hosted) \u2190 only if all gates passed
# 8. (deployed app has monitoring + structured logging active)
#
# Secrets (DATABASE_URL, SESSION_SECRET, ...) come from the CI/platform secret
# store \u2014 injected at the steps that need them, NEVER committed to the repo.
npm ci # reproducible install from the lockfileStep 1 — Foundation
Step 1 establishes the pipeline trigger, the reproducible install, and the configuration gate. The pipeline runs on every push and pull request, installing dependencies reproducibly from the lockfile so the CI environment matches what was committed. The configuration gate then validates that every required environment variable is present, failing immediately with a clear message if one is missing — applying the fail-fast configuration discipline at the pipeline level so a misconfigured deployment is stopped here rather than in production. Building this foundation first means every later stage runs in a consistent, validated environment.
# Steps 1-3: trigger, reproducible install, configuration gate
# (workflow excerpt)
on:
push: { branches: [main] }
pull_request:
jobs:
pipeline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci # reproducible install from lockfile
- name: Validate required env
run: node scripts/check-env.js # fails fast if a required var is missing
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} # from CI secret store
SESSION_SECRET: ${{ secrets.SESSION_SECRET }} # NOT committed
# scripts/check-env.js
# const required = ['DATABASE_URL', 'SESSION_SECRET'];
# for (const k of required) if (!process.env[k]) {
# console.error(`Missing required env var: ${k}`); process.exit(1);
# }Step 2 — Core Logic
Step 2 adds the fast test gate and the build. The unit and component tests run first because they are fast and pinpoint failures precisely, so a broken function or component fails the pipeline in seconds before any expensive work. If they pass, the application builds, and reading the build report confirms the rendering strategy came out as intended — catching an accidentally-dynamic page before it ships. This step establishes the principle of ordering gates from cheapest to most expensive, so the pipeline fails as early and as informatively as possible.
# Steps 4-5: fast tests, then build with report
- name: Unit + component tests
run: npm run test:unit # Vitest \u2014 fast gate, runs in seconds
- name: Build
run: npm run build # next build
env:
# public values inlined at build come from the secret store too
NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }}
- name: Check rendering strategy
run: node scripts/check-build.js # optional: assert key routes are static/ISR as intended
# package.json scripts:
# "test:unit": "vitest run",
# "test:e2e": "playwright test",
# "build": "next build",
# "start": "next start"Step 3 — Integration & Enhancement
Step 3 adds the end-to-end gate and the deployment, the integration heart of the pipeline. The end-to-end tests run against the built application and a dedicated test database, verifying the critical user journeys — login, the core CRUD flow, the authorization checks — in a real browser, exactly the server-first integration that unit tests cannot reach. Only if every prior gate has passed does the deploy stage run, shipping to the managed platform or self-hosted target with secrets injected at runtime from the secret store and monitoring and structured logging active in the deployed app. A rollback path is configured so a bad deploy can be reversed quickly.
# Steps 6-7: end-to-end gate, then deploy only if everything passed
- name: End-to-end tests
run: |
npm run build
npm run test:e2e # Playwright vs a dedicated TEST database
env:
DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }} # test DB, never production
SESSION_SECRET: ${{ secrets.TEST_SESSION_SECRET }}
- name: Deploy
if: github.ref == 'refs/heads/main' # deploy only from main, only if gates passed
run: npm run deploy # managed platform CLI, or self-host release
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }} # PRODUCTION secrets, runtime-injected
SESSION_SECRET: ${{ secrets.SESSION_SECRET }}
# Deployed app: instrumentation.js initializes monitoring/error tracking;
# structured logger active; rollback = redeploy the previous release/build.Step 4 — Testing & Verification
Verify that the pipeline actually gates as intended by deliberately exercising each gate, because a pipeline that does not truly block bad changes gives false confidence. Confirm that a failing unit test stops the pipeline before deploy, that a missing required environment variable fails the config gate fast, that a failing end-to-end test blocks the deploy, and that a clean change flows all the way through to a live deployment. Then verify the deployed result: the app is reachable, monitoring is receiving signals, structured logs are being collected, and the rollback path works by reversing to the previous release. The gate-blocking tests are the essential ones, since the whole value of the pipeline is its refusal to ship anything that fails a check.
# Verify each gate genuinely blocks (the important checks):
# 1. Break a unit test on a branch \u2192 push \u2192 pipeline FAILS at test:unit, NO deploy.
# 2. Remove a required env var from CI secrets \u2192 config gate FAILS fast with a clear message.
# 3. Break a critical flow \u2192 end-to-end test FAILS \u2192 deploy is BLOCKED.
# 4. Push a clean change to main \u2192 all gates pass \u2192 app deploys automatically.
#
# Verify the deployed result:
# 5. App is reachable at its URL; key routes render.
# 6. Trigger a handled error \u2192 it appears in error tracking / monitoring.
# 7. Check structured logs are collected and searchable (no sensitive data in them).
# 8. Roll back: redeploy the previous release \u2192 confirm the prior version is live.
#
# A pipeline that does NOT block on 1-3 is the failure mode to fix \u2014
# its entire value is refusing to ship changes that fail a check.Warning: The critical thing to verify is that gates genuinely block — a pipeline that runs tests but deploys anyway when they fail gives dangerous false confidence. Confirm a failing test actually stops the deploy. And never run end-to-end tests against the production database or inject production secrets into test stages; use a dedicated test database and test secrets, and reserve production secrets for the deploy stage, injected at runtime, never committed.
Extension Challenge: Add preview deployments so every pull request deploys to its own temporary environment for review before merging. Add a staging environment that the pipeline deploys to first, with a manual approval gate before production. Add a database migration step that runs migrations safely as part of deploy, and a smoke test that hits the live deployment after release to confirm it is healthy, automatically rolling back if the smoke test fails. Together these exercise preview environments, staged promotion with approval, safe migrations, and automated post-deploy verification on top of the core pipeline.
- A CI/CD pipeline runs an ordered sequence of gates on every change, refusing to deploy anything that fails any check.
- Gates are ordered cheapest-to-most-expensive — install, config validation, fast unit tests, build, end-to-end tests, deploy — so failures surface as early as possible.
- The configuration gate validates required environment variables and fails fast on a missing one, stopping a misconfigured deployment before production.
- Secrets come from the CI/platform secret store injected at the steps that need them and at runtime for the deployed app, never committed or baked into the build.
- End-to-end tests run against a dedicated test database (never production) and verify the critical journeys before deploy; deploy runs only if all gates passed.
- The deployed app has monitoring and structured logging active, and a rollback path lets a bad deploy be reversed quickly, making deployment routine and reversible.