
A CI/CD pipeline for a Next.js app: from pull request to production
A CI/CD pipeline is the automated chain that turns a code change into a feature in production. Designed well, it gives the team the confidence to ship several times a day. Designed badly, it becomes a 40-minute queue that everyone works around.
Having built this kind of pipeline on Azure DevOps and GitHub Actions, here is the structure I recommend for a Next.js application.
The two halves: CI and CD
- Continuous integration (CI) checks every pull request: the code compiles, follows conventions and breaks nothing.
- Continuous deployment (CD) automatically ships merged code: first to a preview, then to production.
Stage 1: fast checks
The cheapest checks run first, so failures surface fast: lint, type checking and unit tests. On a mid-sized application they should take under three minutes, and they can run in parallel.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm tsc --noEmit
- run: pnpm test -- --ciStage 2: build and end-to-end tests
Once the basics pass, build the application (next build) and run end-to-end tests (Playwright) against the production build, not the dev server. It is the only way to catch static rendering or configuration errors.
e2e:
needs: quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: pnpm }
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test
- uses: actions/upload-artifact@v4
if: failure()
with: { name: playwright-report, path: playwright-report }Keeping the Playwright report as an artifact on failure saves a lot of time: you get the trace, screenshots and video of the failing test.
Stage 3: a preview environment per pull request
This is the feature that changes collaboration the most. Every pull request gets its own URL (Vercel, Netlify or Azure Static Web Apps do it natively). The product owner and designer review on a real deployment, before merging, without installing anything.
Stage 4: production release
- Triggered by merging to `main`, never manually from a developer machine.
- Secrets (API keys, database URLs) live in the CI platform's secret store, never in the repository.
- Database migrations run before the code is deployed and must stay compatible with the previous version (add a column before using it, remove it after).
- One-click rollback: keeping previous builds lets you roll back in seconds if something goes wrong.
Keeping the pipeline fast
- Caching: dependencies (
cache: pnpm), the Next.js build cache (.next/cache) and Playwright browsers. - Parallelism: lint, types and unit tests in separate jobs; E2E tests split with
--shard. - Filtering: in a monorepo, only test the projects affected by the change.
- `concurrency`: automatically cancel the previous run when a new commit is pushed to the same branch.
Conclusion
A good pipeline is not the one with the most stages, but the one the team trusts. Fast checks first, E2E tests on the real build, a preview URL per pull request and automatic, reversible deployment: with these four building blocks, shipping to production becomes a non-event.