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

构建产物超出平台限制后,对nextjs的页面渲染类型的实践优化

从2000+ page的 Static Site Generation 触及限制后 到 ISR(Incremental Static Regeneration)的拓展

29 Jun 20264 min readUpdated9 Jul 2026Frontend · Next.js · Performance

Contents

  1. 根源: 两千多页内容全量预渲染
  2. 先纠正:不是“四种”那么简单
  3. 在哪儿渲染:Server Component 还是 Client Component
  4. 什么时候渲染:构建时、每次请求、还是按需
  5. 怎么送到浏览器:一次性,还是边渲边流
  6. 最后的组合

那天给自己的知识站发个新的部署(https://episteme-self.vercel.app/),Vercel build失败:

files should NOT have more than 15000 items, received 17467.

根源: 两千多页内容全量预渲染

这个项目(Episteme)是一相当于我通过许多遍的agentic workflow打磨(蒸馏)出来的知识展示库,里面涵盖了各种学科,历史,心理,哲学,物理,宇宙学等等 十几个学科两千多篇文章,大约 2300 个内容页。图省事,构建时让它们全量预渲染了。App Router 给每个预渲染页大概吐 6 个产物文件,html、rsc、几个配置加 fallback,一乘就奔两万,撞穿了 Vercel 单次部署 15000 文件的隐形上限。

问题不在 Vercel 抠门,在我把本该按需生成的东西全堆到构建期做完了。而“什么该在构建期做、什么留到运行时”,正好是 Next 渲染模型的核心。

先纠正:不是“四种”那么简单

我一开始也是按老四样记的——SSG(ServerSide Generation)、ISR(IncrementalStstic Regeneration)、SSR(ServerSide Render)、CSR(ClientSide Render)。这四个词好背,但它是 Pages Router 时代的数据获取心智,搬到 App Router 会把两件本来分开的事搅在一起。

在 App Router 里,我后来是拆成三个问题去想的:

  • 一个组件在哪儿渲染,服务器还是浏览器?

  • 一个页面什么时候渲染,构建时还是每次请求?

  • 渲染好的东西怎么送到浏览器,一次性,还是边好边流?

老四样其实是这三根轴上的几个点,单拎出来记反而容易混。下面就按这三件事走。

在哪儿渲染:Server Component 还是 Client Component

App Router 里组件默认是 Server Component,在服务器上跑,渲出 HTML 再加一份 RSC payload,就是我产物里那个 .rsc 文件。这类组件不往浏览器发自己的 JS,所以包更小。

标了 'use client' 的是 Client Component。这里有个我以前一直搞错的点:Client Component 不等于 CSR。它首次加载时照样会在服务器上被预渲成 HTML,到浏览器再“水合”(hydrate)、接管交互。真正纯客户端渲染的,是你明确关掉服务端渲染那种,比如 next/dynamic 带 ssr: false,或干脆在 useEffect 里取数据。

我这站正好两头都有。知识内容是 Server Component,在服务端出。那些 3D 场景,three.js 和 R3F 那套,跟服务器没关系、只能在你浏览器里跑,是实打实的客户端渲染。

什么时候渲染:构建时、每次请求、还是按需

这根轴只管服务端那侧什么时候干活:

  • Static(就是 SSG):构建时就把页面渲成静态成品,部署上去是现成 HTML,最快。代价是页面一多产物就爆——2300 页乘每页 6 个文件,我就是这么撞上 15000 的。

  • Dynamic(就是 SSR):不提前渲,每个请求到了才在服务器跑一遍。碰到 cookies()、headers()、读 searchParams 这类跟当次请求相关的东西,Next 会自动把这页切成 dynamic。适合千人千面,我的词条人人一样,用不上。

  • ISR:静态的基础上加一层“过期重渲”。构建期可以一页不渲,第一个读者来了才现渲一份缓存住,后面复用。也能按时间或手动(revalidatePath)让缓存过期重来。跟 SSG 是一件事的两头,一个提前全印,一个按需现印。

我的修法就在这根轴上:把高基数路由从 Static 改成 ISR,generateStaticParams 从返回全部改成返回空,构建期一页不渲。产物从 17467 掉到 7944。

怎么送到浏览器:一次性,还是边渲边流

最后一件是交付方式。默认是整页渲好再送。但你可以用 <Suspense>(或 loading.tsx)做流式渲染:能定的部分先送,慢的部分等好了再一块一块流过去,首屏不用死等最慢那块。

再进一步是 PPR(Partial Prerendering):同一个页面,静态的壳构建期就印好、请求一来立刻送,页面里动态的那几个洞再流式补上,静态和动态揉进一个路由。这个在我用的 15 还是实验特性,到 16 演进成 Cache Components、更接近默认。我这站还没用上,但方向就是往“静态动态不再二选一”走。

最后的组合

绕回来,搞清楚之后我这站其实是几样叠着跑的:知识内容是 Server Component 加 Static 转 ISR,在服务端出。3D 是纯客户端渲染,在你浏览器里跑。服务端和客户端不是二选一,是同时存在的两层。

以前一直没把这些拆清,全做预渲染,但这样显然是没有做好功课。

Filed under
  • Frontend
  • Next.js
  • Performance
← PreviousThe Conflict Between Local AI Compute And Cloud Compute本地AI与Agentic 工作流的冲突
Next →对于RAG技术的原理性拆解与个人思考
← Back to Writing