Next.js CI/CD Pipeline
In modern web development, automating the testing and deployment of your applications is essential for maintaining quality and rapid delivery. In this tutorial, you'll learn how to set up a Continuous Integration and Continuous Deployment (CI/CD) pipeline for your Next.js applications.
What is CI/CD?
Continuous Integration (CI) is the practice of automatically integrating code changes from multiple contributors into a shared repository, where automated builds and tests verify each integration.
Continuous Deployment (CD) automatically deploys all code changes to a testing and/or production environment after the build stage.
Together, CI/CD creates an automated pipeline that improves developer productivity and helps catch bugs early.
Why implement CI/CD for Next.js applications?
- Consistency: Every code change goes through the same testing process
- Early bug detection: Automated tests catch issues before they reach production
- Faster releases: Automate tedious manual deployment steps
- Better collaboration: Team members can integrate their changes frequently
Setting up a CI/CD Pipeline for Next.js
We'll cover how to set up a CI/CD pipeline using GitHub Actions, which is free for public repositories and includes limited free minutes for private repositories.
Prerequisites
- A Next.js application
- A GitHub repository for your project
- Basic understanding of Git
- A deployment platform (we'll use Vercel in this tutorial)
Creating a Basic GitHub Actions Workflow
Let's start by creating a basic CI workflow that runs tests whenever code is pushed to the repository.
- Create a directory structure in your project:
mkdir -p .github/workflows
- Create a new file .github/workflows/ci.yml:
name: Next.js CI
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Use Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18.x'
        cache: 'npm'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Lint
      run: npm run lint
      
    - name: Build
      run: npm run build
      
    - name: Test
      run: npm test
This workflow does the following:
- Triggers on pushes to the mainbranch and pull requests targetingmain
- Uses an Ubuntu environment
- Sets up Node.js 18
- Installs dependencies with npm ci(faster and more reliable thannpm install)
- Runs linting, building, and testing scripts
Understanding the workflow file
- name: A name for your workflow
- on: Defines when the workflow runs
- jobs: Groups the jobs to run
- build: The job ID
- runs-on: The type of runner to use
- steps: The sequence of tasks to execute
Adding Automated Testing
Testing is a crucial component of CI. Let's set up Jest for testing our Next.js components.
- Install Jest and testing libraries:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom jest-environment-jsdom
- Create a jest.config.jsfile:
const nextJest = require('next/jest')
const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files
  dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    '^@/components/(.*)$': '<rootDir>/components/$1',
    '^@/pages/(.*)$': '<rootDir>/pages/$1',
  },
  testEnvironment: 'jest-environment-jsdom',
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)
- Create a jest.setup.jsfile:
import '@testing-library/jest-dom/extend-expect'
- Add test scripts to your package.json:
"scripts": {
  "test": "jest",
  "test:watch": "jest --watch"
}
- Create a simple test file __tests__/Home.test.js:
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'
describe('Home page', () => {
  it('renders without crashing', () => {
    render(<Home />)
    expect(screen.getByRole('heading')).toBeInTheDocument()
  })
})
Setting up Continuous Deployment with Vercel
Vercel is a deployment platform that works excellently with Next.js (since Next.js is developed by Vercel). Let's set up CD with Vercel:
- Sign up for a Vercel account at vercel.com
- Install the Vercel CLI:
npm install -g vercel
- Log in to Vercel from the CLI:
vercel login
- Create a .github/workflows/cd.ymlfile:
name: Next.js CD
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          vercel-args: '--prod'
- Generate your Vercel token and project details:
vercel whoami
vercel projects
- Add the secrets to your GitHub repository:
- Go to your GitHub repository
- Navigate to Settings > Secrets and variables > Actions
- Add the following secrets:
- VERCEL_TOKEN: Your Vercel token
- ORG_ID: Your Vercel organization ID
- PROJECT_ID: Your Vercel project ID
 
 
Creating a Complete CI/CD Pipeline
Now, let's integrate both CI and CD into a single workflow file:
name: Next.js CI/CD
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Use Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18.x'
        cache: 'npm'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Lint
      run: npm run lint
      
    - name: Build
      run: npm run build
      
    - name: Test
      run: npm test
      
  deploy:
    needs: build-and-test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          vercel-args: '--prod'
Key points in this workflow:
- The deployjob depends on successful completion ofbuild-and-test(needs: build-and-test)
- Deployment only happens for pushes to the mainbranch, not pull requests (if: github.ref == 'refs/heads/main' && github.event_name == 'push')
Adding Environment-Specific Deployments
For a more advanced setup, you might want different environments:
name: Next.js CI/CD Pipeline
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]
jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      # Same as before
  
  deploy-preview:
    needs: build-and-test
    if: github.ref == 'refs/heads/develop' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to Preview
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
  
  deploy-production:
    needs: build-and-test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to Production
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          vercel-args: '--prod'
Real-World Example: Integrating Cache and Performance Checks
Here's a more comprehensive workflow with cache optimization and web performance checks:
name: Production CI/CD
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  build-test-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Use Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18.x'
        cache: 'npm'
        
    - name: Get npm cache directory
      id: npm-cache-dir
      run: echo "::set-output name=dir::$(npm config get cache)"
    
    - name: Cache node modules
      uses: actions/cache@v3
      with:
        path: ${{ steps.npm-cache-dir.outputs.dir }}
        key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.os }}-node-
          
    - name: Install dependencies
      run: npm ci
      
    - name: Lint
      run: npm run lint
      
    - name: Build
      run: npm run build
      
    - name: Test
      run: npm test
      
    - name: Run Lighthouse CI
      uses: treosh/lighthouse-ci-action@v9
      with:
        uploadArtifacts: true
        temporaryPublicStorage: true
        runs: 3
        configPath: './.github/workflows/lighthouserc.json'
      
    - name: Deploy to Vercel
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      uses: amondnet/vercel-action@v20
      with:
        vercel-token: ${{ secrets.VERCEL_TOKEN }}
        vercel-org-id: ${{ secrets.ORG_ID }}
        vercel-project-id: ${{ secrets.PROJECT_ID }}
        vercel-args: '--prod'
And create a .github/workflows/lighthouserc.json file:
{
  "ci": {
    "collect": {
      "staticDistDir": "./out",
      "url": ["http://localhost:3000"]
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", {"minScore": 0.8}],
        "categories:accessibility": ["warn", {"minScore": 0.9}]
      }
    }
  }
}
Summary
You've now learned how to create a CI/CD pipeline for your Next.js applications using GitHub Actions and Vercel. This setup will:
- Automatically run tests when code is pushed or pull requests are created
- Deploy to preview environments for development branches
- Deploy to production for the main branch
- Include performance and accessibility checks
By implementing a CI/CD pipeline, you'll save time, catch issues early, and maintain a high-quality codebase.
Additional Resources
- GitHub Actions Documentation
- Vercel Documentation for Next.js
- Jest Testing Framework
- React Testing Library
- Lighthouse CI
Exercises
- Set up a basic CI workflow for your Next.js project that runs linting and tests
- Add a step to your workflow that checks for bundle size increases
- Configure preview deployments for feature branches
- Implement end-to-end tests using Cypress and add them to your CI pipeline
- Set up notifications (Slack, email, etc.) for failed deployments
These exercises will help you become more familiar with CI/CD pipelines and ensure your Next.js applications are always production-ready.
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!