← All PostsDevOps

CI/CD for Next.js Multi-Zones with Turborepo

Learn how to set up a smart GitLab CI pipeline for a Next.js Multi-Zones monorepo using Turborepo's --affected flag to build only what changed.

KJ
Krupa Jivani
·May 7, 2026·8 min read
CI/CDTurborepoMulti-ZonesGitLab

Running a full build across every zone on every commit is wasteful. With Turborepo's `--affected` flag and a smart GitLab CI pipeline, you can build only the zones that actually changed.

The Problem

In a Next.js Multi-Zones monorepo you have three independent apps — marketing, blog, and dashboard. A typical CI pipeline builds all three on every push. When you fix a typo in the blog, you don't want to wait for the marketing and dashboard builds to finish.

Turborepo's --affected Flag

Turborepo compares the current HEAD against a base commit and determines which packages have changed:

npx turbo build --affected --no-daemon

If only `apps/blog` changed, only the blog build runs. Turbo skips the others.

Setting TURBO_SCM_BASE in GitLab CI

Turborepo needs to know the base commit to diff against. In GitLab CI, this comes from different variables depending on the trigger:

# Merge Request

# Push export TURBO_SCM_BASE="$CI_COMMIT_BEFORE_SHA"

# Fallback export TURBO_SCM_BASE="HEAD~1" ```

The Zone Build Plan

Before running the build, print a summary so engineers know exactly which zones will build:

==========================================
 Zone build plan
==========================================
  [SKIP]   marketing  - no changes detected
  [BUILD]  blog       - source files changed
  [SKIP]   dashboard  - no changes detected
==========================================

This is generated by checking whether `git diff --name-only` includes files under `apps/<zone>/` or shared config files like `package.json` or `turbo.json`.

Shared Config Changes Trigger All Builds

If you change `package.json`, `turbo.json`, or `tsconfig.json` at the root, all three zones are marked as `[BUILD]`. This is intentional — shared config changes can affect any zone.

Full Pipeline Stages

install → lint → build
  • **install**: `npm ci` with npm/turbo cache
  • **lint**: `turbo lint --affected --no-daemon`
  • **build**: `turbo build --affected --no-daemon`, caches `.next` output

Key Requirements

  • Use `node:22` (not `node:22-alpine`) — alpine doesn't include git, and `--affected` requires git
  • Set `GIT_DEPTH: "0"` for full history so any SHA comparison works
  • Set `packageManager` in root `package.json` — Turborepo 2.x requires it

Conclusion

With this setup, a change to a single blog post triggers only the blog build. A change to a shared config triggers all three. Your pipeline stays fast as the monorepo grows.