PAYATHON 2026

如何提高 Next.js 网站的加载速度?

技术老齐

先用生产构建定位瓶颈,再针对图片传输、浏览器卡顿和服务端响应分别优化。图片用 next/image 按显示尺寸加载,文本页面尽量预渲染,并合理设置缓存。后续加入视频时,可以使用对象存储或视频服务,通过 CDN 分发。访问量增加后,再根据压测结果扩容。

仅凭目前的描述,还无法确定具体原因。如果线上页面只有文本也会卡顿,应先检查 JavaScript 执行、数据请求和渲染逻辑。图片优化可以等加入媒体功能后再单独验证。

1. 先确认时间花在哪里

测试时使用生产构建。开发模式有编译和调试开销,不能直接代表上线后的性能。如果项目采用常规 npm scripts,可以运行:

npm run build
npm run start

在 Chrome DevTools 中打开 Network 和 Performance,分别复现首次访问、滚动和点击操作:

现象优先检查
HTML 很久才返回服务端渲染、数据库查询、上游 API、冷启动
HTML 很快,图片迟迟不显示图片体积、请求开始时间、源站速度、图片优化耗时
内容已经显示,点击或滚动仍卡顿主线程长任务、重复渲染、大量 DOM、图片解码
用户少时正常,并发增加后变慢CPU、内存、数据库连接、带宽和缓存命中率

检查图片请求时,要区分等待响应和下载分别花了多久。等待响应过长,应检查图片源站、优化服务和缓存;下载耗时过长,则从文件体积和分发网络入手。

记录优化前后的 LCP、INP、CLS,以及资源大小和服务端响应时间。对比时保持页面、设备和网络条件一致,才能判断改动是否有效。生产构建和性能检查可参考 Next.js 上线优化指南。

2. 图片按实际显示尺寸加载

图片加载慢时,先检查是否把高分辨率原图直接发到了手机上。用 CSS 缩小图片只会改变显示尺寸,下载的文件仍可能很大。

next/image 可以生成不同尺寸的候选图片,默认延迟加载非首屏图片。sizes 也要与布局相符,浏览器才能选择合适的资源。具体说明见 Next.js 图片优化说明。

假设封面位于 public/images/cover.jpg,原图尺寸为 1600 × 900,页面内容最大宽度为 960px:

import Image from 'next/image'

export default function Page() {
  return (
    <main style={{ maxWidth: 960, margin: '0 auto' }}>
      <Image
        src="/images/cover.jpg"
        alt="文章封面"
        width={1600}
        height={900}
        sizes="(max-width: 960px) 100vw, 960px"
        style={{ width: '100%', height: 'auto' }}
        loading="eager"
        fetchPriority="high"
      />
    </main>
  )
}

配置时需要结合页面布局调整:

  • width、height 应保持原图宽高比,帮助浏览器预留空间,减少布局偏移。
  • sizes 应反映图片的显示宽度。例如,三列卡片中的图片通常不需要按整屏宽度下载。
  • 示例中的封面是首屏主要图片,因此使用 loading="eager" 和 fetchPriority="high"。列表中靠后的图片保留默认懒加载,避免争抢首屏带宽。
  • 使用远程图片时,通过 images.remotePatterns 配置允许的来源,并限制到实际使用的域名和路径。

各项配置的具体行为可查阅 next/image API 文档。旧项目应核对所用版本,避免混用旧版图片组件的写法。

源文件也要处理,降低不必要的分辨率,选择合适的格式和压缩程度。模糊占位图能改善等待体验,但不会减少主图的下载量。

3. 页面卡住时,检查 JavaScript 和渲染

如果内容已经显示,按钮却点不动,或滚动不顺畅,可以在 Performance 中查看卡顿期间的主线程任务,优先排查:

  • 渲染过程中反复执行的大数组处理或复杂计算。
  • useEffect 更新状态后,又不断触发自身执行。
  • 一次渲染过长的文本列表或图片列表。
  • 首屏就加载的编辑器、图表库、视频播放器和第三方脚本。

长列表可以先分页,确有需要时再使用虚拟列表。较重且暂时用不到的组件,可以通过 next/dynamic 延迟加载,等用户打开对应功能时再渲染。具体用法见 Next.js 懒加载指南。

使用 App Router 时,文本展示和数据读取可以优先保留在 Server Components 中,需要交互的部分则拆成较小的 Client Components,避免在页面顶层随意添加 "use client"。这样有助于减少发送给浏览器的 JavaScript。服务端组件是否在每次请求时重新执行,取决于页面的渲染和缓存策略。

4. 文本页面优先预渲染,缓存要结合更新频率

目前以文本为主,可以按内容性质选择方案:

内容类型适合考虑的方式
很少变化的介绍、帮助内容构建时静态生成
定期更新、允许短暂延迟的公开内容缓存并按需或定时重新验证
用户个人信息、权限相关内容按用户读取,避免进入公共缓存

Pages Router 可以按需求使用 getStaticProps、ISR 或 getServerSideProps。App Router 的缓存 API 和默认行为因版本而异,需要明确配置并验证,不能假定所有 fetch 请求都会自动缓存。

没有依赖关系的数据请求可以并行执行,减少串行等待。如果只有一块内容加载较慢,可以用 Suspense 或路由级 loading.tsx 先展示其他部分。加载提示能改善等待体验,慢查询和慢接口仍需单独优化。相关建议见 Next.js 数据获取与生产优化建议。

5. 视频按需播放,提前规划存储和分发

加入视频后,避免所有视频在页面打开时同时下载。普通点播内容可以先显示封面,等用户播放时再请求视频:

export default function Video() {
  return (
    <video
      controls
      playsInline
      preload="none"
      poster="/images/video-poster.jpg"
      width={1280}
      height={720}
      style={{ width: '100%', height: 'auto' }}
    >
      <source
        src="https://media.example.com/videos/demo.mp4"
        type="video/mp4"
      />
      当前浏览器不支持视频播放。
    </video>
  )
}

使用时将示例域名替换为实际媒体地址。preload="none" 是给浏览器的提示,封面图仍会产生请求,也需要控制尺寸。默认不要开启自动播放,尤其是同一页面有多个视频时。具体说明见 Next.js 视频使用指南。

对于较长的视频,或需要适应不同网络环境的播放场景,可以考虑 HLS/DASH 自适应码率流。这需要相应的转码、封装和播放器支持,普通 MP4 地址不会自动具备这些能力。

6. 用压测验证高并发下的瓶颈

图片和视频适合存放在对象存储或媒体服务中,通过 CDN 分发,让 Next.js 服务主要处理页面和业务请求。高流量下,还要关注图片首次优化的计算开销。冷缓存请求可能同时触发图片处理,压测也应覆盖这种情况。

多实例部署时,需要检查缓存共享和失效同步,避免不同实例持续返回不同版本的内容。前置 CDN 也要保留 Next.js 所需的请求与响应信息,不能将所有页面一律设为公共缓存。相关配置见 Next.js 自托管指南。

压测至少覆盖文本页面、图片列表和视频播放场景,并分别测试冷缓存与热缓存。观察响应时间的 P95/P99、错误率、CPU、内存、数据库连接和出口带宽,再根据这些数据判断当前部署能承受多少并发,以及下一步该增加缓存、优化数据库,还是扩容应用实例。

备注:内容仅供参考。