CI/CD for Next.js Projects with GitHub Actions
This module has covered unit testing (7.1), E2E testing (7.2), and two deployment approaches (Vercel in 7.3, Docker in 7.4) — but running tests manually before every deployment, and remembering to do so consistently, is exactly the kind of repetitive, error-prone process automation exists to solve. CI/CD (Continuous Integration/Continuous Deployment) pipelines automate this entirely: every time code is pushed, an automated pipeline runs your tests, and if they pass, automatically proceeds toward deployment, catching problems before they ever reach production and removing the risk of a forgotten manual step.
This lesson covers building a complete GitHub Actions workflow for a Next.js project, automatically running both the unit tests (Lesson 7.1) and E2E tests (Lesson 7.2) covered earlier in this module on every push, and gating deployment on those tests actually passing.
Learning Objectives
- Explain what CI/CD means and what problem it solves.
- Write a GitHub Actions workflow file for a Next.js project.
- Configure a pipeline to automatically run unit tests and E2E tests on every push.
- Gate a deployment step on tests passing successfully.
- Understand how CI/CD connects the testing (7.1-7.2) and deployment (7.3-7.4) lessons from this module.
Core Definitions
- CI (Continuous Integration): The practice of automatically building and testing code changes whenever they're pushed, catching integration problems early and consistently.
- CD (Continuous Deployment/Delivery): The practice of automatically deploying code changes that pass all required checks, without manual intervention (Continuous Deployment) or with a manual approval gate (Continuous Delivery).
- GitHub Actions: GitHub's built-in automation platform, letting you define workflows (as YAML files) that run in response to repository events like a push or pull request.
- Workflow: A GitHub Actions configuration file (in .github/workflows/) defining a sequence of automated jobs and steps triggered by specific repository events.
- Pipeline: The overall automated sequence of steps (build, test, deploy) a CI/CD system runs for each code change.
Detailed Explanation
GitHub Actions workflows are defined as YAML files inside a .github/workflows/ folder in your repository, specifying exactly when the workflow should run (like on every push, or specifically on pull requests targeting the main branch) and what steps it should execute. A typical Next.js CI/CD workflow includes several jobs: checking out the repository's code, installing dependencies, running the unit test suite (Lesson 7.1's Jest tests), running the E2E test suite (Lesson 7.2's Playwright or Cypress tests, typically requiring the application to actually be running first), and, only if all of these previous steps succeed, proceeding to a deployment step.
This 'gating' behavior — deployment only happening if tests actually pass — is precisely the safety net CI/CD provides: a developer might forget to run tests locally before pushing, or might run them but ignore a failure under time pressure, but an automated pipeline enforces this check consistently, every single time, with no room for human error or shortcuts. If any test fails, the pipeline stops, the deployment never happens, and the team is notified (typically via GitHub's own pull request status checks, showing a clear pass/fail indicator directly in the PR interface) that something needs to be fixed before merging.
For a project deploying to Vercel (Lesson 7.3), the actual deployment step is often handled automatically by Vercel's own Git integration rather than needing to be explicitly scripted within the GitHub Actions workflow itself — in this case, the GitHub Actions workflow's primary job becomes running and gating on tests, with Vercel separately handling the deployment once a pull request is merged (and Vercel's own preview deployments already provide a review environment for each PR, complementing rather than duplicating the GitHub Actions test gate). For a project self-hosted via Docker (Lesson 7.4), the GitHub Actions workflow more commonly handles the full pipeline explicitly: running tests, then building the Docker image, pushing it to a container registry, and triggering a deployment to your specific infrastructure — all within the same workflow, since there's no equivalent platform-level Git integration automatically handling deployment.
A well-structured pipeline also considers execution order and efficiency: unit tests (fast) typically run before E2E tests (slower, requiring a running application), so a quick, cheap failure is caught and reported as early as possible, without wasting time and compute resources spinning up a full browser-based E2E test suite for a change that already fails a basic unit test.
A Complete GitHub Actions CI/CD Pipeline for Next.js
{"heading":"A Complete GitHub Actions CI/CD Pipeline for Next.js","description":"Visualize a complete CI/CD pipeline flow:\n\n[Developer pushes code / opens a PR]\n │\n ▼\n[GitHub Actions workflow triggers]\n │\n ├── Checkout code + install dependencies\n ├── Run unit tests (Jest, Lesson 7.1) --FAIL--> [Pipeline STOPS, PR shows a red ✗]\n │ │ PASS\n ├── Run E2E tests (Playwright/Cypress, Lesson 7.2) --FAIL--> [Pipeline STOPS, PR shows a red ✗]\n │ │ PASS\n └── Deploy (Vercel auto-handles this, OR explicit Docker build+push+deploy)\n │\n ▼\n [PR shows a green ✓ — safe to merge, or deployment proceeds automatically]"}
Next.js Practical Example
# .github/workflows/ci.yml — a complete CI/CD workflow for a Next.js project
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test # Jest unit tests, Lesson 7.1
e2e-tests:
needs: unit-tests # only run E2E if unit tests already passed
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npm run start & npx wait-on http://localhost:3000
- run: npx playwright test # E2E tests, Lesson 7.2
deploy:
needs: [unit-tests, e2e-tests] # ONLY deploy if BOTH test jobs passed
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Building and pushing Docker image, deploying to infrastructure..."
# (Real steps would build/push a Docker image and trigger deployment here)
The workflow defines three jobs: unit-tests runs first, installing dependencies and running the Jest suite from Lesson 7.1. e2e-tests explicitly declares `needs: unit-tests`, meaning it only runs if unit-tests already succeeded — avoiding the wasted time and compute of spinning up a full browser-based E2E suite when a basic unit test has already failed — then builds and starts the application, waits for it to be ready, and runs the Playwright suite from Lesson 7.2. The deploy job declares `needs: [unit-tests, e2e-tests]`, meaning it only runs if BOTH previous jobs succeeded, and additionally checks `if: github.ref == 'refs/heads/main'` to ensure deployment only happens on the actual production branch, not on every feature branch push — this specific deploy job sketch represents the Docker-based deployment path from Lesson 7.4; for a Vercel-deployed project, this job might be omitted entirely, since Vercel's own Git integration would handle deployment separately and automatically.
How Real Teams Structure Their CI/CD Pipelines
- SaaS companies commonly require all CI checks (unit tests, E2E tests, linting) to pass as a mandatory condition before a pull request can be merged, enforced directly through GitHub's branch protection rules tied to their Actions workflow.
- E-commerce platforms with Docker-based self-hosted deployments use GitHub Actions to build and push a new Docker image to a container registry (like AWS ECR or Docker Hub) only after their full test suite passes, then trigger a rolling deployment to their infrastructure.
- Teams using Vercel rely on GitHub Actions primarily as a testing gate — ensuring code quality before merge — while letting Vercel's own, separate Git integration handle the actual preview and production deployment process.
- Larger engineering organizations often split CI/CD into more granular jobs (linting, type-checking, unit tests, E2E tests, security scanning) run in parallel where possible, to get the fastest possible feedback on a pull request.
- Open-source projects rely heavily on GitHub Actions' free tier for public repositories, running comprehensive test suites on every external contributor's pull request before a maintainer needs to manually review the code.
Common Mistakes to Avoid
- Running E2E tests before unit tests, wasting time and compute resources on slower tests when a faster, cheaper check would have caught the failure first.
- Forgetting to gate a deployment step on tests actually passing, allowing broken code to deploy even when tests fail.
- Not configuring GitHub's branch protection rules to actually require CI checks to pass, making the pipeline's results merely informational rather than enforced.
- Duplicating deployment logic in both a GitHub Actions workflow and a platform's own Git integration (like Vercel's), causing confusing, redundant, or conflicting deployments.
- Not properly waiting for the application to be fully ready (using something like wait-on) before running E2E tests against it, causing tests to fail simply because the server wasn't ready yet.
Interview Notes
- CI (Continuous Integration) automates building and testing code changes; CD (Continuous Deployment/Delivery) automates deploying changes that pass required checks.
- GitHub Actions workflows are defined as YAML files in .github/workflows/, triggered by repository events like pushes or pull requests.
- A well-structured pipeline runs fast unit tests before slower E2E tests, and gates deployment on all tests passing.
- The `needs` keyword expresses job dependencies, letting you control execution order and gate later jobs on earlier ones succeeding.
- GitHub Actions' role differs by deployment target: primarily testing/gating for Vercel projects, versus the full pipeline (including build and deploy) for Docker-based projects.
Key Takeaways
- CI/CD closes the loop on this module's testing lessons, ensuring tests actually run consistently rather than depending on manual discipline.
- Structuring a pipeline with fast checks first and deployment gated on all checks passing is both an efficiency and a safety best practice.
- Understanding how GitHub Actions' role shifts depending on your deployment target (Vercel vs Docker) helps avoid duplicated or conflicting automation.
- This lesson demonstrates how the module's individual pieces — unit testing, E2E testing, and deployment — combine into one cohesive, automated workflow.
Summary
CI/CD (Continuous Integration/Continuous Deployment) automates the testing and deployment process, removing the risk of a developer forgetting to run tests or ignoring a failure under time pressure. GitHub Actions, defined through YAML workflow files in .github/workflows/, lets you build a complete pipeline: running unit tests (Jest, from Lesson 7.1) first since they're fast and cheap, then E2E tests (Playwright or Cypress, from Lesson 7.2) since they're slower and require a fully running application, using the `needs` keyword to ensure later jobs only run after earlier ones succeed. Deployment is gated on all these tests passing — for a Vercel-deployed project (Lesson 7.3), GitHub Actions' primary role is typically this testing gate, since Vercel's own Git integration handles deployment separately; for a Docker-based, self-hosted project (Lesson 7.4), GitHub Actions more commonly handles the full pipeline explicitly, building and pushing a Docker image and triggering deployment. This lesson demonstrates how the module's individually-covered concepts — unit testing, E2E testing, and deployment strategy — combine into one cohesive, automated workflow that runs consistently on every code change.