Back to BlogDevOps

CI/CD Best Practices for Next.js Applications

DevOps Team
Oct 22, 2024
9 min read

CI/CD Best Practices for Next.js Applications


Continuous Integration and Continuous Deployment (CI/CD) are essential for modern web applications. For Next.js projects, a well-configured pipeline can provide automatic testing, preview environments, and zero-downtime deployments. Here's how we set up CI/CD for production Next.js applications.


Our CI/CD Philosophy


1. **Every commit is a potential release:** All code merged to main should be deployable

2. **Fast feedback:** Tests and builds should complete in <10 minutes

3. **Preview everything:** Every PR gets a live preview environment

4. **Zero-downtime deploys:** Users should never experience interruptions

5. **Rollback-ready:** Should be able to revert to previous version in <5 minutes


The Pipeline Architecture


Our standard Next.js CI/CD pipeline has these stages:


\`\`\`

Pull Request → Lint → Type Check → Test → Build → Preview Deploy

Merge

Production Build → Deploy to Canary → Health Check → Full Deploy → Notify

\`\`\`


Let's break down each stage.


Stage 1: Code Quality (Parallel)


When a PR is opened, we run these checks in parallel:


Linting (ESLint + Prettier)

\`\`\`yaml

  • name: Lint
  • run: |

    npm run lint

    npm run format:check

    \`\`\`


    Configuration:

  • ESLint with Next.js recommended rules
  • Prettier for consistent formatting
  • Custom rules for our conventions (e.g., no default exports except pages)

  • **Time:** ~30 seconds


    Type Checking (TypeScript)

    \`\`\`yaml

  • name: Type Check
  • run: npm run type-check

    \`\`\`


    We use strict mode and require types for all function parameters and returns.


    **Time:** ~45 seconds


    Unit & Integration Tests (Jest + React Testing Library)

    \`\`\`yaml

  • name: Test
  • run: npm run test:ci

    \`\`\`


    We run:

  • Unit tests for utilities and hooks
  • Integration tests for components
  • Coverage threshold: 80% for utils, 60% for components

  • **Time:** 2-3 minutes


    **Optimization:** We use Jest's \`--onlyChanged\` flag to run only tests affected by the PR.


    Stage 2: Build Validation


    Next.js Build

    \`\`\`yaml

  • name: Build
  • run: npm run build

    env:

    NODE_ENV: production

    \`\`\`


    This catches:

  • Build errors from TypeScript or imports
  • API route issues
  • Image optimization errors

  • **Time:** 3-5 minutes


    Cache Strategy:

  • Cache \`node_modules\` by package-lock hash
  • Cache Next.js build cache (\`.next/cache\`)
  • Typical cache hit reduces build time by 60%

  • Stage 3: Preview Deployment


    Every PR gets a preview environment with a unique URL.


    Using Vercel (Recommended for Next.js)

    Vercel's GitHub integration provides:

  • Automatic preview deployments for every commit
  • Unique URL per PR (\`pr-123-projectname.vercel.app\`)
  • Automatic HTTPS
  • Comments on PR with preview URL

  • Configuration:

    \`\`\`json

    // vercel.json

    {

    "github": {

    "silent": false,

    "autoAlias": true

    },

    "buildCommand": "npm run build",

    "outputDirectory": ".next"

    }

    \`\`\`


    Alternative: AWS + GitHub Actions

    For teams not using Vercel, we use S3 and CloudFront for preview deployments with custom scripts.


    Stage 4: Automated Preview Testing (E2E)


    Once preview is deployed, run E2E tests against the preview URL using Playwright.


    What we test:

  • Critical user flows (signup, login, purchase)
  • Page load times
  • Console errors
  • Visual regression (screenshots)

  • **Time:** 4-6 minutes


    **Tool:** Playwright (faster and more reliable than Cypress in our experience)


    Stage 5: Merge to Main


    Once PR is approved and all checks pass, merge to main triggers production deployment.


    Stage 6: Production Deployment Strategy


    We use a **canary deployment** strategy:


    1. **Build production version**

    2. **Deploy to 5% of users** (canary)

    3. **Monitor for 5 minutes** (error rates, latency)

    4. **If healthy, deploy to 100%**

    5. **If issues detected, auto-rollback**


    Implementation with Vercel

    \`\`\`yaml

    vercel.json

    {

    "regions": ["iad1"],

    "cleanUrls": true,

    "trailingSlash": false,

    "redirects": [...],

    "headers": [...]

    }

    \`\`\`


    Vercel handles canary deployments automatically with their "Instant Rollback" feature.


    Implementation with Kubernetes

    \`\`\`yaml

    apiVersion: argoproj.io/v1alpha1

    kind: Rollout

    spec:

    replicas: 10

    strategy:

    canary:

    steps:

    - setWeight: 5

    - pause: {duration: 5m}

    - setWeight: 100

    analysis:

    successfulRunHistoryLimit: 3

    failureLimit: 1

    \`\`\`


    Stage 7: Health Checks & Monitoring


    After deployment, automated health checks verify the deployment is successful.


    Monitored metrics:

  • HTTP 5xx error rate (< 0.1%)
  • API latency p99 (< 500ms)
  • Client-side errors (Sentry)
  • Core Web Vitals (LCP, FID, CLS)

  • Alert thresholds:

  • Error rate > 1%: Auto-rollback
  • Latency > 2s: Alert team
  • Failed health check: Auto-rollback

  • Stage 8: Notifications


    Successful deploys trigger notifications:

  • Slack message with deploy details
  • Link to Vercel deployment
  • Rollback command (one-click)

  • Environment Variables Strategy


    **Never commit secrets.** We use:


    Development:

  • \`.env.local\` (gitignored)
  • Local values for testing

  • Preview:

  • Vercel environment variables (set via dashboard)
  • Preview-specific values (e.g., staging database)

  • Production:

  • Vercel environment variables (encrypted)
  • Pulled from secret manager (AWS Secrets Manager, Vercel vault)

  • Naming convention:

  • \`NEXT_PUBLIC_*\` for client-side vars
  • No prefix for server-side only vars

  • Rollback Strategy


    If something goes wrong in production:


    Vercel

    1. Go to Vercel dashboard

    2. Find previous successful deployment

    3. Click "Promote to Production"

    4. Done in <30 seconds


    Self-Hosted

    For Kubernetes, use \`kubectl rollout undo\` command. For Docker, use the service update rollback command.


    **Time to rollback:** <5 minutes


    Cost Optimization


    CI/CD isn't free. Here's how we optimize:


    1. **Cache aggressively:** \`node_modules\` and build cache

    2. **Run tests selectively:** Only affected tests on PRs

    3. **Parallel jobs:** Lint, type-check, and test in parallel

    4. **Preview cleanup:** Delete preview environments after PR merge


    Our costs (for a medium app):

  • GitHub Actions: ~$50/month
  • Vercel Pro: $20/month
  • Total: ~$70/month for unlimited deployments

  • Lessons Learned


    1. Fast Feedback Matters

    If CI takes >10 minutes, developers will context-switch and lose focus. Optimize for speed.


    2. Preview Deployments are a Game-Changer

    Non-technical stakeholders can review features before they go live. Catches issues early.


    3. Automate Rollbacks

    Don't rely on humans to notice and fix issues. Automated health checks + rollbacks save the day.


    4. Don't Skip E2E Tests

    They're slower but catch integration issues unit tests miss. Run them on every deployment.


    5. Monitor Everything

    You can't fix what you don't measure. Instrument your app and watch key metrics after every deploy.


    Sample GitHub Actions Workflow


    Here's a simplified example workflow structure:


    \`\`\`yaml

    name: CI/CD


    on:

    pull_request:

    push:

    branches: [main]


    jobs:

    quality:

    runs-on: ubuntu-latest

    steps:

    - uses: actions/checkout@v3

    - uses: actions/setup-node@v3

    - run: npm ci

    - run: npm run lint

    - run: npm run type-check

    - run: npm run test:ci


    build:

    runs-on: ubuntu-latest

    needs: quality

    steps:

    - uses: actions/checkout@v3

    - uses: actions/setup-node@v3

    - run: npm ci

    - run: npm run build

    \`\`\`


    Conclusion


    A solid CI/CD pipeline is an investment that pays dividends in velocity, quality, and confidence. For Next.js applications, the combination of automated testing, preview environments, and safe production deployments enables teams to ship faster without breaking things.


    Start simple, measure everything, and continuously improve your pipeline. Your future self will thank you.


    D

    DevOps Team

    The DevOps Team at Senpai Software shares insights and best practices from real-world software development projects.