跳到主要内容

Next.js 图片、字体与第三方脚本

这块经常被拆成三个零散话题来看:

  • next/image
  • next/font
  • next/script

但放到一起看更容易理解。它们其实都在做同一类事:

  • 优化首屏资源加载
  • 减少布局抖动
  • 控制第三方资源对性能的影响
  • 让资源加载策略更明确

next/image 解决什么问题

官方的 <Image /> 不是普通 <img> 的简单封装,它主要解决:

  • 自动尺寸优化
  • 现代格式输出
  • 原生懒加载
  • 避免布局偏移
  • 远程图片安全白名单控制
import Image from 'next/image';

export default function Avatar() {
return (
<Image
src="/profile.png"
alt="Profile"
width={500}
height={500}
/>
);
}

为什么它比 <img> 更值得默认用

最直接的原因有三个:

  • 宽高信息明确,能避免 CLS
  • 浏览器不会一次性把所有图片都抢先拉下来
  • 可以根据设备和视口输出更合适的资源尺寸

本地图片和远程图片的区别

本地图片

放在项目内,Next 更容易推断资源。

远程图片

要显式允许来源。

import type { NextConfig } from 'next';

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

export default nextConfig;

官方文档现在更推荐 remotePatterns,而不是旧式 domains。新的方向更细,也更安全。

<Image /> 最常见的坑

1. 没有宽高

如果不是 fill 模式,就应该提供 widthheight

2. 远程图片白名单太宽

配置越泛,越容易引入滥用和安全边界问题。官方也强调要尽量写得具体。

3. 把所有首屏图都设成高优先级

较新的版本里,priority 已经走到废弃路线,新的方向是 preload。但本质没变:

  • 只有真正关键的首屏图才值得提前加载
  • 不要把整页所有图都当成关键资源

4. 还在使用 next/legacy/image

这通常说明项目还带着 13 之前的历史包袱,应该逐步迁到现在的 next/image

next/font 解决什么问题

字体是性能里很容易被低估的一项。

官方的 next/font 主要做两件事:

  • 自动优化字体加载
  • 自动自托管字体文件,避免浏览器直接请求 Google Fonts
import { Geist } from 'next/font/google';

const geist = Geist({
subsets: ['latin'],
display: 'swap',
});

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={geist.className}>
<body>{children}</body>
</html>
);
}

为什么 next/font 很值得默认采用

  • 减少外部网络请求
  • 隐私更好
  • 更容易控制 display 策略
  • 更容易避免字体导致的布局跳动

本地字体怎么接

import localFont from 'next/font/local';

const brandFont = localFont({
src: './fonts/Brand-Regular.woff2',
display: 'swap',
variable: '--font-brand',
});

这在品牌项目、中文字体、自定义字库场景里很常见。

字体最常见的几个判断

全局统一字体

通常放根布局。

某个专题或品牌块单独字体

可以局部定义,避免把全站都绑进去。

多字体系统

尽量明确层级和变量,不要让字体定义到处散落。

next/script 解决什么问题

第三方脚本经常是性能问题大户。next/script 的价值在于:

  • 控制脚本什么时候加载
  • 控制加载范围
  • 避免在路由切换时重复插入同一脚本
import Script from 'next/script';

export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>{children}</body>
<Script src="https://example.com/analytics.js" />
</html>
);
}

Script 的四种策略

beforeInteractive

在任何 Next 代码和页面水合之前加载。

适合:

  • 极早期必须可用的脚本
  • 很少数必须抢在应用前执行的能力

afterInteractive

默认策略。页面发生一部分 hydration 后尽早加载。

适合:

  • 分析脚本
  • 常规第三方 SDK

lazyOnload

在浏览器空闲时加载。

适合:

  • 非关键统计
  • 不影响主流程的增强脚本

worker

实验态,把脚本放进 web worker。

但官方文档也明确提醒:

  • 这还是实验能力
  • 在 App Router 里还不能稳定使用

所以现在不适合作为默认方案。

脚本应该挂在哪

根布局

适合全站都要用的脚本。

某个子布局

适合某个业务区块才会用的脚本。

某个页面

适合范围非常明确的第三方能力。

官方也明确建议:尽量把第三方脚本限制在必要页面或布局,而不是全站一股脑注入。

最常见的第三方脚本问题

1. 脚本放太早

首屏被第三方拖慢。

2. 脚本挂太高

明明只有一个页面用,却放进全站布局。

3. 直接手写 <script>

能跑,但失去了 Next 对策略和复用的控制。

4. 图库、播放器、广告脚本直接上 SSR

这类组件很多天然偏浏览器环境,必要时要配合客户端组件、动态导入甚至 ssr: false 来处理。

一套比较稳的资源加载思路

  1. 图片默认先用 next/image
  2. 字体默认先用 next/font
  3. 第三方脚本默认先用 next/script
  4. 只有确认边界特殊时,再退回更底层写法

什么时候该保守一点

图片

  • 首屏关键图数量别太多
  • 远程来源范围别放太宽

字体

  • 不要一次挂太多字重和子集
  • 中文字体尤其要控制体积

脚本

  • 先判断是不是真的必须全站加载
  • 先判断是不是必须在首屏前执行

推荐继续往下看

  1. Metadata、SEO、sitemap 与 robots
  2. 部署、自托管与 standalone
  3. 水合(Hydration)

参考资料