Next.js服务渲染性能

May 22, 2026

Next.js 核心优势是混合渲染(静态 / 动态),但如果混用不当、配置错误,会直接导致页面加载慢、服务端压力大、首屏卡顿等性能问题。

之前的个人项目使用了Next.js框架,仅考虑了功能的完成度,在性能方面没有完整考虑,所以导致性能不佳,最近有时间做了些性能上的优化,尤其在ISR、SSG渲染相关问题上遇到了不少问题,记录如下。

问题发现

如何判断页面是否真正开启了SSG/ISR。

  1. 编译阶段

next build 完成后会给出各个路由/页面的编译结果,其中也包含了渲染形态。

[shell]
符号	含义
ƒ	动态 SSR(每次请求实时渲染)
	ISR(静态生成 + 定时重验证)
	纯静态 SSG(构建时生成,永不重验证)
λ	旧版 Next.js 的动态标志

Next.js的原则是能静态(ISR静态增量、SSG纯静态)尽量使用静态,否则访问量大时可能达到性能上的瓶颈。

  1. 现网观察http返回header

编译结果不代表真正的访问结果。部署后发现访问页面时的Cache-Control值不对,返回为 private, no-cache, no-store, max-age=0, must-revalidate ,这跟文档直接描述不一致:

Incremental Static Regeneration (ISR) sets the Cache-Control header of s-maxage: <revalidate in getStaticProps>, stale-while-revalidate. This revalidation time is defined in your getStaticProps function in seconds. If you set revalidate: false, it will default to a one-year cache duration.

ISR实际未生效,还是动态渲染。ISR的返回应该是:

Cache-Control: s-maxage=60, stale-while-revalidate

优化

导致ISR/SSG未生效的原因可能包括多种。包括:

  • 使用了动态API:比如header、cookies、searchParams、noStore等
  • fetch配置问题
  • export const dynamic = 'force-dynamic'显式强制 SSR
  • 其他

这里遇到的几个典型问题详细分析。

动态API

使用了 next/header

即使只是读取 header/cookies 也会导致页面变为动态渲染,Next.js 无法在编译时知道你是否用 headers 改变了页面内容。所以只有出现相关api调用就会降级为动态渲染。

项目内容页面为了服务端跳转方便,在某个服务端组件中使用了从 header 取域名的方式,只要使用到了 header ,next server渲染方式就会变成dynamic。

使用以下 API 会自动将路由标记为动态:

  • cookies()
  • headers()

只要用一个 → 整页变成动态渲染。父布局用了 → 所有子页面全部动态。

缺失 generateStaticParams()

很显然,没有参数无法在build时生成。但是看文档好像也不是必须,不知道是否跟Next.js版本有关。

对于SSG页面,如果没有此方法调用,而是指定了revalidate,那么页面会变为ISR。如果二者都没有指定则又会默认为SSG。

使用了searchParams

文章翻页时将翻页ID写入了 searchParams: `page=3&limit=10`

在 App Router 里,只要 Server Component 直接用了 searchParams,页面会被强制变成「动态渲染(SSR)」,SSG/ISR 直接失效。注意Pages Router 不受这个影响,但也不会把 query 做进静态缓存 key。

  • Server Component 解构 searchParams → 页面自动 dynamic = 'force-dynamic'
  • dynamic = 'force-dynamic' → 不能 SSG、不能 ISR,只能每次请求都 SSR
[typescript]
// app/page.tsx
export default function Page({
  searchParams, // ❌ 服务端组件直接拿
}: {
  searchParams: Record<string, string>
}) {
  return <div>{searchParams.q}</div>
}

为什么 Next.js 这么设计:

  • searchParams请求级、用户级、任意多组合
  • 静态缓存(SSG/ISR)是「一个路径 → 一个 HTML」
  • query 无穷多,没法在 build 时预生成,也没法稳定缓存

解决方法:

想保留 ISR,又要读 query:正确做法应该是把读 searchParams 放到 Client Component,服务端只做静态渲染:

