那天给自己的知识站发个新的部署(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 是纯客户端渲染,在你浏览器里跑。服务端和客户端不是二选一,是同时存在的两层。
以前一直没把这些拆清,全做预渲染,但这样显然是没有做好功课。