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 toapps/<name>. - Build Command: just
turbo build— Turbo 1.8+ infers the filter from the Root Directory. No need to addturboas a dependency; it is globally available on Vercel. - Ignored Build Step:
npx turbo-ignore --fallback=HEAD^1. The--fallbackflag is what stops it from full-building every new branch. - Share packages with
transpilePackages(Next.js) or adist/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/docstriggers 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 buildsucceeds 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:
| Setting | Value | Notes |
|---|---|---|
| Root Directory | apps/www | The single most-skipped step; cause of most “module not found” errors. |
| Framework Preset | Astro / Next.js | One of 35+ presets; auto-detected. |
| Build Command | turbo build | Turbo 1.8+ infers --filter from Root Directory. |
| Install Command | leave default | Vercel auto-detects pnpm/npm/yarn and installs the whole workspace from the repo root. |
| Output Directory | framework default | Astro dist, Next.js .next. |
| Ignored Build Step | npx turbo-ignore --fallback=HEAD^1 | Skips the build when this app’s dependency graph is unchanged. |
Two things people get wrong here:
- Build command. You do not have to install
turboas a dependency — it is globally available on every Vercel build, and since Turbo 1.8 the filter is inferred from the Root Directory, so plainturbo buildis enough. The older long formcd ../.. && turbo run build --filter=wwwstill works if you want to be explicit. - Node version. Pin it once with
engines.nodein the rootpackage.jsonso 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:
- 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'],
};
- Pre-built packages. Each package has a
buildscript that emits todist/, and itspackage.jsonpointsmain/typesat the built output. Turbo builds dependencies before dependents because the build task declares"dependsOn": ["^build"]inturbo.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:
| Item | Hobby | Pro |
|---|---|---|
| Price | Free (non-commercial only) | $20 per seat/month, $20 usage credit |
| Concurrent builds | 1 | 12 |
| Projects | 200 | higher |
| Deploys/day | 100 | higher |
| Build time cap | 45 min/deploy | 45 min/deploy |
| Build minutes | not 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 installat the repo root in the build command instead of letting Vercel detect the workspace — breaks pnpm/yarn workspaces. - Plain
npx turbo-ignorewithout--fallback— every new branch full-builds. outputsinturbo.jsonnot 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 explicitcd ../.. && turbo run build --filter=<app>also works. - Why does my new branch always full-build?: You are missing
--fallback=HEAD^1onturbo-ignore. With no prior deploy on a fresh branch, plainturbo-ignorebuilds 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.
Related
- Deploying an Astro site on Vercel
- Vercel content site go-live checklist
- Vercel build failed common causes
- Vercel ISR vs SSG for Content Sites: Which Wins
External references: Vercel: Deploying Turborepo and Vercel Fair Use Guidelines.