Turborepo basics
You have a monorepo with pnpm workspaces. Code sharing works. But two problems remain: everything rebuilds even when unchanged (10-minute CI runs for one-line changes), and coordinating build order across packages is complex - you need to ensure dependencies build before dependents.
Turborepo solves both with intelligent caching and automatic task orchestration. Let's see it work.
Outcome
Experience Turborepo's intelligent caching with a 17x speedup on unchanged builds and selective rebuilding when code changes.
Fast track
- Run
turbo buildand see ~5 second build - Run it again unchanged - see 17x speedup from cache (296ms vs 5s)
- Change a file - see selective rebuild with automatic cache invalidation
- Explore turbo.json to understand caching configuration
Hands-on exercise 1.3
Experience Turborepo's core feature - intelligent caching - by running builds and seeing automatic optimization in action.
Requirements:
- Run initial build with
turbo buildand record baseline time (~5s) - Run build again without changes and see cache hit (17x faster)
- Modify source code and see cache invalidation + selective rebuild
- Run build again to see new cache created
- Explore
turbo.jsonto understand caching configuration
Implementation hints:
- Pay attention to cache hit messages: "cache hit, replaying outputs"
- Notice the time difference: ~5s vs ~300ms
- Understand what triggers cache invalidation (source code, dependencies, environment variables)
- Learn turbo.json patterns:
^buildmeans "dependencies' build tasks" - Notice
outputsdefines what build artifacts get cached (.next/**); logs are cached automatically
Key concepts to experience:
- Hashing inputs: Turborepo hashes source + deps to create cache key
- Cache hit: Same hash = restore from cache instantly
- Cache miss: Different hash = rebuild and create new cache
- Selective rebuilding: Only changed packages rebuild, others use cache
Experience the cache firsthand
Make sure you're in the starter directory:
cd production-monorepos-starterRun your first Turborepo build:
turbo buildWatch the output:
Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total
Time: 5.123s
Notice:
- 0 cached - First build, nothing in cache
- Time: ~5s - Full Next.js build
Now run the exact same command again without changing anything:
turbo build >>> @geniusgarage/web:build: cache hit, replaying outputs [22B]
Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 296ms >>> FULL TURBO
296ms vs 5.1s - that's a 17x speedup! (Your numbers will vary slightly.)
Turborepo works with minimal setup. It automatically:
- Hashed your source code and dependencies
- Cached the
.nextbuild output - Detected no changes on the second run
- Restored from cache instantly
This is what makes monorepos faster than managing separate repos.
See cache invalidation
Caching only helps when nothing changes. Let's see what happens when you modify code. Open apps/web/app/page.tsx and change the tagline:
<p style={{ fontSize: '1.5rem', color: '#666', marginBottom: '2rem' }}>
Store your genius code snippets
</p>Change to:
<p style={{ fontSize: '1.5rem', color: '#666', marginBottom: '2rem' }}>
Manage and share your code snippets
</p>Save and build again:
turbo build >>> @geniusgarage/web:build: cache miss, executing [hash: e7f4a9c2]
Tasks: 1 successful, 1 total
Cached: 0 cached, 1 total
Time: 4.891s
Turborepo detected the change and rebuilt. The cache was invalidated because source code changed.
Run it once more (no changes):
turbo build >>> @geniusgarage/web:build: cache hit, replaying outputs
Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 134ms >>> FULL TURBO
Fast again! Turborepo cached the new build with the updated code.
Right now there's only one package, so cache invalidation affects everything. Once you add shared packages and multiple apps, you'll see true selective rebuilding: change packages/ui → only apps using it rebuild, other packages stay cached.
Try it
Let's verify your understanding with a few more experiments.
1. Check Turborepo version
turbo --versionYou should see:
2.6.0
2. Dry run to see what would execute
See what Turborepo plans to do without actually running it:
turbo build --dry-runOutput shows:
Tasks to Run
build
Task = build
Package = @geniusgarage/web
Hash = e7f4a9c2abc123
Cached (Local) = true
Cached (Remote) = false
Directory = apps/web
Command = next build
Outputs = .next/**, !.next/cache/**
Log File = .turbo/turbo-build.log
Dependencies =
Dependents =
Notice:
- Hash: Unique identifier for this build configuration
- Cached (Local): Whether this exact build is already cached
- Outputs: What files get stored in cache
3. Revert your change
Change the tagline back to original:
<p style={{ fontSize: '1.5rem', color: '#666', marginBottom: '2rem' }}>
Store your genius code snippets
</p>Build again:
turbo buildSince you're back to the original code, Turborepo will find the original cache (from your very first build) and restore it instantly. The cache remembers every unique state!
>>> @geniusgarage/web:build: cache hit, replaying outputs
Tasks: 1 successful, 1 total
Cached: 1 cached, 1 total
Time: 142ms >>> FULL TURBOCache hit! Even though you made changes in between, Turborepo remembers the original hash and restores that exact build.
How caching works
What you just experienced:
- First build - Turborepo hashes inputs (source code, dependencies) and stores outputs (
.next/**) with that hash - Second build (unchanged) - Hash matches, restore from cache
- Modified file - Hash changes, cache miss, rebuild
- Third build (unchanged) - New hash matches new cache, instant restore
This works for any task: build, test, lint, custom scripts.
Turborepo caches based on:
- Source code files
- Dependencies in package.json
- Environment variables (specified in
env) - Configuration files
Change any of these → cache invalidates → task reruns.
Explore turbo.json
Now that you've seen caching work, let's understand the configuration. Open turbo.json:
cat turbo.jsonYou'll see:
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}Let's break it down piece by piece.
Globaldependencies
"globalDependencies": ["**/.env.*local"]What it does: Any file matching this pattern invalidates ALL caches.
Why: Environment variables affect all apps. If .env.local changes, everything should rebuild.
Tasks.build configuration
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
}dependsOn: ["^build"]
- The
^means "dependencies' build tasks" - Before building this package, build all packages it depends on first
- Right now you only have one app, but once you add
packages/ui, Turborepo will build it before buildingapps/web
outputs: What to cache
.next/**- Cache everything in the.nextdirectory!.next/cache/**- EXCEPT the Next.js internal cache (negation pattern)
Tasks.dev configuration
"dev": {
"cache": false,
"persistent": true
}cache: false - Don't cache dev servers (they're always fresh)
persistent: true - Keep the dev server running (don't kill it after task completes)
This is the Turborepo 2.x pattern for long-running tasks like next dev.
You just experienced Turborepo's core benefits:
- 17x faster builds through intelligent caching
- Automatic cache invalidation when code changes
- Minimal configuration - it worked immediately
- Hash-based caching - Turborepo remembers every unique build state
Next, you'll add shared packages and see how Turborepo orchestrates builds across multiple packages, running tasks in parallel and caching selectively.
Commit
This is an exploratory lesson with no code changes (you reverted the tagline change), so there's nothing to commit. Section 1 begins the real building.
Done-when
Verify you've experienced Turborepo's caching:
- Ran initial
turbo buildand saw ~5s build time - Ran build again unchanged and saw cache hit (~300ms, 17x faster)
- Modified
apps/web/app/page.tsxand saw cache miss + rebuild - Ran build again and saw new cache hit with updated code
- Checked Turborepo version with
--versionflag - Used
--dry-runto preview build plan without executing - Understood caching workflow: hash inputs → check cache → build or restore
- Explored
turbo.jsonand understood key concepts - Ready for Section 1 where you'll see caching with multiple packages
Time to build.
Was this helpful?