Next.js CI/CD: Automate Builds and Deploys in GitHub Actions
The most surprising thing about automating Next.js builds and deploys with GitHub Actions is that you’re not just automating a script; you’re orchestrating a miniature cloud environment to produce and serve your application.
Let’s see it in action. Imagine a simple Next.js app. When you push a commit to your main branch, a GitHub Actions workflow kicks off. This workflow, defined in a .github/workflows/deploy.yml file, spins up a temporary runner (a virtual machine), checks out your code, installs Node.js, installs your project dependencies, builds your Next.js app for production, and then pushes the built artifacts to a hosting service like Vercel, Netlify, or even a custom S3 bucket.
Here’s a typical deploy.yml workflow for deploying to Vercel:
name: Deploy to Vercel
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Build Next.js app
run: npm run build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v2
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
project-alias: my-nextjs-app # Replace with your Vercel project alias
github-commit-sha: ${{ github.sha }}
This workflow breaks down into several key steps:
name: Deploy to Vercel: This is just a human-readable name for your workflow.on: push: branches: - main: This specifies that the workflow should only trigger when code is pushed to themainbranch. You could also trigger on pull requests, tags, or manually.jobs: build-and-deploy:: A workflow can have multiple jobs. Here, we have one job namedbuild-and-deploy.runs-on: ubuntu-latest: This tells GitHub Actions to use the latest Ubuntu Linux runner to execute the job. Other options includewindows-latestandmacos-latest.steps:: This is where the actual work happens, defined as a sequence of tasks.-
uses: actions/checkout@v3: This action checks out your repository’s code onto the runner, making it available for subsequent steps. -
uses: actions/setup-node@v3: This action sets up a Node.js environment on the runner. We specifynode-version: '18'to ensure compatibility. -
run: npm ci: This command installs project dependencies.npm ciis preferred overnpm installin CI environments because it’s faster and more deterministic, usingpackage-lock.jsonornpm-shrinkwrap.jsonto ensure exact versions are installed. -
run: npm run build: This executes thebuildscript defined in yourpackage.jsonfile, which for Next.js typically runsnext build. This command generates a production-ready build of your Next.js application in the.nextdirectory. -
uses: amondnet/vercel-action@v2: This is a community action specifically designed to interact with Vercel. It handles the deployment process.-
vercel-token: ${{ secrets.VERCEL_TOKEN }}: This uses a GitHub Secret to securely provide your Vercel API token. You’ll need to generate this token in your Vercel account settings. -
github-token: ${{ secrets.GITHUB_TOKEN }}: This is a built-in GitHub token that grants the action permissions to interact with your GitHub repository, often used for deployment status updates. -
project-alias: my-nextjs-app: This tells Vercel which project to deploy to. You’ll find this alias in your Vercel project settings. -
github-commit-sha: ${{ github.sha }}: This passes the commit SHA to Vercel, allowing for easy traceability between your Git commit and the deployed version.
-
-
The mental model here is that GitHub Actions acts as your build server and deployment orchestrator. It provides a consistent, isolated environment for every build, eliminating "it works on my machine" issues. For deployments, it leverages integrations (like the vercel-action) or custom scripts to push your built artifacts to your chosen hosting provider.
The real power comes from understanding the secrets context. You should never hardcode API keys or sensitive information directly into your workflow files. Instead, you store them as encrypted secrets in your GitHub repository’s settings under "Settings" -> "Secrets and variables" -> "Actions". These secrets are then securely injected into the runner environment at runtime via the {{ secrets.YOUR_SECRET_NAME }} syntax.
One of the most overlooked aspects of Next.js CI/CD is cache management. By default, npm ci will re-download dependencies on every run. To speed things up, you can leverage GitHub Actions’ caching mechanism. Add this step before npm ci:
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
This tells GitHub Actions to save your node_modules directory after a successful run, keyed by your operating system and the hash of your package-lock.json. On subsequent runs, if the lock file hasn’t changed, it will restore the cached node_modules instead of re-downloading everything, drastically reducing build times.
The next logical step in your CI/CD journey will be implementing automated testing and more sophisticated deployment strategies like canary releases or blue-green deployments.