Deploy a Content Site From a Turborepo Monorepo on Vercel

The exact Vercel settings to ship a content site from a Turborepo monorepo: per-app Root Directory, turbo build, turbo-ignore with --fallback, and shared packages without a publish step. Verified June 2026.

A Turborepo with a marketing site, a docs site, and a web app is a normal 2026 setup. Vercel ships it cleanly, but only if you get three settings right per project: the Root Directory, the build command, and the Ignored Build Step. The wrong combination either rebuilds all three apps on every push (slow, and on a Pro plan it burns paid build minutes) or fails outright with Module not found: @workspace/ui.

This guide gives you the exact settings Vercel’s own docs recommend as of June 2026, plus the two bugs that catch most people: new-branch over-building and cache-miss “output not found” errors.

TL;DR

  • One Vercel project per app. A project deploys exactly one framework build.
  • Put apps under apps/. Set each project’s Root Directory to apps/<name>.
  • Build Command: just turbo build — Turbo 1.8+ infers the filter from the Root Directory. No need to add turbo as a dependency; it is globally available on Vercel.
  • Ignored Build Step: npx turbo-ignore --fallback=HEAD^1. The --fallback flag is what stops it from full-building every new branch.
  • Share packages with transpilePackages (Next.js) or a dist/ build step — no npm publish needed.

How a monorepo maps onto Vercel

A Vercel project deploys one app with one framework preset. So a monorepo with N apps becomes N Vercel projects, each pointing at a different subdirectory in the same Git repo. Every project watches the whole repo for pushes; the per-project Root Directory and Ignored Build Step decide which project actually builds.

Turborepo’s job is the build graph: it knows that apps/www depends on packages/ui, caches task outputs by content hash, and exposes turbo-ignore so a docs-only commit never rebuilds the marketing site.

One commercial-use caveat first, because it bites content sites specifically: the Vercel Hobby plan is non-commercial only (last reaffirmed in Vercel’s Fair Use Guidelines, updated Feb 2026). Any site running ads, affiliate links, or otherwise tied to financial gain must be on Pro ($20 per seat/month). A monorepo of revenue content sites is unambiguously commercial.

Symptoms you have it wrong

  • A one-line edit to apps/docs triggers builds on all three projects.
  • The build log fails with Module not found: Can't resolve '@workspace/ui'.
  • Vercel deploys the wrong app because it ran the build command from the repo root.
  • turbo build succeeds but Vercel reports the output directory is empty on a cache hit.
  • Every new PR branch full-builds even when nothing in that app changed.

Project structure that works

my-monorepo/
├── apps/
│   ├── www/                 → marketing (Astro)
│   ├── docs/                → docs (Next.js)
│   └── app/                 → web app (Next.js)
├── packages/
│   ├── ui/                  → shared React components
│   ├── config-tsconfig/     → shared tsconfig
│   └── eslint-config/       → shared lint
├── turbo.json
├── package.json             → workspaces: ["apps/*", "packages/*"]
└── pnpm-workspace.yaml      → if using pnpm

Each apps/<name>/package.json declares the shared package as a normal dependency: "@workspace/ui": "workspace:*" for pnpm, or "@workspace/ui": "*" for npm/yarn workspaces.

Per-app Vercel settings (June 2026)

These mirror the values Vercel auto-detects when you import a Turborepo, taken from the official deploy doc. Set them under Settings → General for each project:

SettingValueNotes
Root Directoryapps/wwwThe single most-skipped step; cause of most “module not found” errors.
Framework PresetAstro / Next.jsOne of 35+ presets; auto-detected.
Build Commandturbo buildTurbo 1.8+ infers --filter from Root Directory.
Install Commandleave defaultVercel auto-detects pnpm/npm/yarn and installs the whole workspace from the repo root.
Output Directoryframework defaultAstro dist, Next.js .next.
Ignored Build Stepnpx turbo-ignore --fallback=HEAD^1Skips the build when this app’s dependency graph is unchanged.

Two things people get wrong here:

  1. Build command. You do not have to install turbo as a dependency — it is globally available on every Vercel build, and since Turbo 1.8 the filter is inferred from the Root Directory, so plain turbo build is enough. The older long form cd ../.. && turbo run build --filter=www still works if you want to be explicit.
  2. Node version. Pin it once with engines.node in the root package.json so every project builds on the same major; otherwise different projects can land on different Node versions.

Why --fallback matters in the Ignored Build Step

