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-daemonIf 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.