如何提高 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、内存、数据库连接和出口带宽,再根据这些数据判断当前部署能承受多少并发,以及下一步该增加缓存、优化数据库,还是扩容应用实例。
备注:内容仅供参考。
