BEGINNERGITHUB-ACTIONSCI/CD

Next.js CI Workflow with Lint, Typecheck, and Build

A clean GitHub Actions workflow for Next.js projects: install, lint, typecheck, build. Cached, fast, and PR-ready.

Published Apr 12, 2026
github-actionsnextjsci-cdyamlautomation

A starter GitHub Actions workflow for Next.js apps that runs install, lint, typecheck, and build on every push and pull request. Each step is isolated so a lint failure doesn't hide a typecheck failure. Cached at the package-manager level for ~30s install times on warm runs.

Tested on Next.js 16, Node 24 LTS, GitHub Actions runners (ubuntu-latest).

When to Use This

  • Every Next.js repo, public or private
  • As a baseline before adding deployment steps
  • As a PR gate that blocks merging on red builds
  • Combined with concurrency to cancel superseded runs on the same branch

Don't use this when you need a build matrix across multiple Node versions (use the matrix-node-versions snippet instead) or when builds take more than 10 minutes (split lint/typecheck/build into parallel jobs).

Code

# .github/workflows/ci.yml
name: CI
 
on:
  push:
    branches: [main, dev]
  pull_request:
    branches: [main, dev]
 
concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  ci:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '24'
          cache: 'npm'
 
      - name: Install dependencies
        run: npm ci
 
      - name: Lint
        run: npm run lint
 
      - name: Typecheck
        run: npx tsc --noEmit
 
      - name: Build
        run: npm run build

The concurrency block cancels any in-progress run when you push a new commit to the same branch — saves CI minutes on rapid-fire pushes.

Usage

Drop the file at .github/workflows/ci.yml and push. The workflow runs automatically on every push to main and dev, and on every PR opened against them. The status badge appears in PR checks within a minute.

To make CI a required check before merge:

  1. GitHub repo → Settings → Branches → Branch protection rules
  2. Add main (and dev) → Require status checks to pass before merging
  3. Search for CI / ci and add it

Pitfalls

  • npm ci is mandatory in CI, not npm install. ci enforces the lockfile and is faster. install can mutate the lockfile, which breaks reproducibility.
  • Cache key is based on the lockfile path. If you have multiple lockfiles in a monorepo, point cache-dependency-path at the specific one.
  • tsc --noEmit ignores next-env.d.ts issues. That's fine — Next regenerates it on build. Don't waste time fighting it.
  • Build runs Next.js's own type check anyway. If you only need one of the two, drop the explicit tsc --noEmit step. I keep it because it fails faster than build.
  • timeout-minutes: 10 is intentional. Without it, a stuck build burns through your CI quota.
  • Cache npm Dependencies in GitHub Actions(coming soon) — deeper caching strategy
  • Matrix Build Across Node Versions(coming soon) — for libraries that test against multiple Node versions
  • Conditional Skip on Docs-Only Changes(coming soon) — save CI minutes when only Markdown changed

Frequently Asked Questions

Why run lint, typecheck, and build as separate steps in CI?

Separate steps give you isolated failure reports. If lint fails, the typecheck and build still run so you see every problem in one CI run. Bundling them into a single command makes you fix issues one at a time across multiple pushes.

Should you cache node_modules or use the actions/setup-node cache?

Use actions/setup-node with the cache option set to npm or pnpm. It caches the package manager store, not node_modules, which is faster and cache-key correct across different platforms and Node versions.

X (Twitter)LinkedIn