GitHub Actions CI/CD for Next.js: Test, Build, Deploy on Every Push
A solid CI/CD pipeline catches bugs before they reach production and automates deploys.
Here's a production-ready setup for Next.js.
Basic Pipeline Structure
# .github/workflows/ci.yml
name: CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run type-check
- run: npm test
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: vercel/action@v3
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Database Migrations in CI
migrate:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run migrations
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: npx prisma migrate deploy
deploy:
needs: [test, migrate]
# ... rest of deploy job
Caching Dependencies
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # caches ~/.npm
# Also cache Next.js build cache
- uses: actions/cache@v4
with:
path: |
~/.npm
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
This reduces build times by 40-60% on repeated runs.
Preview Deployments for PRs
preview:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: vercel/action@v3
id: deploy
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
- uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `Preview: ${{ steps.deploy.outputs.preview-url }}`
})
Running Tests with a Database
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: testpassword
POSTGRES_DB: testdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Setup test database
env:
DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/testdb
run: npx prisma migrate deploy
- name: Run tests
env:
DATABASE_URL: postgresql://postgres:testpassword@localhost:5432/testdb
run: npm test
Environment Secrets Management
- name: Build
env:
NEXT_PUBLIC_API_URL: ${{ vars.API_URL }} # non-sensitive
DATABASE_URL: ${{ secrets.DATABASE_URL }} # sensitive
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET }} # sensitive
run: npm run build
Use vars (variables) for non-sensitive config, secrets for credentials.
Notifications on Failure
- name: Notify on failure
if: failure()
uses: slackapi/slack-github-action@v1
with:
payload: |
{"text": "Deploy failed on ${{ github.ref }} by ${{ github.actor }}"}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
The Ship Fast Skill Pack includes a /deploy skill that generates complete GitHub Actions workflows for your stack — CI, preview deploys, and production. $49 one-time.
Top comments (0)