JackyLove 的技术人生

人生艰难,唯有一技傍身才能慢慢走向通途。个人博客,记录生活,分享技术,记录成长。专注全栈技术,next技术。

第18章—组件篇Images

首次发表于 2024-03-22, 更新于 2024-03-22

前言

本篇开始我们进入组件篇,为大家详细介绍 Next.js 内置的四个组件,分别是:<Image>Font<Link><Script>,它们都是 Next.js 基于原生 HTML 标签做了诸多优化而专门抽象的组件,在开发的时候尽可能的使用这些组件。

其中 <Image> 组件实现了懒加载和根据设备尺寸自动调整图片大小,<Link> 组件实现了后台预获取资源,从而让页面转换更快更平滑,<Script> 组件使得你可以控制加载和执行第三方脚本等等……具体的功能和 API 我们会在组件篇中详细介绍。

本篇将介绍 <Image> 组件,因为图片往往占据了网页大小很大一部分,图片的优化可谓是重中之重。Image 组件也提供了非常多的 prop 和配置项,了解这些 prop 以及背后的原理有助于我们更加深入的使用 Image 组件,带来更好的用户体验。

图片与 LCP

1. 图片占比

根据 Web Almanac 中的介绍,图片大小占典型网站页面大小的很大一部分。根据统计,2021 年 6 月网站的总大小中位数是 2019 KB(移动端),其中 881 KB 是图像。这比 HTML(30 KB),CSS(72 KB),JavaScript(461 KB)和字体(97 KB)的总和还要多。

在绝大多数页面上(70% 移动设备,80% 桌面),最有影响的就是图片。Largest Contentful Paint(最大内容绘制,简写:LCP) 是一种 Web 性能指标,可以标识首屏中最大的内容元素。大部分时候,该元素都有图片。

2. LCP 背景

考虑到 LCP 并不算是一个常为大家熟知的概念,所以我们单独介绍下 LCP。

对于 Web 开发者而言,衡量网页主要内容的加载速度一直是一个挑战。

传统我们会使用 load、DOMContentLoaded 等方法,但它们并不表示用户在屏幕上看到的内容的时间。

而像首次内容渲染(FCP),如果页面有 loading 效果,那获取的时间也是不准确的。

当然也有首次有效绘制(FMP)等指标,但是这些指标非常复杂,往往是错误的。所以也不能用来确定主要内容的加载时间。

根据 W3C Web 性能工作组中的讨论和 Google 的研究,要衡量网页主要内容的加载时间,更为准确的方法是查看最大元素的呈现时间。这就是 LCP。

3. LCP 概念与标准

Largest Contentful Paint (LCP) 指标会报告视口内可见的最大图片或文本块的呈现时间(相对于网页首次开始加载的时间)。

为了提供良好的用户体验,网站应尽力将 Largest Contentful Paint 设置为 2.5 秒或更短。

让我们看一些 LCP 的例子:

那么问题来了,页面往往是分阶段加载的,网页中最大元素可能是在不断变化的,LCP 是怎么计算出来的呢?

首先,浏览器会将 LCP 的元素限定在一些特定的元素类型内,比如<img> 元素、包含文本节点或其他内嵌级别文本元素的子项的块级元素、为自动播放 <video> 元素而绘制的第一帧、动画图片格式(例如 GIF 动画)的第一帧等等(这是为了简化这个问题,如果什么元素都计算一遍大小,就太复杂了而且没必要)。

然后浏览器在绘制完第一帧后,就会立即分派 largest-contentful-paint 类型的 PerformanceEntry,用于标识最大的内容元素。在渲染后续帧后,只要最大内容元素发生变化,该 API 就会再分派另一个 PerformanceEntry。简单的来说,每一帧绘制的时候,浏览器都会标示出最大内容元素。

当用户与页面发生交互(通过点按、滚动或按键),浏览器就会停止报告新条目。(因为用户交互通常会改变向用户显示的内容,就比如滚动操作)。一般来说,发出的最后一个条目的 startTime 值是 LCP 值。

<Image>

1. 功能特性

讲解 LCP,只是为了帮助大家认识到图片优化的重要性(毕竟最大内容元素往往是图片)。回到 <Image> 组件上,Next.js 基于原生的 HTML <img> 元素,实现了这些优化功能:

  1. 尺寸优化:自动为每个设备提供正确尺寸的图片,也会使用现代图片格式如 WebP 和 AVIF。
  2. 视觉稳定性:防止图片加载时发生布局偏移(Layout Shift)
  3. 更快的页面加载:图片只有在进入视口的时候才会加载,使用懒加载功能,并可选使用模糊占位符
  4. 灵活配置:按需进行图片调整,远程服务器上的图片也可以

这些功能我们会在讲解组件 API 的时候一一涉及。

2. 基础使用

这是 <Image> 组件的使用示例,看起来如同使用正常的 img 元素一样:

// app/page.js
import Image from 'next/image'
 
export default function Page() {
  return (
    <Image
      src="/profile.png"
      width={500}
      height={500}
      alt="Picture of the author"
    />
  )
}

3. 支持的 props

<Image> 组件支持传入这些 props:

Prop 示例 类型 是否必须
src src="/profile.png" String
width width={500} Integer (px)
height height={500} Integer (px)
alt alt="Picture of the author" String
loader loader={imageLoader} Function -
fill fill={true} Boolean -
sizes sizes="(max-width: 768px) 100vw" String -
quality quality={80} Integer (1-100) -
priority priority={true} Boolean -
placeholder placeholder="blur" String -
style style={{objectFit: "contain"}} Object -
onLoadingComplete onLoadingComplete={img => done())} Function -
onLoad onLoad={event => done())} Function -
onError onError(event => fail()} Function -
loading loading="lazy" String -
blurDataURL blurDataURL="data:image/jpeg..." String -

其中,srcwidthheightalt 是必须的,其他是可选的。让我们逐一讲解。

4. src-必须

src 支持传入一个静态导入图片文件,也支持传入一个路径字符串。

使用本地图片的时候,就可以采用静态导入图片文件的方式。通过 import 导入 .jpg.png 或者 .webp 格式的文件。使用示例如下:

// app/page.js
import Image from 'next/image'
import profilePic from './me.png'
 
export default function Page() {
  return (
    <Image
      src={profilePic}
      alt="Picture of the author"
      // width={500} automatically provided
      // height={500} automatically provided
      // blurDataURL="data:..." automatically provided
      // placeholder="blur" // Optional blur-up while loading
    />
  )
}

使用静态文件导入的方式,widthheight 不需要传入,Next.js 会自动提供。

注意:动态的 await import() 或者 require() 是不支持的。import 必须是静态的,才可以在构建的时候进行分析。

使用远程图片的时候,src 可以传入一个 URL 字符串。

由于 Next.js 在构建的时候无法获取远程文件,你需要手动提供 widthheight 和可选的 blurDataURL props。

widthheigth 属性用于推断图像正确的宽高比(Aspect ratio,也称为纵横比)以及避免图片加载的时候发生布局偏移。但 widthheight并不决定图片最终的渲染尺寸,这也很好理解,因为你也可能设置拉伸模式等。

// app/page.js
import Image from 'next/image'
 
export default function Page() {
  return (
    <Image
      src="https://s3.amazonaws.com/my-bucket/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

注意使用远程地址的时候,Next.js 要求在 next.config.js文件中定义支持的远程图片地址,这是为了防止一些恶意使用。配置方法如下:

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 's3.amazonaws.com',
        port: '',
        pathname: '/my-bucket/**',
      },
    ],
  },
}

关于配置文件中的 remotePatterns,本篇后面我们会详细讲解。

5. width-必须

width 属性表示图片渲染的宽度,它以像素为单位,影响图片的显示大小。

该属性必须,除非是静态导入图片或者图片有 fill 属性。

6. height-必须

height 属性表示图片渲染的高度,它以像素为单位,影响图片的显示大小。

属性必须,除非是静态导入图片或者图片有 fill 属性。

7. alt

alt 属性用于描述图片,提供给屏幕阅读器和搜索引擎使用。如果图片被禁用或者加载图片时出现错误,它会作为降级的文本提示。

alt 属性应该使用在不改变页面含义的情况下替代图片的文本描述,不应该重复图片上方或下方标题中提供的信息。

如果图片纯粹是装饰或者不是给用户使用,那 alt 属性应该用一个空字符串表示(alt="")。

8. loader

 loader 表示解析图片地址的自定义函数。让我们看段示例代码:

'use client'
 
import Image from 'next/image'
 
const imageLoader = ({ src, width, quality }) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
 
export default function Page() {
  return (
    <Image
      loader={imageLoader}
      src="me.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

函数接受 srcwidthquality作为参数,返回图片的 URL 字符串。

注意:由于 loader prop 传入的是一个函数,所以需要使用客户端组件,在这个例子中,顶部也是用的 'use client'

每一个图片都添加一个 loader 非常麻烦,你也可以使用 next.config.js 中的 loaderFile 配置项来配置应用里的每个 next/image 实例,而无需传递 loader prop。这个配置项本篇后面会讲解。

9. fill

fill={true} // {true} | {false}

fill表示是否将图片填充父元素。默认值为 false。当图片的 widthheight 未知的时候很有用。

但是要注意:使用 fill,父元素必须指定为 position: "relative"position: "fixed"position: "absolute"。而 img 元素会默认自动指定为 position: "absolute"

如果图片没有应用其他样式,图片会被拉伸以填充容器。

当然填充容器有很多方式,CSS 属性里的 object-fit: "container"object-fit: "cover" 也都可以用来填充图像。

让我们简单看下区别:

// app/page.js
import Image from 'next/image'
import profilePic from './image.png'
 
export default function Page() {
  return (
    <div style={{
      width: '200px',
      height: '200px',
      backgroundColor: "#ccc",
      position: 'relative'
    }}>
     <Image
        src={profilePic}
        alt="Picture of the author"
      />
    </div>
  )
}

正常显示如下:

如果添加 fill 属性后:

<Image
  src={profilePic}
  alt="Picture of the author"
  fill={true}
/>

效果如下,图片会被拉伸以适应容器:

如果使用 object-fit: "contain"

<Image
  src={profilePic}
  alt="Picture of the author"
  fill={true}
  style={{objectFit: "contain"}}
/>

效果如下,图片在保持其宽高比的同时填充元素的整个内容框:

如果使用 object-fit: "cover"

<Image
  src={profilePic}
  alt="Picture of the author"
  fill={true}
  style={{objectFit: "cover"}}
/>

效果如下,图片在保持其宽高比的同时填充元素的整个内容框。如果对象的宽高比与内容框不相匹配,该对象将被剪裁以适应内容框:

10. sizes

HTML 5.1 新增加了 img 元素的 srcsetsizes 属性,用于设置响应式图像。

当我们需要不同的设备展示不同的图片的时候,就需要用到 srcset。这里具体又分为两种情况,一种是图片是相同的尺寸,但是不同的分辨率对应不同的图片,即高分辨率下对应高倍图。一种是相同的图片内容,但依据设备显示的更大或者更小。这分别对应着 srcset 的两种语法。

先说第一种,根据分辨率不同展示不同的图片,使用示例如下:

<img
  srcset="elva-fairy-320w.jpg, elva-fairy-480w.jpg 1.5x, elva-fairy-640w.jpg 2x"
  src="elva-fairy-640w.jpg"
  alt="Elva dressed as a fairy" />

效果如下:

srcset 是由逗号分隔的一个或多个字符串组成,每段字符串由以下组成:

  1. 指向图像的 URL
  2. 一个空格(可选)
  3. 一个像素密度描述符(一个正浮点数,后面紧跟 x 符号)

如果我们给图片应用一个 CSS 样式:

img {
  width: 320px;
}

它的宽度在屏幕上就是 320 像素(CSS 像素)。浏览器会计算出正在显示的显示器的分辨率,然后显示 srcset 引用的最适合的图片。如果是普通的分辨率,一个设备像素表示一个 CSS 像素,那就会加载 elva-fairy-320w.jpg,它的大小是 39KB,如果设备是高像素,用两个或者更多的设备像素表示一个 CSS 像素,那就会加载 elva-fairy-640w.jpg,它的大小是 93KB。

再说第二种情况,相同的图片内容,但依据设备显示的更大或者更小。使用示例代码如下:

<img
  srcset="elva-fairy-small.jpg 480w, elva-fairy-large.jpg 800w"
  src="elva-fairy-large.jpg"
  alt="Elva dressed as a fairy" />

srcset 的语法与刚才略有不同,它定义了浏览器可选择的图片设置以及每个图片的大小。分析它的语法,依然是由逗号分隔的一个或多个字符串组成,每段字符串由以下部分组成:

  1. 指向图像的 URL
  2. 一个空格
  3. 图片的固有宽度(以像素为单位)。注意,这里使用宽度描述符 w,而非 px。但一个 w 对应 1 个像素。图片的固有宽度是指它的真实大小。

此时我们就告诉了浏览器,这张图片有两种备选图片可以显示,一张是 elva-fairy-small.jpg,这张图片的宽度是 480px,一张是 elva-fairy-large.jpg,它的宽度是 800px。

那浏览器怎么知道用哪张图片呢?比如当前设备视口宽度是 640px,是选择 480px 的图片还是选择 800px 的图片呢?

为了帮助浏览器判断,你就需要写 sizes 属性。sizes 属性就是一组媒体查询条件,告诉浏览器,什么样的条件使用什么样的图片。一个使用示例如下:

<img
  srcset="elva-fairy-small.jpg 480w, elva-fairy-large.jpg 800w"
  sizes="(max-width: 600px) 480px,
         800px"
  src="elva-fairy-large.jpg"
  alt="Elva dressed as a fairy" />

sizes 也是由逗号分隔的一个或多个字符串组成,每段字符串由以下组成:

  1. 一个媒体条件,例子中为 (max-width:600px),它表示当视口的宽度小于等于 600px 时
  2. 一个空格
  3. 当媒体条件为真时,图片将填充的宽度,这个宽度可以是固定值,比如这个例子中的 480px,也可以是一个相对于视口的宽度(如 50vw),但不能是百分比。如果没有媒体条件,表示是默认生效。当浏览器成功匹配第一个媒体条件的时候,剩下所有的条件都会被忽略。所以顺序很重要。

有了这些属性后,浏览器会:

  1. 检查设备宽度
  2. 检查 sizes 列表中哪个媒体条件是第一个为真
  3. 查看给予该媒体查询的槽大小
  4. 加载 srcset 列表中引用的最接近所选的槽大小的图像

比如在这个例子中,如果浏览器的视口是 480px,那么 sizes 中的第一个条件 (max-width: 600px) 就为真,所以选择 480px 大小,因为 480px 与固有宽度 480w 最接近,所以加载 elva-fairy-small.jpg。通过这种方式就可以实现移动端加载小图片,从而加快移动端的加载速度。

讲完 img 元素的 srcsetsizes 属性,回到 <Image> 组件上,使用 Next.js,你并不需要设置 srcset,Next.js 会自动为你生成。设置 sizes 属性会影响生成的 srcset 的值。

如果你不设置组件的 sizes 属性,Next.js 会用 1x、2x 这种像素密度描述符,而如果你设置了 sizes 属性,Next.js 会用 640w、750w 这种固有宽度描述符。

设置前:

设置后:

11. quality

表示优化图片的质量,值为 1 到 100 之间的整数,100 表示最好的品质,也是最大的文件大小。默认是 75。

quality={75} // {number 1-100}

12. priority

表示图片加载优先级,布尔类型,默认值为 false。当值为 true 时表示高优先级并预加载。使用 priority 的图片会自动禁用懒加载。

priority={false} // {false} | {true}

使用该属性有两个建议:

  1. 在首屏可见的图片上使用
  2. 在 LCP 图片元素上使用,考虑到不同的视口宽度可能有不同的 LCP 图片,可以设置多个

运行 next dev 的时候,如果 LCP 元素是一个图片,但没有设置 priority 属性,控制台里会有警告:

使用示例代码:

// app/page.js
import Image from 'next/image'
import profilePic from '../public/me.png'
 
export default function Page() {
  return <Image src={profilePic} alt="Picture of the author" priority />
}

13. placeholder

表示加载图片时的占位符。可选值为 bluremptydata:image/... ,默认值是 empty

placeholder = 'empty' // "empty" | "blur" | "data:image/..."

当值为 empty 的时候,加载不会有占位符,只有空白区域。

当值为 data:image/...的时候,使用 Data URL 作为图片加载时的占位图片。Data URL,即前缀为 data: 协议的 URL,允许内容创建者向文档中嵌入小文件。比如 base64 图片就是 Data URL。

当值为blur的时候,blurDataURL属性的值会被用于作为占位符图片。如果图片是静态导入的,并且导入的图片为 .jpg.png.webp.avif ,blurDataURL 会自动生成,但动态图片除外。如果是动态图片,必须提供 blurDataURL属性。

这是一个默认的 blur 效果演示

14. blurDataURL

只有当你设置了 placeholder="blur",该属性值才会生效。

必须是 base64 编码的图片。图片会被放大并模糊,建议使用一个非常小的图片(10px 或者更小)。

可以借助 blurDataURI 实现这种色彩效果

可以借助 https://png-pixel.com/ 快速获得一个纯色图片的 Data URL。

15. style

设置图片的样式

// components/ProfileImage.js
const imageStyle = {
  borderRadius: '50%',
  border: '1px solid #fff',
}
 
export default function ProfileImage() {
  return <Image src="..." style={imageStyle} />
}

当你使用 style 修改了图片宽度的时候,注意设置 heightauto 以保持宽高比,否则图片会被扭曲。这是因为 Next.js 会自动为图片添加 widthheight 属性,通过 style 样式只修改宽度,加上原本添加的 height 属性,就会导致图片变形。

16. onLoadingComplete

'use client'
 
<Image onLoadingComplete={(img) => console.log(img.naturalWidth)} />

当图片加载完毕的时候,会执行该回调函数,同时占位符图片会被删除。回调函数调用的时候会传入一个参数,该参数是对底层 元素的引用。

注意:因为组件接收一个函数作为参数,需要使用客户端组件。

17. onLoad

'use client'
  
<Image onLoad={(e) => console.log(e.target.naturalWidth)} />

同样是图片加载完的时候执行,该回调函数可能会在占位符被删除以及图片被完全解码前执行。所以如果你想等到图片完全加载完毕,使用 onLoadingComplete。

注意:因为组件接收一个函数作为参数,需要使用客户端组件。

18. onError

'use client'
  
<Image onError={(e) => console.error(e.target.id)} />

图片加载失败时执行的回调函数。

注意:因为组件接收一个函数作为参数,需要使用客户端组件。

19. loading

loading = 'lazy' // {lazy} | {eager}

设置图片的加载行为,默认值为 lazy

当值为 lazy 的时候,图片会延迟加载,直到图片接近视口。

当值为 eager 的时候,图片会被立即加载。

使用 eager 通常会损害性能。Next.js 推荐使用 priority 属性代替。

20. unoptimized

取消优化。当值为 true 的时候,使用源图片,不会更改质量、大小、格式。默认值是 false。示例如下:

import Image from 'next/image'
 
const UnoptimizedImage = (props) => {
  return <Image {...props} unoptimized />
}

从 Next.js 12.3.0 起,可以通过 next.config.js 设置所有的图片取消优化:

// next.config.js
module.exports = {
  images: {
    unoptimized: true,
  },
}

21. 其他 props

其他传给 <Image /> 组件的属性都会传给底层的 img 元素。但以下属性除外:

  1. srcSet,Next.js 会自动生成,如果你想更改,使用配置项里的 deviceSizes,下一节会讲到。
  2. decoding,它的值总是 "async"

配置选项

除了通过 props,你也可以在 next.config.js 中配置图片组件。

1. remotePatterns

为保护应用远离恶意用户,当使用外部图片的时候需要配置 remotePatterns

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',
        pathname: '/account123/**',
      },
    ],
  },
}

这个示例的意思是,next/imagesrc 属性的值必须是以 https://example.com/account123/ 为开头。其他的协议、主机名、端口或者不匹配的路径都会返回 400 错误。

这是另外一个例子,在此例子中使用了通配符:

// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: '**.example.com',
      },
    ],
  },
}

这个示例的意思是,next/imagesrc 属性的值必须是以 https://xxx.example.comhttps://xxx.xxx.example.comhttps://xxx.xxx.xxx.example.com……开头。

pathnamehostname 都可以使用通配符,其中:

  1. * 表示匹配单个路由段或者子域
  2. ** 表示匹配任意数量的路由段或者子域。

注意 ** 语法在模式的中间是不起作用的。

2. domains

自 Next.js 14 起因为使用了更为严格的 remotePatterns 而废弃。仅当所有的内容都来自你所能控制的域的时候你再使用。

remotePatterns 类似,domains 配置项提供了一个用于外部图片的 hostname 列表:

// next.config.js
module.exports = {
  images: {
    domains: ['assets.acme.com'],
  },
}

但是注意 domains 不支持通配符,并且无法限制协议、端口或者路径名。所以更建议使用 remotePatterns

3. loaderFile

如果你不希望使用 Next.js 内置的图片优化 API,那你可以自己配置,使用 next.config.jsloaderFile 配置项:

// next.config.js
module.exports = {
  images: {
    loader: 'custom',
    loaderFile: './my/image/loader.js',
  },
}

loaderFile 必须指向相对于 Next.js 应用根目录的文件。该文件必须导出一个默认函数,该函数返回一个字符串。举个例子:

'use client'
 
export default function myImageLoader({ src, width, quality }) {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

这会应用到所有的 next/image 的实例,如果你要修改个别图片,使用 loader prop。

4. deviceSizes

如果你知道用户的设备宽度,那你可以使用 next.config.jsdeviceSizes来声明一系列的设备宽度断点。当 next/image 组件使用 sizes prop 的时候,这些宽度会被用来推断正确加载的图片。

如果没有配置,默认值是:

// next.config.js
module.exports = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  },
}

5. imageSizes

你可以使用 next.config.jsimageSize 属性声明一系列的图片宽度。

如果没有配置,默认值是:

// next.config.js
module.exports = {
  images: {
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}

imageSizedeviceSizes 会影响图片生成最终的 srcset 尺寸:

那么问题来了,都是用来生成最终的 srcset,直接用一个数组不就好了吗?为什么非要用两个数组?

你可以这样理解,deviceSizes 用来处理大图片,imageSizes 用来处理小图片,而且 imageSizes 只会在图片使用了 sizes prop 的时候生效,比如当你这样写:

import Image from 'next/image'
import profilePic from './image.png'
 
export default function Page() {
  return (
    <Image
      src={profilePic}
      sizes="(max-width: 600px) 160px,
      320px"
      alt="Picture of the author"
    />
  )
}

对应生成的 srcset 就包含了deviceSizes 和 imageSizes 的所有尺寸:

当你使用了 sizes prop 的时候,说明图片的宽度是小于全屏宽度的。imagesSizes 的中的所有值应该都小于 deviceSizes 中的最小值。

6. formats

Next.js 默认的图片优化 API 会自动通过请求中的 Accept 请求头检测浏览器支持的图片格式。

如果 Accept 匹配多个配置的格式,数组中的第一个会被首先使用。因此,数组的顺序很重要,如果没有匹配到,或者源图片为动图,图片优化 API 会自动回退到原本的图片格式。

如果没有配置,默认值是:

// next.config.js
module.exports = {
  images: {
    formats: ['image/webp'],
  },
}

你可以使用下面的配置开启 AVIF 格式支持:

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

7. minimumCacheTTL

图片会根据请求动态优化并存储在 <distDir>/cache/images目录。优化后的图像文件会被用于后续请求,直到缓存过期。当匹配到过期的文件时,过期图片会立刻失效,同时图片会在后台重新优化并使用新的失效日期储存在缓存中。

通过读取响应头中的 x-nextjs-cache标头确定图片的缓存状态:

  • MISS:路径不在缓存中
  • STALE :路径缓存了但是超出了重新验证时间,它会在后台被更新
  • HIT :路径在缓存中,且未超过重新验证时间

过期时间可以通过两种方式定义:

一种是通过 minimumCacheTTL配置项,一种通过 Cache-Control标头,具体而言,使用Cache-Control标头的 max-age 字段。如果 s-maxagemax-age都有,s-maxage优先。

两种方法如果同时设置,以较大者为准。

minimumCacheTTL 配置项用来设置缓存优化图片的过期时间,它以秒为单位。使用示例如下:

// next.config.js
module.exports = {
  images: {
    minimumCacheTTL: 60,
  },
}

如果你需要更改每张图片的缓存行为,你可以通过 headers 配置图片资源的 Cache-Control标头。

大部分时候,建议使用静态图片导入,它会自动对文件内容进行哈希处理,并使用 immutableCache-Control 标头。

目前还没有重新验证缓存的机制,所以最好将 minimumCacheTTL 的值设低一点、否则你可能需要手动修改 src prop 或者删除 <distDir>/cache/images以更新缓存。

8. disableStaticImages

如果图片静态导入功能跟其他插件发生冲突,你希望禁用此功能:

// next.config.js 
module.exports = {
  images: {
    disableStaticImages: true,
  },
}

9. dangerouslyAllowSVG

默认 loader 不会优化 SVG 图片。首先,SVG 是一种矢量格式,这意味着它可以无损地调整大小。其次,SVG 具有许多与 HTML/CSS 相同的功能,如果没有适当的内容安全策略,这些功能可能会导致漏洞。

如果你需要使用默认的图像优化 API 来提供 SVG 图像,设置 next.config.jsdangerouslyAllowSVG值:

// next.config.js
module.exports = {
  images: {
    dangerouslyAllowSVG: true,
    contentDispositionType: 'attachment',
    contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
  },
}

Next.js 强烈推荐设置 contentDispositionType 强制浏览器下载图片,以及 contentSecurityPolicy阻止执行图片中嵌入的脚本。

动画图像

默认 loader 将自动跳过动态图片的优化并按原样展示。

Next.js 会自动检测动态图片,支持 GIF、APNG 和 WebP 格式。对于特定的动态图片,如果你想显式声明跳过,使用 unoptimized 属性(这样就省得 Next.js 检测判断了)。

响应式图片

图片默认生成的 srcset 包括 1x2x 图片,这是为了支持不同的设备像素比。不过有的时候,你希望渲染响应式图片,自动适配视口,这个时候,你就需要设置 sizes 以及 style(或者 className)。下面这些方式都可以用来渲染响应式图片:

1. 使用静态导入的响应式图片

如果源图片不是动态的,你可以通过静态导入创建一个响应式图片:

// components/author.js
import Image from 'next/image'
import me from '../photos/me.jpg'
 
export default function Author() {
  return (
    <Image
      src={me}
      alt="Picture of the author"
      sizes="100vw"
      style={{
        width: '100%',
        height: 'auto',
      }}
    />
  )
}

2. 保持宽高比的响应式图片

如果源图片是动态或者远程 URL,你需要提供 widthheight 来设置正确的响应式图片宽高比。

// components/page.js
import Image from 'next/image'
 
export default function Page({ photoUrl }) {
  return (
    <Image
      src={photoUrl}
      alt="Picture of the author"
      sizes="100vw"
      style={{
        width: '100%',
        height: 'auto',
      }}
      width={500}
      height={300}
    />
  )
}

3. 使用 fill 属性的响应式图片

如果你不知道图片宽高比,那可以考虑使用 fill 属性,注意设置父元素为 postion:relative 当然不用这种方式。也可以使用 object-fit ,具体看你想要什么样的效果:

// app/page.js
import Image from 'next/image'
 
export default function Page({ photoUrl }) {
  return (
    <div style={{ position: 'relative', width: '500px', height: '300px' }}>
      <Image
        src={photoUrl}
        alt="Picture of the author"
        sizes="500px"
        fill
        style={{
          objectFit: 'contain',
        }}
      />
    </div>
  )
}

4. 搭配 grid 实现

import Image from 'next/image'
import mountains from '../public/mountains.jpg'
 
export default function Fill() {
  return (
    <div
      style={{
        display: 'grid',
        gridGap: '8px',
        gridTemplateColumns: 'repeat(auto-fit, minmax(400px, auto))',
      }}
    >
      <div style={{ position: 'relative', height: '400px' }}>
        <Image
          alt="Mountains"
          src={mountains}
          fill
          sizes="(min-width: 808px) 50vw, 100vw"
          style={{
            objectFit: 'cover', // cover, contain, none
          }}
        />
      </div>
      {/* And more images in the grid... */}
    </div>
  )
}

主题判断

如果你希望实现浅色和深色模式下显示不同的图片,你可以创建一个新组件包含两个 <Image> 组件,然后通过 CSS 媒体查询显示正确的那一个:

// omponents/theme-image.module.css
.imgDark {
  display: none;
}
 
@media (prefers-color-scheme: dark) {
  .imgLight {
    display: none;
  }
  .imgDark {
    display: unset;
  }
}
// components/theme-image.tsx
import styles from './theme-image.module.css'
import Image from 'next/image'
 
const ThemeImage = (props) => {
  const { srcLight, srcDark, ...rest } = props
 
  return (
    <>
      <Image {...rest} src={srcLight} className={styles.imgLight} />
      <Image {...rest} src={srcDark} className={styles.imgDark} />
    </>
  )
}

累计布局偏移

在使用 Next.js 图片组件的时候,你会发现,Next.js 要求必须有 width 和 height 属性,哪怕使用静态导入图片的方式,也只是不用自己手写这两个属性而已,Next.js 依然会为你自动添加 width 和 height,之所以这样做,就是为了防止发生布局偏移。所谓布局偏移,顾名思义,原本内容的位置突然发生偏移,多出现在加载的时候。导致布局偏移的原因有很多,图片没有尺寸是常见的一个原因,让我们看个演示视频:

10TEOBGBqZm1SEXE7KiC.gif

这就是没有设置图片尺寸导致的布局偏移,如果设置尺寸呢:

38UiHViz44OWqlKFe1VC.gif

你会发现图片在加载的时候,浏览器为图片预留了位置。

不要小瞧布局偏移,为此专门有累计布局偏移(Cumulative Layout Shift,简称 CLS)这个 Web 性能衡量指标。这可是 Google 三大核心网页指标之一。累计布局偏移会统计视口中可见内容的移动量以及移动的距离,综合算出一个得分。

next/image 的设计就是为了防止发生布局偏移,所以如果要调整图片大小,应该使用下面三种方式之一:

  1. 自动静态导入
  2. 显示声明 widthheight 属性
  3. 隐式声明,通过使用 fill 让图片填充父元素

参考链接

  1. https://www.youtube.com/watch?v=IU_qq_c_lKA&feature=youtu.be
  2. https://web.dev/articles/lcp?hl=zh-cn
  3. Media | 2022 | The Web Almanac by HTTP Archive
  4. How to use the Next.js Image Component Effectively
  5. https://web.dev/articles/optimize-cls#images-without-dimensions
  6. Optimizing: Images
  7. Components:
© Copyright 2025 JackyLove 的技术人生. Powered with by CreativeDesignsGuru