用 Next.js App Router 降低 80% 慢响应:生产环境迁移实录
用 Next.js App Router 降低 80% 慢响应:生产环境迁移实录
导语
很多团队从 Pages Router 切到 App Router 后,首屏反而变慢了,慢的不是构建,而是“用户点了链接却要等服务器返回 HTML”的那段时间。我在一个真实项目里连续踩了三次坑,最后把慢响应从平均 2800ms 压到了 480ms,这篇文章把判断标准和落地点都给你。
正文
用 Next.js 自带的分析和 Chrome DevTools 拆开看:
- TTFB 高:服务端渲染耗时,或者数据库/接口慢。
- LCP/FID 高:客户端 hydration 重,或者资源阻塞。
大多数“App Router 变慢”是前者:组件里直接 await 了多个接口,页面要等全部数据回来才吐 HTML。Pages Router 时代用 getStaticProps 会把数据拉取和构建解耦,到了 App Router 里很多人直接把接口放在 Server Component 顶层,反而把请求串行了。
2. 三个可落地方案
#### 方案一:并行数据获取 + 手动编排
// ❌ 顺序拉取,TTFB 被最慢的接口拖垮
const user = await fetchUser()
const orders = await fetchOrders()
const stats = await fetchStats()
// ✅ 并行拉取
const [user, orders, stats] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchStats(),
])
// 只把当前页必须的字段序列化给客户端
return <Page user={user} orders={orders} stats={stats} />
实测:一个聚合三个接口的首页,TTFB 从 2.1s 降到 980ms。
#### 方案二:把非首屏组件推迟到客户端
// app/page.tsx
import { Suspense } from 'react'
import Recommendations from './Recommendations'
export default async function Page() {
// 首屏必须数据
const product = await fetchProduct()
return (
<main>
<ProductSection data={product} />
{/* 推荐列表延迟加载,不阻塞首屏 HTML */}
<Suspense fallback={<Skeleton />}>
<Recommendations />
</Suspense>
</main>
)
}
效果:首屏 HTML 提前 400-600ms 返回,慢响应率直接下降。
#### 方案三:给动态接口加全页缓存
不是所有页面都值得实时渲染。像产品详情、帮助中心这类更新频率低于 5 分钟的内容,完全可以在服务端做 ISR:
// app/product/[slug]/page.tsx
export const revalidate = 300
export default async function Page({ params }) {
const product = await fetchProduct(params.slug)
return <ProductPage data={product} />
}
生产数据:Vercel Edge 上命中缓存时,TTFB 能压到 200ms 以下。
3. 还有一个被忽略的坑:loading.js 的时机
App Router 会在有 Suspense 边界时才立刻显示 loading 状态。如果你在顶层 await,loading.js 反而要等服务端返回才会渲染。解决办法是把数据获取下沉到具体段,不要让整页卡在同一个 Promise 上。
4. 结论与判断标准
- 页面首屏接口超过 2 个:优先做并行 + 分段 Suspense。
- 页面内容更新频率 < 5 分钟:加
revalidate。 - 看到 loading.js 不立刻出来:检查顶层是否有大块
await。 - 目标:把 95 分位慢响应控制在 800ms 以内。
收尾
如果你刚迁移到 App Router,先拿一个慢页面做“数据获取拆分”实验,通常第一天就能看到 TTFB 下降 30%-50%。别急着全局重构,先跑通一个页面的判断标准,再复制到其他页面。
评论区
登录后可评论。












