ByteNoteByteNote
静态导出时失效没有落点
字

字节笔记本

2026年10月7日 · 约 10 分钟读完

静态导出时失效没有落点

API中转
¥120

Pages 路由里,静态页靠 getStaticProps,动态段再加 getStaticPaths。App Router 里对应的是页面组件加上 generateStaticParams。next.config.js 里的 output: 'export' 会把站点收成静态文件,同时拿掉需要服务器的那一部分。同一份样本里又放了 revalidatePath 的路由,这两件事不能同时成立。

静态导出和按路径再验证不能写在一起

两种路由的生成点不同

Pages 的例子是 pages/staticpage.js。组件接收 data。getStaticProps 在构建时取数,放进 props。动态段 pages/posts/[id].js 用 getStaticPaths 返回 params.id,fallback: false。不在列表里的 id 就是没有这一页。注释里写了也可以用 blocking。false 和 blocking 是两种未列出路径的处理,不要同时想要“没有页”和“第一次请求再生成”。

App Router 的例子是 app/page.js 直接返回标题,以及 app/blog/[slug]/page.js。generateStaticParams 把文章列表映射成 { slug }。构建时为每个 slug 生成一页。数据可以在异步的页面组件里取,构建会等这些异步完成。这和 Pages 里把取数放在 getStaticProps 是同一件事的两个入口,不要两套都留。

next build 产出静态 HTML。配置里 output: 'export' 才是完全导出。对话写明:完全静态导出不支持增量再生成。样本却在 app/api/revalidate/route.js 里 POST 一段 JSON 路径,调用 revalidatePath,返回 { revalidated: true, now: Date.now() }。导出模式下没有这个常驻路由。要按路径失效,就不要开 output: 'export',用常规部署。要纯静态文件,就删掉这条再验证路由。两个都留,构建或部署会在“没有服务器”上失败,或者再验证请求无处可去。

几万页仍然是一次构建里的参数列表

大量页面的样本是 app/[category]/[id]/page.js。generateStaticParams 调用 fetchAllProducts,映射出 category 和字符串化的 id。另一个工具函数 generateInBatches 以 batchSize = 1000 循环拉取,直到某一页长度为 0,把参数推进 allParams 再返回。分批只影响怎么取数据,最后仍要把全部参数交给这次构建。它不会把生成拆成多次独立部署。页数是几万时,构建时间和数据源的分页都要能跑完这个循环。中途失败会少一截参数,对应的页面不会出现。

id.toString() 必须有。数字 id 放进路径参数,类型和目录名不一致时,生成结果对不上链接。fallback 那种“先上线再补页”在这份导出配置里没有。要几万页,就在构建前把数据源准备全。

样本还写了 next/dynamic 去拆一个组件。那是浏览器包的体积,不是静态 HTML 的页数。页数由 generateStaticParams 的返回长度决定。动态导入不能减少要生成的路径。

分批拉取最后仍要交回全部路径

先选定部署形态

页面内的表单或接口若还要服务器,就不要 output: 'export',动态段用 generateStaticParams,更新用单独的再验证,并且接受那不是纯文件导出。只发静态文件,就用导出,删掉再验证路由,getStaticPaths 或 generateStaticParams 列出的路径就是全部页面。fallback: false 时,列表之外没有页。几万条记录要在一次构建里把参数集齐,分批只是取数的方式。

导出之后哪些文件还在

output: 'export' 的产物是一组 HTML 和资源,没有常驻的 Node 进程去接 /api/revalidate。revalidatePath 需要能改服务器上的缓存。纯静态文件没有这份缓存。所以样本里的 POST 路由和导出配置只能留一个。留路由,就用常规的 next build 与带服务器的部署,动态段仍由 generateStaticParams 在构建时生成。留导出,就把 app/api/revalidate/route.js 删掉,更新页面的办法是重新构建再发文件。

fallback: false 时,getStaticPaths 没列出的 id 不会有页。blocking 是第一次有人访问再生成,这依赖服务器。导出模式下没有“第一次访问再生成”。几万个商品若数据源在构建时不完整,缺的那些 id 不会在上线后补出来。generateInBatches 把每批 1000 条推进 allParams,直到某次返回空。循环结束前抛错,allParams 不会被拿去生成,这次构建失败,而不是悄悄少一截。要确认页数,看构建日志里的路径数量是否等于数据源的总数。

id.toString() 让路径参数是字符串。链接若拼了数字以外的格式,对不上生成出的目录。分类和 id 两段都要在返回值里。只返回 id、页面在 [category]/[id],这一页不会按预期出现。

next/dynamic 拆的是浏览器里的组件包。静态 HTML 的数量不因此减少。首页 app/page.js 没有 generateStaticParams,它只有一页。博客的 slug 列表有多长,博客就有多少页。不要把首页的静态和动态段的静态混成“整个站只有构建时的一页”。

再验证路由读的是 JSON 里的 path,调用 revalidatePath,响应体是 revalidated 为真以及当时的 Date.now()。导出之后没有进程接这个 POST。调用方会得到连接失败,或这条路由根本没被导出。要保留失效,就关掉 output: 'export',用带服务器的部署。要纯文件,就删掉 app/api/revalidate/route.js,更新靠重新构建。

generateInBatches 的 batchSize 默认 1000。currentPage 从 0 增加,直到某一页长度为 0。每一页映射出 category 和 id.toString(),推进 allParams。返回值仍是完整列表。它避免的是一次把全部行装进内存,不是把站点拆成多次上线。构建日志里的页面数应等于数据源总数。少一截说明循环中途失败,而不是导出功能少生成了一些。

Pages 路由的 fallback: false 表示路径表之外没有页。blocking 表示第一次访问再生成,这需要服务器。和 output: 'export' 写在一起时,后一种没有落点。动态段只保留一种策略。App Router 用 generateStaticParams 的返回长度决定页数,不要再留一套 getStaticPaths。

样本配置里还有 experimental.outputFileTracingRoot,值是 process.cwd()。这是文件跟踪的根,不决定生成多少 HTML。next/dynamic 同样只拆组件包。页数只看参数列表的长度。首页没有动态段,就只有一页。博客的 slug 有多少,博客目录就有多少页。 同一份配置里如果既有 output: 'export' 又有 app/api/revalidate/route.js,构建目标是一组没有服务器的文件,POST 却要求有进程去改缓存。两条只能留一条。留导出时,动态段的全部地址必须在构建前进到 generateStaticParams 或 getStaticPaths。fallback: false 的名单之外没有页,上线后也不会补。分批函数的循环必须跑到空页才返回。中途抛错应让这次构建失败,而不是带着半份 allParams 去发布。发布后抽查一个在数据源里、一个不在数据源里的 id。前者有 HTML,后者没有。dynamic 导入只影响浏览器包的大小,抽查时不要用包体积代替页面数量。

纯静态导出和按路径失效不能同时成立。要文件,就删掉失效路由,全部地址在构建前列出。要失效,就不要开启导出,改用带服务器的部署。分批拉取只改变取数方式,最后仍把全部参数交回这一次构建。名单之外的地址不会在上线后补出来。首页没有动态段就只有一页。动态段有多少参数就有多少页。组件懒加载只影响浏览器里的脚本体积,不减少要生成的页面。抽查时拿一个存在的编号和一个不存在的编号对照。

相关文章

分享: