Deploy web app
Your monorepo works locally. Now let's deploy it to Vercel and see Turborepo in action during CI/CD. Vercel uses Turborepo under the hood, so your builds will be cached across deployments. Change one file → only affected packages rebuild.
You'll also see how to configure Vercel to build just the web app (not the entire monorepo).
Outcome
Deploy the web app to Vercel production and experience Turborepo's remote caching in CI (2s cached vs 16s full build).
Fast track
- Build the web app with Turborepo and see local caching (50x speedup)
- Commit and push to GitHub
- Deploy to Vercel with proper monorepo build configuration
- Experience remote caching in CI (change README → cache hit)
Hands-on exercise 2.5
Deploy the web app to Vercel and experience Turborepo's remote caching in production CI/CD.
Requirements:
- Build web app locally with
turbo build --filter=weband observe caching - Inspect build output in
apps/web/.next/ - Clean builds with
turbo build --forceto bypass cache - Commit all changes and push to GitHub
- Deploy to Vercel with proper build settings:
- Root Directory: leave blank (monorepo root)
- Build Command:
turbo build --filter=web - Output Directory:
apps/web/.next
- Watch Turborepo cache in Vercel build logs
- Make a change and deploy again to see cache invalidation
- Change README only and see remote cache hit (2s vs 16s)
Implementation hints:
--filter=webensures only web app builds, not entire monorepo- Local cache:
~/.turbo/or.turbo/cache/ - Remote cache: Vercel automatically uses Turborepo remote caching
- Build logs show cache hits: "cache hit (remote), restoring outputs"
- Vercel detects monorepo automatically if turbo.json exists at root
Key concepts to experience:
- Local caching: 50x speedup on unchanged builds
- Filtered builds: Only build what's needed for deployment
- Remote caching: CI reuses cache from previous builds
- Selective rebuilds: Change web → rebuild web, change README → cache hit
Build for production
First, let's see what a production build looks like. Run:
turbo build --filter=@geniusgarage/webWatch the output:
Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total
Time: 14.287s
>>> @geniusgarage/web:build
▲ Next.js 16.0.0
- Environments: .env
Creating an optimized production build ...
✓ Compiled successfully
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (3/3)
✓ Collecting build traces
✓ Finalizing page optimization
Route (app) Size First Load JS
┌ ○ / 5.12 kB 95.1 kB
├ ○ /features 1.85 kB 91.8 kB
└ ○ /_not-found 871 B 90.9 kB
This builds the web app. Notice:
- Next.js compiled 3 pages (home, features, not-found)
- Total bundle size ~95 kB for the home page
- All pages are static (○ symbol)
Run it again:
turbo build --filter=@geniusgarage/web Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 0.287s ⚡
>>> @geniusgarage/web:build: cache hit, replaying outputs
0.287s vs 14.287s - cached! This is what will happen in CI after the first deploy.
Notice Turborepo only ran 1 task (@geniusgarage/web:build). The UI package doesn't have its own build step - it's consumed as TypeScript source and compiled directly into the web app's Next.js build.
This is a common pattern for simple React component libraries. The UI package provides .tsx source files, and the Next.js app handles all the compilation (TypeScript, JSX, bundling). Turborepo still tracks the UI package - if you change a Button component, the web build cache invalidates and rebuilds.
Inspect build output
Check what Turborepo cached:
ls apps/web/.next/You'll see the Next.js build output. Pay special attention to:
static/- Static assetsserver/- Server-side codeBUILD_ID- Unique build identifier
Turborepo cached all of this. If you change the UI package, the web app will rebuild. But if you change something unrelated (like a README), the cache stays valid.
Clean and rebuild
Delete the build output:
apps/web/.nextNow run build again:
turbo build --filter=@geniusgarage/web Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 0.195s
>>> @geniusgarage/web:build: cache hit, replaying logs
Turborepo restored the entire .next/ directory from cache in 0.195 seconds!
Check apps/web/.next/ again - it's back. This is what makes Turborepo so fast in CI.
Commit your work
Before deploying, commit your changes:
git add .
git commit -m "feat: add shared UI package with Button and Card components"Push to GitHub:
git push origin mainMake sure you've initialized a Git repo and connected it to GitHub. If not, follow GitHub's instructions to create a new repo and push.
Deploy to Vercel
Go to vercel.com and sign in with GitHub.
Click "Add New", select "Project", and import your repository.
Configure Build Settings:
Vercel will auto-detect Next.js. You need to tell it to build only the web app:
- Framework Preset: Next.js (auto-detected)
- Root Directory:
apps/web - Build Command:
cd ../.. && turbo build --filter=web - Output Directory:
.next(default) - Install Command:
pnpm install(default)
The default Next.js build command is next build, which doesn't use Turborepo. By using turbo build --filter=web, you get:
- Turborepo caching (faster rebuilds)
- Dependency awareness (rebuilds if UI package changes)
- Consistent with local development
The cd ../.. navigates to the monorepo root before running turbo.
Click Deploy.
Watch Turborepo in CI
While deploying, click "Build Logs". You'll see:
Running "pnpm install"
...
Running "cd ../.. && turbo build --filter=web"
Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total (first deploy - nothing cached)
Time: 16.234s
>>> @geniusgarage/web:build
▲ Next.js 16.0.0
Creating an optimized production build ...
✓ Compiled successfully
First deploy: ~16 seconds, 0 cached.
Now make a small change. Update the home page:
<h1 style={{ fontSize: '3rem', marginBottom: '1rem' }}>🧠 GeniusGarage</h1>
<p style={{ fontSize: '1.5rem', color: '#666', marginBottom: '2rem' }}>
Your code snippet library
</p>Commit and push:
git add .
git commit -m "update tagline"
git pushWatch the new build logs:
Running "cd ../.. && turbo build --filter=web"
Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total
Time: 15.891s
Still not cached (we changed a file in web). But now change just the README:
echo "# GeniusGarage" > README.md
git add .
git commit -m "docs: add readme"
git pushWatch this build:
Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total ← Cached!
Time: 348ms
>>> @geniusgarage/web:build: cache hit (remote), replaying logs
348ms vs 16 seconds - Turborepo used the remote cache from the previous build!
Vercel stores Turborepo cache remotely. When files outside apps/web change (like README.md), the web build stays cached. This dramatically speeds up CI when you have multiple apps and packages.
Later in Section 6, you'll enable remote caching for your local machine too.
Verify deployment
Once deployed, Vercel gives you a URL like https://your-app.vercel.app.
Visit it and test:
- Home page shows
Buttonfrom@geniusgarage/ui - Features page shows
Cardcomponents from@geniusgarage/ui - Navigation works
- Everything looks identical to local
Your shared package is working in production!
What you deployed
Your production build includes:
apps/web- The Next.js apppackages/ui- Bundled into the web app
The UI package isn't deployed separately - it's compiled into the web app's bundle. This is important: shared packages are build-time dependencies, not runtime deployments.
Build performance over time
As your monorepo grows, you'll see the caching benefits compound:
Scenario 1: Change UI package
- UI package rebuilds
- Web app rebuilds (depends on UI)
- Other apps cache hit (didn't change)
Scenario 2: Change web app only
- Web app rebuilds
- UI package cache hit (didn't change)
- Other apps cache hit (didn't change)
Scenario 3: Change docs or configs
- Everything cache hit if it doesn't affect build inputs
This is how teams with 50+ packages keep CI fast.
Troubleshooting
Build fails with "Cannot find module '@geniusgarage/ui'"
- Verify
@geniusgarage/ui: workspace:*is inapps/web/package.json - Run
pnpm installto ensure workspace packages are linked
Build is slow even with caching
- Vercel free tier has CPU limits
- Check that build command uses
--filter=web(not building entire monorepo)
Changes to UI package don't rebuild web app
- Turborepo is working correctly - cache stays valid when dependencies don't change
- Force rebuild:
git commit --allow-empty -m "force rebuild" && git push
Done-when
Verify your deployment:
- Ran
turbo build --filter=weblocally and saw 14s build - Ran build again and saw cache hit (0.3s, 50x faster)
- Inspected build output in
apps/web/.next/ - Tested clean build with
--forceflag - Committed all changes to git
- Pushed to GitHub repository
- Created Vercel project connected to GitHub repo
- Configured Vercel build settings:
- Build Command:
turbo build --filter=web - Output Directory:
apps/web/.next
- First Vercel deployment succeeded (~16s)
- Visited deployed URL and verified:
- Home page loads with shared
Button - Features page shows 6 shared
Cardcomponents - Navigation works between pages
- No console errors
What's next
Section 2: Second App - You'll add a second app (the snippet manager) that reuses the same UI package. This is where monorepos really shine - both apps stay in sync automatically, and Turborepo caches builds across both.
Was this helpful?