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
run: |
npm run lint
npm run format:check
\`\`\`
Configuration:
**Time:** ~30 seconds
Type Checking (TypeScript)
\`\`\`yaml
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
run: npm run test:ci
\`\`\`
We run:
**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
run: npm run build
env:
NODE_ENV: production
\`\`\`
This catches:
**Time:** 3-5 minutes
Cache Strategy:
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:
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:
**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:
Alert thresholds:
Stage 8: Notifications
Successful deploys trigger notifications:
Environment Variables Strategy
**Never commit secrets.** We use:
Development:
Preview:
Production:
Naming convention:
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):
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.