Plain npx turbo-ignore compares against the previous successful deploy. On a brand-new branch there is no prior deploy for that branch, so it conservatively builds — meaning every new PR full-builds all apps, defeating the purpose. Adding --fallback=HEAD^1 tells it to diff against the previous commit when no prior deploy exists, so only genuinely-changed apps build. This is the value Vercel itself sets on import, and the fix for the well-known “turbo-ignore always builds all projects for new branches” complaint.

For a non-Turbo workspace, the manual equivalent in Ignored Build Step (from each app’s Root Directory):

git diff HEAD^ HEAD --quiet . ../../packages/ui ../../packages/config-tsconfig

Vercel skips the build when this exits 0 (no changes in the app or its shared deps).

Share packages without publishing

Two patterns, pick one:

  1. Transpile on build (Next.js). Let the consuming app compile the shared TypeScript at build time:
/** @type {import('next').NextConfig} */
module.exports = {
  transpilePackages: ['@workspace/ui', '@workspace/config-tsconfig'],
};
  1. Pre-built packages. Each package has a build script that emits to dist/, and its package.json points main/types at the built output. Turbo builds dependencies before dependents because the build task declares "dependsOn": ["^build"] in turbo.json.

Pattern 1 is simpler for a content site that imports a shared component library. Pattern 2 scales better when the package is also published to npm.

The cache-hit “output not found” trap

A common failure: turbo build hits the Turborepo cache, restores nothing into the expected output directory, and Vercel errors that the build produced no output. The cause is almost always a mismatch between your framework’s real output and the outputs key in turbo.json. Align them:

{
  "$schema": "https://turborepo.com/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**", ".vercel/output/**"]
    }
  }
}

Run turbo build locally and confirm the directory it produces matches what Vercel expects for your framework preset. Two related gotchas as of June 2026: Next.js Skew Protection always produces a Turborepo cache miss (it injects a per-deploy env var into the hash), and Turborepo below 2.4.1 can ship missing assets under Skew Protection, so upgrade to 2.4.1 or later.

Cost: what a monorepo actually uses

Build minutes are the line item most teams forget. As of June 2026:

ItemHobbyPro
PriceFree (non-commercial only)$20 per seat/month, $20 usage credit
Concurrent builds112
Projects200higher
Deploys/day100higher
Build time cap45 min/deploy45 min/deploy
Build minutesnot included~$0.014/min standard machine

The practical takeaway: a three-app monorepo of revenue content sites belongs on Pro, and turbo-ignore is your main lever for keeping paid build minutes down. Without it, a single typo in one app’s README can trigger three full builds.

Common mistakes

  • Leaving Root Directory at /, so every project builds the same (wrong) app.
  • Hard-coding npm install at the repo root in the build command instead of letting Vercel detect the workspace — breaks pnpm/yarn workspaces.
  • Plain npx turbo-ignore without --fallback — every new branch full-builds.
  • outputs in turbo.json not matching the framework’s real output dir — cache-hit “output not found”.
  • Not pinning Node in the root package.json — projects drift onto different Node majors.
  • Scoping an env var to only one project when two apps need it — env vars are per-project on Vercel.
  • Running a commercial content site on Hobby — against the Fair Use Guidelines and grounds for suspension.

FAQ

  • Do I need Turbo to deploy a monorepo on Vercel?: No. Vercel supports npm/yarn/pnpm workspaces directly. Turbo adds caching and turbo-ignore. For two or three apps, plain workspaces are often enough, but you lose selective builds.
  • Can one Vercel project deploy multiple frameworks?: No. One project equals one framework build. Use one project per app.
  • What’s the exact build command Vercel recommends now?: turbo build (Turbo 1.8+, filter inferred from Root Directory). The explicit cd ../.. && turbo run build --filter=<app> also works.
  • Why does my new branch always full-build?: You are missing --fallback=HEAD^1 on turbo-ignore. With no prior deploy on a fresh branch, plain turbo-ignore builds conservatively.
  • What about Nx instead of Turbo?: Same shape — Root Directory per app, nx build <app> as the build command, and Nx affected commands in the Ignored Build Step.
  • Can a content site run on the free Hobby plan?: Only if it is truly non-commercial. Ads or affiliate revenue make it commercial, which requires Pro under Vercel’s Fair Use Guidelines.

External references: Vercel: Deploying Turborepo and Vercel Fair Use Guidelines.

Tags: #Indie dev #Vercel #Hosting #monorepo #Workflow