100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Next.js App Router
65 minintermediate

Production Practice — CI/CD and Deployment

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.

Analogy🏏Cricket
🏏 Think of it like cricket: Building this dashboard is like running a full match-day operation rather than a single drill — the scorers, the broadcast, the substitutes, and the ground staff all working together under live conditions. Just as match day proves every department functions in concert, this project proves server fetching, mutations, streaming, and error handling work together. Just as the broadcast keeps showing the ground while a replay loads, your dashboard stays usable while the slow panel streams. Just as the team has substitutes ready for injuries, your dashboard has error boundaries ready for failures. This is the match-day rehearsal that confirms the whole Module 2 system holds up under realistic load.

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.

Analogy🏏Cricket
🏏 Think of it like cricket: a coach lays out the match-day plan before the toss — who gathers the standings and top-scorer stats, who handles a mid-innings change, which slow specialist analysis can arrive late, and what the fallback is if a player is injured — so every job has an owner before play. Just as planning each role first makes match day run smoothly, planning your dashboard folders first gives each concept a clear home: a page that fetches standings and top scorer in parallel, an actions file holding the add-note Server Action, a slow analytics component wrapped in Suspense, and error and not-found files for safety. Just as the plan shows how batting, bowling, and fielding combine, the layout shows how server fetching, mutation, streaming, and error handling fit together. The payoff: a structure where every piece has an obvious place before you write a line.
bash
# 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 lockfile

Step 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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 1 is like the analysts compiling the opposition report — multiple researchers working at once on batting, bowling, and fielding, then combining their findings into one briefing. Just as the researchers work concurrently rather than one after another, your fetches run in parallel with Promise.all. Just as the combined briefing is ready before the captain walks out, the data is resolved before the page renders. This shows why the foundation is parallel server fetching: gather everything at once, securely, before play begins.
yaml
# 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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 2 is like the captain logging a tactical note in the official book mid-innings — a direct entry that updates the team's shared record everyone then sees. Just as the note goes straight into the authoritative book, the form submission goes straight to the Server Action. Just as the updated book is immediately visible to the staff, revalidatePath makes the new note immediately visible on the page. This shows why the action is the core write logic: one direct submission records and refreshes in a single motion.
yaml
# 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.

Analogy🏏Cricket
🏏 Think of it like cricket: Step 3 is the live match with everything running together — the scoreboard updating, a replay streaming in behind a 'pending' graphic, and substitutes ready if a fielder goes down. Just as the replay loads without freezing the broadcast, the analytics stream without freezing the dashboard. Just as substitutes contain an injury to one position, the error boundary contains a failure to one segment. This reveals why integration is the payoff: the value is in fetching, mutating, streaming, and recovering all coexisting smoothly.
yaml
# 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.

Analogy🏏Cricket
🏏 Think of it like cricket: a captain rehearses each match scenario in the nets before it counts — a quick single, a run-out call, a rejected review, an injury contingency — confirming each response works under real conditions rather than trusting it will. Just as rehearsing each scenario proves the plan holds, running your dashboard and verifying each behaviour deliberately proves the concepts work together: the fast content paints before the analytics stream in, adding a note refreshes the list without a reload, a too-short note is rejected, and the error boundary catches a thrown error. Just as testing both the smooth single and the failed review checks success and failure paths, you check both valid and invalid inputs. And just as net rehearsal turns separate skills into a coordinated performance, verifying each path proves the streaming, mutation, and error handling work in concert, not just in isolation. The payoff: confidence the whole dashboard behaves as designed.
bash
# 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.
Lesson 30 of 35
0% complete