Skip to content
Swil
  • Writing
  • Work
  • Vibe
  • Lab/lab3D scenes, a playable piano, the solar system.
  • 3D Models/modelsModels you can turn and look around.
  • Music/musicA listening room, an aespa archive and a piano.
  • Terminal/terminalA small command line in the browser.

Things to click, turn and listen to.

  • Planner/tools/workA week of tasks with a focus timer.
  • Image editor/tools/image-editCrop, resize and adjust in the browser.
  • Typing/tools/typingA typing trainer.
  • All tools →/tools

Small tools I use myself, all in the browser.

  • Finuwell↗finuwell.comAn atlas of how the body works.(opens in a new tab)
  • Swil News↗news.supwil.comAI and tech news, updated daily.(opens in a new tab)
  • SwirlRead↗swirl-read.vercel.appA Markdown reader for a notes folder.(opens in a new tab)

Separate apps I built, each on its own site.

  • Resources/resourceTools and reading I keep going back to.
  • Perf/perfThis site's own measurements.
  • Changelog/changelogWhat changed on the site, and when.

Reading I go back to, and how this site is measured and changed.

Writing/blogWork/projectVibe/vibe

Explore

Lab/lab3D scenes, a playable piano, the solar system.3D Models/modelsModels you can turn and look around.Music/musicA listening room, an aespa archive and a piano.Terminal/terminalA small command line in the browser.

Planner/tools/workA week of tasks with a focus timer.Image editor/tools/image-editCrop, resize and adjust in the browser.Typing/tools/typingA typing trainer.All tools →/tools

Finuwell ↗(opens in a new tab)finuwell.comAn atlas of how the body works.Swil News ↗(opens in a new tab)news.supwil.comAI and tech news, updated daily.SwirlRead ↗(opens in a new tab)swirl-read.vercel.appA Markdown reader for a notes folder.

Resources/resourceTools and reading I keep going back to.Perf/perfThis site's own measurements.Changelog/changelogWhat changed on the site, and when.

Site

  • Home
  • Writing
  • Work
  • Vibe
  • RSS

Playground

  • Lab
  • 3D Models
  • Music
  • Terminal

Tools

  • Planner
  • Image editor
  • Typing
  • All tools→

Apps

  • Finuwell↗
  • Swil News↗
  • SwirlRead↗

More

  • Resources
  • Perf
  • Changelog

Elsewhere

  • GitHub
  • LinkedIn
  • Bilibili
  • Email

Huahao Shang, 2026 · Privacy

Built with Next.js · measured at /perf

Writing/Essay

`.next` 长到 170 GB:Turbopack persistent cache 在 monorepo 下的代价

突然发现一个项目从几个G的大小膨胀到170G,磁盘占用瞬间增加20%,在惊叹之余的细节探索。

23 May 20264 min readUpdated9 Jul 2026Next.js · Performance · Turbopack

Contents

  1. 这个 cache 到底是什么
  2. monorepo 里那个 barrel 把问题放大了
  3. 怎么改
  4. 收尾

事情的起点很普通。前两天打开 DaisyDisk,我习惯用它看磁盘占用,扇形图一眼能看出谁占得多。本来只想顺手清一下下载文件夹,结果一打开就愣住了:上次扫描硬盘用了 40% 多,这次直接到 60%。

DaisyDisk 那种螺旋扇形图,outlier 很好认。最外圈最厚的那一片是 swil-fitneheal/apps/web/.next/。点进去,单独一个 dev/cache/turbopack/<hash>/ 目录就占了 170 GB。仓库根目录还有一份没人管的 .next/,叠在一起接近 178 GB。

这个 cache 到底是什么

打开目录看一眼就明白了:上千个 .sst、几百个 .meta、一份 LOG、一份 CURRENT。这是 RocksDB 的 on-disk 格式,Turbopack 拿它做 persistent cache。

LSM-tree 的特点是只往尾部追加,靠后台 compaction 回收旧版本。我这个目录里 SST 一共 2145 个,其中 654 个已经合并到顶层(每个 258 MB,是默认的 level-size cap),还有 1500 多个散落在低层等着合并。删除标记 .del 只有 13 个。cache 几乎没在有效回收:HMR 每次重编译都往里写一批新 SST,旧的不会立刻物理删掉,要等 compaction;而 Turbopack 现在的 compaction 触发,对「持续小步改」的开发模式偏保守。

跟 Webpack 的 cache.type = 'filesystem' 对照一下。Webpack 用 single-pack 的 pack 文件,再加一份滚动覆盖的 .pack.old,每次构建结束写一份新的盖上去。容量大致跟项目模块图的大小成正比,中型项目几百 MB,撑死 1~2 GB。Turbopack 这套 LSM 增量写入更快、查询带索引,但磁盘占用会随写入次数涨,而不是随项目规模封顶。

