Next.js

图片优化

深入理解 Next.js 图片优化机制,包括 Image 组件、本地/远程图片、尺寸优化、占位符、样式和配置

概述

Next.js <Image> 组件扩展了 HTML <img> 元素,提供以下功能:

  • 尺寸优化:自动为每个设备提供正确尺寸的图片,使用 WebP 等现代图片格式
  • 视觉稳定性:在图片加载时自动防止布局偏移
  • 更快的页面加载:使用原生浏览器懒加载,仅在图片进入视口时加载,可选模糊占位符
  • 资源灵活性:按需调整图片大小,即使是存储在远程服务器上的图片

基本使用

next/image 导入并在组件中渲染:

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

本地图片

静态文件(如图片和字体)可以存储在根目录下的 public 文件夹中。public 内的文件可以从基础 URL(/)开始引用。

使用本地图片

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

静态导入

import Image from 'next/image'
import profilePic from './profile.png'

export default function Page() {
  return (
    <Image
      src={profilePic}
      alt="Picture of the author"
      // width 和 height 会自动从导入的图片推断
    />
  )
}

使用静态导入时,Next.js 会自动确定图片的 widthheight,用于防止图片加载时的布局偏移。

远程图片

要使用远程图片,src 属性应该是一个 URL 字符串。

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 在构建过程中无法访问远程文件,你需要手动提供 widthheight 和可选的 blurDataURL 属性。

要安全地允许来自远程服务器的图片,需要在 next.config.js 中定义支持的 URL 模式列表:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 's3.amazonaws.com',
        port: '',
        pathname: '/my-bucket/**',
        search: '',
      },
    ],
  },
}

export default config

使用多个远程模式

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
      },
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
      },
    ],
  },
}

export default config

必需属性

src

图片的源,可以是以下之一:

  • 内部路径字符串:/profile.png
  • 绝对外部 URL:https://example.com/profile.png(必须配置 remotePatterns
  • 静态导入:import profile from './profile.png'

alt

alt 属性用于为屏幕阅读器和搜索引擎描述图片。如果图片被禁用或加载时发生错误,它也是后备文本。

<Image
  src="/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
/>

width 和 height

widthheight 属性用于推断图片的正确宽高比并避免图片加载时的布局偏移。

<Image
  src="/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
/>

对于静态导入的图片,这些值会自动推断。对于远程图片,必须手动提供。

可选属性

fill

布尔值,使图片填充父元素的大小。父元素必须设置 position: relativeposition: fixedposition: absolute

import Image from 'next/image'

export default function Page() {
  return (
    <div style={{ position: 'relative', width: '300px', height: '500px' }}>
      <Image
        src="/profile.png"
        alt="Picture of the author"
        fill
        style={{ objectFit: 'cover' }}
      />
    </div>
  )
}

sizes

定义图片在不同断点的大小,用于响应式图片。

<Image
  src="/profile.png"
  alt="Picture of the author"
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>

quality

优化图片的质量,介于 1 和 100 之间的整数,默认为 75。

<Image
  src="/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
  quality={90}
/>

priority

布尔值,当为 true 时,图片将被视为高优先级并预加载。对于 LCP(Largest Contentful Paint)图片应该使用此属性。

<Image
  src="/hero.png"
  alt="Hero image"
  width={1200}
  height={600}
  priority
/>

placeholder

图片加载时使用的占位符。可能的值:

  • blur:使用 blurDataURL 作为占位符
  • empty(默认):图片加载时没有占位符
<Image
  src="/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

blurDataURL

用作占位符图片的 Data URL。仅在与 placeholder="blur" 结合使用时生效。

对于静态导入的图片,会自动生成。对于远程图片,必须手动提供。

<Image
  src="https://example.com/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAAIAAoDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWEREiMxUf/EABUBAQEAAAAAAAAAAAAAAAAAAAMF/8QAGhEAAgIDAAAAAAAAAAAAAAAAAAECEgMRkf/aAAwDAQACEQMRAD8AltJagyeH0AthI5xdrLcNM91BF5pX2HaH9bcfaSXWGaRmknyJckliyjqTzSlT54b6bk+h0R//2Q=="
/>

loading

图片的加载行为。可能的值:

  • lazy(默认):延迟加载图片,直到它接近视口
  • eager:立即加载图片
<Image
  src="/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
  loading="eager"
/>

unoptimized

布尔值,当为 true 时,源图片将按原样提供,不改变质量、大小或格式。默认为 false。

<Image
  src="/profile.png"
  alt="Picture of the author"
  width={500}
  height={500}
  unoptimized
/>

样式

使用 className

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
      className="rounded-lg shadow-lg"
    />
  )
}

使用 style

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
      style={{ borderRadius: '8px', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)' }}
    />
  )
}

使用 CSS Modules

.image {
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
import Image from 'next/image'
import styles from './page.module.css'

export default function Page() {
  return (
    <Image
      src="/profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
      className={styles.image}
    />
  )
}

响应式图片

使用 fill 和 sizes

import Image from 'next/image'

export default function Page() {
  return (
    <div className="container">
      <Image
        src="/hero.png"
        alt="Hero image"
        fill
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        style={{ objectFit: 'cover' }}
      />
    </div>
  )
}
.container {
  position: relative;
  width: 100%;
  height: 400px;
}

响应式宽度和高度

import Image from 'next/image'

export default function Page() {
  return (
    <div className="image-container">
      <Image
        src="/profile.png"
        alt="Picture of the author"
        width={500}
        height={500}
        sizes="100vw"
        style={{
          width: '100%',
          height: 'auto',
        }}
      />
    </div>
  )
}

配置选项

remotePatterns

配置允许的远程图片 URL 模式:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',
        pathname: '/images/**',
        search: '',
      },
    ],
  },
}

export default config

formats

配置图片优化 API 支持的图片格式:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

export default config

deviceSizes

定义设备宽度断点:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
  },
}

export default config

imageSizes

定义图片宽度断点:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}

export default config

minimumCacheTTL

配置优化图片的最小缓存 TTL(以秒为单位):

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    minimumCacheTTL: 60,
  },
}

export default config

dangerouslyAllowSVG

允许 SVG 图片优化:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    dangerouslyAllowSVG: true,
    contentDispositionType: 'attachment',
    contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;",
  },
}

export default config

自定义加载器

配置全局加载器

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    loader: 'custom',
    loaderFile: './my-loader.ts',
  },
}

export default config

创建自定义加载器

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

使用组件级加载器

import Image from 'next/image'

const myLoader = ({ src, width, quality }: { 
  src: string
  width: number
  quality?: number 
}) => {
  return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}

export default function Page() {
  return (
    <Image
      loader={myLoader}
      src="profile.png"
      alt="Picture of the author"
      width={500}
      height={500}
    />
  )
}

高级用法

背景图片

import Image from 'next/image'

export default function Page() {
  return (
    <div style={{ position: 'relative', width: '100%', height: '400px' }}>
      <Image
        src="/background.jpg"
        alt="Background"
        fill
        style={{ objectFit: 'cover', zIndex: -1 }}
      />
      <div style={{ position: 'relative', zIndex: 1, padding: '20px' }}>
        <h1>Content over background</h1>
      </div>
    </div>
  )
}

图片画廊

import Image from 'next/image'

const images = [
  { src: '/gallery/1.jpg', alt: 'Image 1' },
  { src: '/gallery/2.jpg', alt: 'Image 2' },
  { src: '/gallery/3.jpg', alt: 'Image 3' },
]

export default function Gallery() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {images.map((image, index) => (
        <div key={index} className="relative aspect-square">
          <Image
            src={image.src}
            alt={image.alt}
            fill
            sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
            style={{ objectFit: 'cover' }}
          />
        </div>
      ))}
    </div>
  )
}

带占位符的图片

import Image from 'next/image'
import profilePic from './profile.jpg'

export default function Page() {
  return (
    <Image
      src={profilePic}
      alt="Picture of the author"
      placeholder="blur"
      // blurDataURL 会自动从静态导入生成
    />
  )
}

动态模糊占位符

import Image from 'next/image'

async function getBase64(imageUrl: string) {
  const response = await fetch(imageUrl)
  const buffer = await response.arrayBuffer()
  const base64 = Buffer.from(buffer).toString('base64')
  return `data:image/jpeg;base64,${base64}`
}

export default async function Page() {
  const blurDataURL = await getBase64('https://example.com/profile.jpg')

  return (
    <Image
      src="https://example.com/profile.jpg"
      alt="Picture of the author"
      width={500}
      height={500}
      placeholder="blur"
      blurDataURL={blurDataURL}
    />
  )
}

性能优化

优先加载关键图片

对于 LCP(Largest Contentful Paint)图片使用 priority

import Image from 'next/image'

export default function Page() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority
    />
  )
}

懒加载非关键图片

默认情况下,图片会懒加载。对于首屏以下的图片,保持默认行为:

import Image from 'next/image'

export default function Page() {
  return (
    <div>
      <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
      
      {/* 这些图片会懒加载 */}
      <Image src="/image1.jpg" alt="Image 1" width={500} height={500} />
      <Image src="/image2.jpg" alt="Image 2" width={500} height={500} />
    </div>
  )
}

使用正确的图片格式

Next.js 会自动选择最佳格式(WebP、AVIF)。确保配置支持现代格式:

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
  },
}

export default config

最佳实践

1. 始终提供 alt 文本

// 好的做法
<Image src="/profile.png" alt="John Doe's profile picture" width={500} height={500} />

// 不好的做法
<Image src="/profile.png" alt="" width={500} height={500} />

2. 为 LCP 图片使用 priority

export default function Page() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority // 对于首屏最大的图片
    />
  )
}

3. 使用适当的尺寸

// 好的做法 - 提供实际显示尺寸
<Image src="/profile.png" alt="Profile" width={200} height={200} />

// 不好的做法 - 加载过大的图片
<Image src="/profile.png" alt="Profile" width={2000} height={2000} />

4. 使用 fill 进行响应式布局

<div style={{ position: 'relative', width: '100%', height: '400px' }}>
  <Image
    src="/hero.jpg"
    alt="Hero"
    fill
    sizes="100vw"
    style={{ objectFit: 'cover' }}
  />
</div>

5. 配置远程模式

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
    ],
  },
}

export default config

6. 使用占位符改善 UX

<Image
  src="/profile.png"
  alt="Profile"
  width={500}
  height={500}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..."
/>

常见问题

布局偏移

始终提供 widthheight 或使用 fill 属性:

// 方案 1:提供尺寸
<Image src="/profile.png" alt="Profile" width={500} height={500} />

// 方案 2:使用 fill
<div style={{ position: 'relative', width: '500px', height: '500px' }}>
  <Image src="/profile.png" alt="Profile" fill />
</div>

远程图片不显示

确保在 next.config.ts 中配置了 remotePatterns

import type { NextConfig } from 'next'

const config: NextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
      },
    ],
  },
}

export default config

图片质量低

调整 quality 属性:

<Image
  src="/profile.png"
  alt="Profile"
  width={500}
  height={500}
  quality={90} // 默认是 75
/>

总结

Next.js Image 组件提供了强大的图片优化功能:

  1. 自动优化:自动选择最佳格式(WebP、AVIF)和尺寸
  2. 懒加载:默认懒加载,提高性能
  3. 占位符:支持模糊占位符,改善用户体验
  4. 响应式:使用 fillsizes 实现响应式图片
  5. 远程图片:支持远程图片,需配置 remotePatterns
  6. 性能优化:使用 priority 预加载关键图片
  7. 灵活配置:支持自定义加载器和多种配置选项

通过合理使用 Image 组件和配置选项,可以显著提升应用的性能和用户体验。

在 GitHub 上编辑

上次更新于