Next.js App Router 性能优化实战笔记
最近两周在处理一个多布局站点时,最大的感受并不是功能复杂,而是很多性能问题都藏在默认行为里。如果不把渲染边界理顺,页面看起来能跑,但首屏和切换成本会一直在增长。
1. 先把页面按“静态、半静态、动态”重新分类
最初的问题是所有页面都被同一套布局和全局状态包住,结果连本应静态输出的页面也被拖进了动态渲染链路。处理这类问题时,我现在习惯先做一个粗粒度分类:
- 完全可以预渲染的页面
- 依赖少量用户态信息的页面
- 必须实时读取数据的页面
只有把页面真正分层,后续的缓存和数据获取策略才有意义。
2. layout 嵌套深度会直接影响缓存命中
App Router 的好处是布局可以复用,但问题也出在这里。公共布局里放太多依赖请求头、Cookie 或客户端状态的逻辑时,整棵子树都会失去静态化机会。
我最后保留的原则是:布局只做稳定结构,动态判断尽量下沉到页面段或者客户端壳层。
export const dynamic = "force-static";
export async function generateMetadata() {
return {
title: "Aether Tech Log | 个人编程笔记",
};
}
3. metadata 不只是 SEO,也会影响页面复用方式
一个很容易忽略的点是 metadata 的继承关系。如果业务页继续沿用根布局的 metadata,搜索引擎和社交分享信息会串掉,用户体验也会变得奇怪。
所以我后来把 metadata 当成布局边界的一部分处理:博客、营销页、后台登录页分别生成,不再期待“根布局全局兜底”解决所有场景。
4. 客户端动画壳和服务端布局分离
为了保留部分页面过渡动效,同时又不影响服务端 metadata,我把真正带 use client 的动效壳拆成独立组件,再让布局本身回到服务端组件。
这样做的好处很直接:
- metadata 可以稳定工作
- 动效范围只作用在需要的路由段
- 页面结构更容易推断
5. bundle 体积通常不是组件多,而是边界放错
之前我一直把首屏 JavaScript 增长归因到组件数量,后来发现更多时候是因为把整个页面都提前变成客户端组件。只要一个布局段错误地引入客户端依赖,相关子树就会一起变重。
检查时我会优先看三件事:
- 有没有把纯展示组件错误写成客户端组件
- 有没有把图表、富文本、动画库直接塞进首屏
- 有没有把仅后台使用的能力带到前台页面
6. 页面切换卡顿通常和数据重新获取有关
在 App Router 场景里,路由切换慢不一定是动画问题。更常见的情况是页面切换时触发了一次不必要的服务端请求或重新计算。
这时候我更愿意优先确认缓存策略,而不是先做前端层面的过渡优化。
7. 最后留下一个固定检查清单
每次上线前我都会简单过一遍:
- 首页和博客页是否仍是静态输出
- 详情页 metadata 是否独立
- 动效是否只存在于真正需要的业务段
- 不同域名或子路由之间有没有互相泄露信息
总结
App Router 本身没有明显问题,真正的问题在于默认结构很容易被写成“看起来可用,但边界模糊”。把布局、metadata、动画和缓存策略一起看,通常比单独盯着 Lighthouse 分数更有效。