Next.js 图片优化:从懒加载占位到性能的完整认知
图片通常是页面体积最大的资源,也是首屏性能最容易翻车的地方。这篇不谈"要用 WebP"这种老生常谈,而是讲清 浏览器 + 框架两层 对图片的处理机制,以及我们该在哪里发力。
一、浏览器的懒加载干预
现在的浏览器对 <img> 有原生懒加载:给 loading="lazy",滚动到视口附近才开始加载。但它的行为有个细节容易踩坑——
<img loading="lazy" src="big.png" />
浏览器为了省流量,会把视口外的懒加载图替换成占位符,推迟 load 事件。这在控制台会有这样的提示:
[Intervention] Images loaded lazily and replaced with placeholders.
Load events are deferred.
关键认知:这不是错误,是浏览器的主动优化。但要小心两点:
- 首屏内的图不要 lazy。第一屏就可见的图用 lazy 反而会延迟渲染,首屏应给
loading="eager"或直接不给 loading(默认 eager)。 useEffect里读图片尺寸/加载状态会有偏差。懒加载图的 load 事件被推迟,实际可能比你预期的晚很多才触发。
二、next/image 到底做了什么
Next.js 的 <Image> 组件不是简单包一层 img,它做了四件框架层面的事:
import Image from "next/image";
<Image
src="/cover.jpg"
width={1200}
height={630}
sizes="(max-width: 768px) 100vw, 768px"
priority // 首屏图加这个,等于 eager + 预加载
/>
- 自动生成多种尺寸 + 按视口下发合适体积的图片(
sizes告诉它选哪张)。 - 内置 lazy 载入机制:IntersectionObserver 检测元素进入视口才真正发请求。
- 保留宽高比占位:传了
width/height就能定死盒子,避免布局抖动(CLS)。 - 占位方案可配:
placeholder="blur"可以用极小缩略图占位,配合blurDataURL。
为什么有些图会"闪烁"?
如果你看到图片先是一小片模糊、滚动到才清晰,那是 next/image 的懒加载 + placeholder 在起作用。它不是 bug。如果你不想要这个行为:
<Image ... priority /> // 首屏:立即加载,不懒加载
<Image ... loading="eager" /> // 明文要求 eager
三、远程图片的坑
next/image 默认不允许任意远程域名(防 SSRF/性能)。要在 next.config 白名单:
// next.config.ts
export default {
images: {
remotePatterns: [
{ protocol: "https", hostname: "images.example.com" },
],
},
};
注意:远程图无法自动知道宽高,必须手动传 width 和 height(或 fill + 父容器定高),否则无法占位、会跳动。
四、最佳实践清单
| 场景 | 做法 |
|---|---|
| 首屏 hero 图 | <Image priority />,不 lazy |
| 文章列表封面 | 用 next/image,sizes 给准确值 |
| 正文内大图,不急着显示 | 保持默认 lazy,接受占位 |
| 远程图片 | 白名单域名 + 手动宽高 |
| 纯装饰图 | 直接 CSS background,不进 image 优化管线 |
小结
图片优化的本质是多尺寸下发 + 懒加载 + 占位防抖动三件事。浏览器和 next/image 已经做了大部分,我们要做的就是:首屏 eager、准确 sizes、远程图给宽高。想清楚"谁在加载、什么时候加载",就不会被各种"闪烁"和"占位"吓到——那都是优化在正常工作。
真正要警惕的反而是:把首屏图 lazy、sizes 乱填、远程图不给尺寸——这三件事会让优化反过来变成瓶颈。