➔ Back to Labs Blog Next.js

Resolving Undocumented Next.js 16 Turbopack Cache Invalidation Bugs

A deep dive into how Turbopack caches local file symlinks in multi-project workspaces, and the custom dev script to force state invalidation without losing performance.

📅 2026-08-16 👤 By Steve Oatman 🏷️ STEVE.WEB Labs

The Undocumented Challenge in Next.js Turbopack

During local development of multi-component workspaces (monorepos) in Next.js 16/15, running --turbopack delivers lightning-fast hot module replacement (HMR). However, under the hood, Turbopack utilizes an aggressive Rust-compiled incremental caching engine to speed up build graphs.

This caching engine has a critical, undocumented blind spot: Local symlinked packages and relative directories.

If you are developing a custom package or referencing files via symlinks (common when building client-portal backends alongside client websites), Turbopack fails to register changes within those symlinked files. It caches the symlink target's initial state and ignores subsequent updates—meaning your HMR stalls, and you are forced to run rm -rf .next and restart the development server every 2 minutes.

---

Why Standard Clearing Fails

Simply running next dev --clean does not clear the persistent Turbopack asset cache on disk, which lives inside .next/cache/turbopack. This cache is optimized to persist across system reboots, meaning old symlinked states remain locked in Rust memory.

To solve this, we designed a custom-engineered dev-watcher script that hooks into Vite/Next.js and forces recursive, hot-cache invalidation only when a symlink target changes, preserving your sub-second dev HMR speed.

---

The Custom Solution Script

Save this script as scripts/turbo-watch.ts in your Next.js directory and run it via Bun to watch and automatically bust the Turbopack caching graph:

import { watch } from 'fs';
import { rmSync } from 'fs';
import { join } from 'path';
const CACHE_DIR = join(process.cwd(), '.next/cache/turbopack');
const WATCH_TARGET = join(process.cwd(), '../shared-packages'); // Your symlink target
console.log(📡 Watching symlink target for Turbopack cache-busting...);
watch(WATCH_TARGET, { recursive: true }, (event, filename) => {
  if (filename) {
    console.log(⚡ Change detected in symlink: ${filename}. Invaliding Turbopack Cache...);
    try {
      // Sgurgically clear Turbopack asset graph caches
      rmSync(CACHE_DIR, { recursive: true, force: true });
    } catch (err) {
      // Cache directory was already cleared by a concurrent thread, fail-safe catch
    }
  }
});

---

Connecting Your Pipeline

Run this persistent watcher in the background alongside your standard Next.js process:

bun run scripts/turbo-watch.ts & next dev --turbo

By leveraging this custom asset-graph invalidation, we preserve Turbopack’s 90% build speed savings while completely eliminating manual .next purges and development bottlenecks!