两种 cache 的 size 函数不一样:Webpack 是 f(项目规模);Turbopack persistent cache 是 f(项目规模 × HMR 次数 − compaction),第二项才是那 170 GB 的来源。

monorepo 里那个 barrel 把问题放大了

之前用 Turbopack 跑 next dev 时没遇到过这种事。当时 fitnuhealth 还是单 app 的 Next 仓库,.next/dev/cache/turbopack/ 一般 2~4 GB,偶尔到 8 GB,重启 dev server 就归零,不用专门盯。

改成 monorepo 之后完全不同。repo 里有 apps/web(Next.js)、apps/mp(Taro 微信小程序)、packages/content(共享内容包)。160 多个 story、citations / edges 都在 packages/content/src/,apps/web 通过 workspace 直接 import。

我一开始怀疑是 mp 构建生成的 *.generated.json:每次 pnpm build:weapp,几个被大量模块依赖的文件 hash 全变,看起来很合理。跑了一遍 grep 才发现,apps/web 根本没 import 那些 generated.json,mp 的构建和 web 的 Turbopack cache 没有依赖关系。所以「monorepo 让 cache 涨」得把原因说细一点。

实际链条是这样:

packages/content/src/<story>.ts (160+ 个独立 story 文件)

↓ import

packages/content/src/stories/by-region/<region>.ts (7 个大陆 barrel)

↓ import

packages/content/src/stories/index.ts (STORIES 总 barrel)

↓ import

apps/web/src/app/atlas/[region]/[slug]/page.tsx

apps/web/src/app/atlas/[region]/[slug]/[scene]/page.tsx

apps/web/src/lib/search.ts

apps/web/src/lib/tracking/story-meta.ts

apps/web/src/app/sitemap.ts

任意一个 story 改一个字,所在 region barrel 的导出 hash 会变,连带 stories/index.ts 也变,然后所有从总 barrel 拿数据的页面整片失效。Turbopack 把每个失效模块的新版本写进 LSM 顶部,旧 SST 等 compaction,而 compaction 跟不上写入。

写一段内容、改一段引言、保存。一下就有几十个模块 entry 进 cache。一天几百次保存,一周几十 GB,170 GB 就这么堆出来的。

回头看,不是 Turbopack 不行,也不是 monorepo 本身有问题,而是一个被广泛 import 的 barrel,叠上 LSM 没有 size cap。单 app 时代没事,是因为当时没有「一个 barrel 聚合 160 个文件」这种结构。

怎么改

最直接的做法是把热路径从总 barrel 上摘下来。两个 region 路由(atlas/[region]/[slug]/...)在 URL 里已经知道是哪个大陆,不必拉全量 STORIES,改成按 region 查:

// packages/content/src/stories/lookup.ts

const REGION_LOADERS = {

conditions: () => import("./by-region/conditions").then(m => m.CONDITIONS_STORIES),

vitamins: () => import("./by-region/vitamins").then(m => m.VITAMINS_STORIES),

// ... 7 个大陆

};

export async function storyBySlugInRegion(region, slug) {

const list = await REGION_LOADERS[region]();

return list.find(s => s.slug === slug);

}

import() 要用动态形式,这样 lookup.ts 本身不静态依赖任何 story 文件。改一个 condition story 只会让 by-region/conditions.ts 标脏,不再连累 lookup.ts 和挂在上面的页面。以前改一个字、十个模块进 cache,现在大概是两个。

sitemap.ts 和 search.ts 这种确实要全量 stories 的,barrel 可以留着。generateStaticParams 只在 build 时跑,可以把 STORIES 的 import 挪进函数体里做动态 import,模块顶层就别静态依赖了。

改完跑 tsc --noEmit 和 vitest --run:typecheck 过,215 个测试全过。dev cache 的 LSM 写入压力大概降到原来的 1/7,一两周应该能从体积上看出来。

收尾

dev cache 平时跟 node_modules 一样,占地方但很少主动去管。LSM 持久化 cache 加上一个聚合大量数据的 barrel,会把背景资源变成前台麻烦。

Turbopack 比 Webpack 快,这点没问题;代价是磁盘预算要重新估。更值得查的是 import 拓扑:任何被几十、上百个文件 fan-in 的 barrel,在 incremental cache 里都是放大器。

硬盘莫名其妙涨了几十 GB,可以先开 DaisyDisk,再看 .next/dev/cache/,十有八九是它。然后翻翻自己的 barrel,有没有一个文件塞得太满。

环境:Next.js 16 + Turbopack persistent cache,pnpm workspace monorepo(apps/web + apps/mp + packages/content),macOS Sequoia,磁盘可视化用 DaisyDisk。

Filed under
  • Next.js
  • Performance
  • Turbopack
  • Frontend
  • Webpack
← Previous从 AWS Amplify 到 Vercel:部署和持续开发
Next →The Conflict Between Local AI Compute And Cloud Compute本地AI与Agentic 工作流的冲突
← Back to Writing