Vercel Monorepo Content Site Deploy: Turbo + Multiple Apps

Deploying a content site from a Turborepo monorepo on Vercel is mostly settings, not magic. Here is the exact root config, ignored builds, and shared package wiring that works.

A Turborepo with a marketing site, a docs site, and a web app is a normal setup in 2026. Vercel handles it, but only if you configure the root directory, ignored builds, and shared packages correctly. The wrong combination either rebuilds everything on every push (slow and expensive) or refuses to find your shared UI library at all.

Background

A Vercel project deploys exactly one app. For a monorepo with N apps, you create N Vercel projects, each pointing to a different directory. Turbo helps by caching builds and skipping unaffected apps. The friction is at the boundary: telling Vercel where each app lives, what to build, and when to skip.

How to tell

  • Pushing a tiny edit to one app triggers builds on all three projects.
  • Vercel cannot find @workspace/ui and fails with “Module not found” in the build log.
  • The wrong app is being deployed because Vercel followed the wrong build command from the repo root.
  • Your turbo.json works locally but Vercel ignores the cache entirely.

Quick verdict

Put each app under apps/, create one Vercel project per app, set Root Directory to apps/<name>, use turbo as the build wrapper, and add an ignoreCommand to skip builds that do not touch the app.

Project structure that works

A typical layout:

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 @workspace/ui as a normal dependency ("workspace:*" for pnpm, "*" for npm/yarn workspaces).

Configure Vercel per app

Settings → General for each Vercel project:

  • Root Directory: apps/www (or apps/docs, etc.). This is the most-skipped step and the cause of most “module not found” errors.
  • Build Command: cd ../.. && pnpm turbo run build --filter=www (replace pnpm with npm / yarn as needed). The --filter is what makes Turbo build only this app and its dependencies.
  • Install Command: leave blank — Vercel auto-detects pnpm / npm / yarn and runs the workspace install from the repo root.
  • Output Directory: framework default (Astro: dist, Next.js: .next).
  • Node version: pin via engines.node in the root package.json.

Skip unaffected builds with Ignored Build Step

The single biggest cost saver in a monorepo on Vercel. In each project, Settings → Git → Ignored Build Step:

npx turbo-ignore www

Replace www with the app name. turbo-ignore exits 0 (skip build) if no files in that app’s dependency graph changed since the last successful deploy. So a docs-only edit no longer rebuilds www.

For a non-Turbo monorepo, the manual version:

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

Vercel skips the build if this exits 0.

Shared packages without a publish step

Two patterns work; pick one and stick to it:

  1. Transpile-on-build (Next.js). Use transpilePackages in next.config.js:
/** @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 the package’s package.json points main / types at the built output. turbo builds dependencies before dependents because of pipeline.build.dependsOn = ["^build"].

Pattern 1 is simpler for content sites. Pattern 2 scales better when the package is also published to npm.

Common mistakes

  • Leaving Root Directory at / and wondering why the wrong app builds — every project must point to its own subdir.
  • Using npm install at the project root in the build command instead of letting Vercel detect the workspace — breaks pnpm / yarn workspaces.
  • Forgetting --filter in the build command — Turbo builds every app on every deploy, defeating the point of a monorepo.
  • Skipping turbo-ignore and paying for full rebuilds on docs typos.
  • Not pinning Node in the root package.json — Vercel may pick different Node versions across projects.
  • Putting environment variables in only one project when two apps need them — env vars are scoped per Vercel project.

FAQ

  • Do I need Turbo to deploy a monorepo on Vercel?: No. Vercel supports npm / yarn / pnpm workspaces directly. Turbo is for caching and turbo-ignore. For under 3 apps, plain workspaces are often enough.
  • Can one repo deploy to one Vercel project with multiple frameworks?: No. One project = one framework / build. Use multiple projects.
  • What about Nx instead of Turbo?: Works the same way: set Root Directory per app, run nx build <app> in the build command, use Nx’s affected commands for ignored builds.
  • How do preview deploys work across projects?: Each project gets its own preview URL per PR. You can configure your PR template to list all three URLs, or use the Vercel comment for each project.
  • Does Vercel cache node_modules across projects in the same monorepo?: Each project has its own cache. Turbo’s remote cache helps across projects if you link them all to the same Turbo remote.

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