[typescript]
// app/page.tsx(服务端,可 ISR)
export const revalidate = 60; // ✅ ISR 生效

import SearchContent from './SearchContent';

export default function Page() {
  return <SearchContent />;
}
[typescript]
// app/SearchContent.tsx(客户端)
'use client';
import { useSearchParams } from 'next/navigation';

export default function SearchContent() {
  const searchParams = useSearchParams();
  const q = searchParams.get('q');
  return <div>Search: {q}</div>;
}

项目中我选择了绕过 SearchParams 的方法,把page参数放到URL path中。比如 xxx.com/page/1

编译结果一定准确吗

部署过程中,发现编译结果显示为 ISR,但是实际访问时的 Cache-Control 值明显不对。此时可以采用debug编译的方式: next build -d

debug编译时会给出详细的未生效信息,猜测是因为debug编译进行了更严格的检查校验。

[shell]
>> next build -d

Static generation failed due to dynamic usage on /zh-CN/notes/0b03eea7-bb82-4120-82e1-42a357742d1c, reason: headers
Static generation failed due to dynamic usage on /zh-CN/notes, reason: searchParams.page

 

排查后发现实际在页面的某个组件里面间接使用到了 cookie。

[text]
Page(文章页)
  └─ Layout(lobby/layout.tsx)
       └─ SiteHeader
            └─ getCacheUser()
                 └─ createClient()
                      └─ cookies()  ← 💥 这里触发动态渲染

lib/supabase/server.ts 中调用了 cookies(),这是 Next.js 的动态函数(Dynamic Function)。任何组件树中只要有一个地方调用了 cookies() / headers() / searchParams,整个路由就会被强制降级为动态渲染,无论页面本身配置了什么 revalidate 或 force-static

 

解决方法:

  1. 强制改为静态渲染试试
[typescript]
export const dynamic = 'force-static';

参考文档:

Forces a static rendering and caches the data of a layout or page by returning an empty values for cookies()headers() and useSearchParams()Causes revalidate to be set to false.

即页面生成之后永远不更新。即使设置revalidate也无效。

修改后发现确实生效了,但是引入了新的问题:无法获取用户信息。

force-static 会强制将所有动态函数(cookies()headers()返回空值,即使在 Suspense 隔离的子组件里也一样。所以 getCacheUser() 拿到的 cookie 是空的,supabase.auth.getUser() 自然返回 null

 

所以如果某些组件依赖动态函数,可能获取到的数据会跟想要的不一致了。

 

中间件会有影响吗

由于使用了supabase,在middleware里面会触发票据更新操作,这会涉及cookie的读写。是否会影响渲染逻辑?

middleware 里的 cookies() 操作不会影响页面的静态/动态判断(middleware 运行在独立的 edge runtime,不在渲染树内)。

数据fetch的问题

项目依赖从notion获取数据接口,所以很早就引入了redis作为数据缓存。Next.js中使用了自定义cache handler来降低开发复杂度。

找不到js文件

可能的原因:

  1.  构建产物与运行版本不一致(最常见)

浏览器或 CDN 缓存了旧 HTML,请求了已被新构建替换掉的 chunk。

[shell]
rm -rf .next
npm run build
npm run start
  1. 服务端缓存问题(这里使用了redis)

检查 cacheHandler 如果 Redis 中缓存了旧的 HTML 页面内容,需要清空 Redis 缓存后重新访问

每次编译不会重新生成缓存key吗?理论上会,但实际上存在例外情况。

总结

  • 想要 ISR(定期重新生成):去掉 force-static,只保留 revalidate
  • 想要完全静态(构建时生成,永不更新):只保留 force-static,去掉 revalidate
  • 想要静态生成 + 定期更新:只保留 revalidate,Next.js 有 generateStaticParams 的情况下会自动 ISR。
  • 需要静态渲染,那么就将依赖cookie、headers的组件放入client组件。
See all postsSee all posts