GoForum🌐 V2EX

Next.js 多语言路由性能优化:从 5.8s LCP 到极致体验

hazellin549 · 2026-02-20 23:07 · 0 次点赞 · 0 条回复

为什么你的英文版比中文版慢 3 倍?


01 一个诡异的性能问题

上周,我在检查网站的性能报告时,发现了一个诡异的现象:

语言版本 LCP CLS
中文 (默认) 2.3s ✅ 0.06 ✅
英语 /en/ 5.8s ❌ 0.68 ❌

同一套代码,同一个页面,为什么英文版比默认语言慢了整整 2.5 倍?

这篇文章记录我的排查过程和最终解决方案。


02 先搞清楚 LCP 是什么

LCP (Largest Contentful Paint) 是 Core Web Vitals 的核心指标之一。

简单说,它测量的是:页面主要内容出现的时间

Google 的标准是:

  • < 2.5s — 良好
  • ⚠️ 2.5s - 4s — 需要改进
  • > 4s — 较差

我的英文版 5.8s ,妥妥的「红牌警告」。


03 第一个嫌疑人:缓存策略

在 Next.js 的多语言路由中,默认语言和非默认语言的处理逻辑是不同的。

默认语言: example.com/product
非默认语言: example.com/en/product

我检查了 CDN 缓存命中率:

路由 缓存命中率
/product 89% ✅
/en/product 31% ❌

问题出现了:非默认语言的缓存命中率低得可怜。

原因分析

Next.js 默认生成的 Cache-Control 头对于动态路由是这样的:

Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate

而我的多语言路由 /[locale]/... 被识别为「动态路由」,导致 CDN 几乎不缓存。

解决方案

next.config.js 中显式配置缓存策略:

async headers() {
  return [
    {
      source: '/:locale(en|es|fr)/:path*',
      headers: [
        {
          key: 'Cache-Control',
          value: 'public, s-maxage=3600, stale-while-revalidate=86400',
        },
      ],
    },
  ];
}

效果:缓存命中率从 31% 提升到 85%


04 第二个嫌疑人:关键资源预加载

优化缓存后,LCP 从 5.8s 降到了 3.9s ,但还是不达标。

Lighthouse 报告给了一个提示:

⚠️ Preload key requests
Fonts and critical CSS are not being preloaded

我检查了页面的资源加载顺序:

1. HTML 文档
2. 等待解析...
3. 发现 CSS 引用
4. 下载 CSS
5. 发现字体引用
6. 下载字体
7. 渲染文字 ← LCP 发生在这里

问题是:字体要等到 CSS 加载完才开始下载,形成了瀑布流。

解决方案

<head> 中添加预加载标签:

<Head>
  {/* 预加载关键字体 */}
  <link
    rel="preload"
    href="/fonts/inter-variable.woff2"
    as="font"
    type="font/woff2"
    crossOrigin="anonymous"
  />
  
  {/* 预加载关键 CSS */}
  <link
    rel="preload"
    href="/_next/static/css/app.css"
    as="style"
  />
</Head>

效果:LCP 从 3.9s 降到 2.7s


05 第三个嫌疑人:图片加载

还差 0.2s 才能达到 2.5s 的「良好」标准。

Lighthouse 又给了提示:

⚠️ Largest Contentful Paint element
<img src="/hero-banner.png" ...>

原来 LCP 元素是首屏的大图。

问题

图片使用了 Next.js 的 <Image> 组件,默认是懒加载的:

<Image src="/hero-banner.png" ... />
// 默认 loading="lazy"

但对于首屏图片,懒加载反而拖慢了显示速度。

解决方案

对 LCP 元素禁用懒加载,并添加 priority 属性:

<Image
  src="/hero-banner.png"
  priority  // 关键!
  loading="eager"
  ...
/>

效果:LCP 从 2.7s 降到 2.2s


06 CLS 问题:页面为什么在「抖动」?

解决完 LCP ,再看 CLS 。

CLS (Cumulative Layout Shift) 测量的是:页面元素的意外位移

我的英文版 CLS 是 0.68 ,而标准是 < 0.1 。

打开页面慢动作回放,发现了两个问题:

问题 1:字体加载导致的「闪烁」(FOUT)

页面先用系统字体渲染,字体加载完后「闪」一下变成自定义字体。

由于两种字体的 metrics 不同,文字位置发生偏移。

解决方案:使用 font-display: optional

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2');
  font-display: optional; /* 如果字体没及时加载,就不换了 */
}

问题 2:图片没有指定尺寸

// ❌ 错误写法
<img src="/card.png" />

// ✅ 正确写法
<img src="/card.png" width={300} height={400} />

Next.js 的 <Image> 组件会自动处理这个问题,但我有几个地方用了原生 <img>

效果:CLS 从 0.68 降到 0.04


07 优化前后对比

指标 优化前 优化后 改善
LCP 5.8s ❌ 2.2s ✅ -62%
CLS 0.68 ❌ 0.04 ✅ -94%
缓存命中率 31% 85% +174%
Lighthouse 52 91 +75%

08 核心经验总结

多语言路由的性能优化,核心要点:

🎯 缓存策略

非默认语言路由容易被当作「动态页面」而跳过缓存。显式配置 Cache-Control

🎯 预加载关键资源

字体和关键 CSS 形成瀑布流是 LCP 的常见杀手。<link rel="preload"> 打破瀑布。

🎯 LCP 元素优先加载

首屏图片不要懒加载。priority 属性告诉 Next.js 这是关键资源。

🎯 防止布局偏移

  • 图片必须指定尺寸
  • 字体使用 font-display: optionalswap + 预加载
  • 避免动态插入内容到首屏

09 一个容易忽略的细节

最后分享一个容易忽略的点:

不同语言版本的字体 metrics 可能不同。

比如中文字体通常比英文字体更高,如果你用同一套 CSS ,line-height 可能导致不同语言的布局高度不一致。

我的做法是为每种语言定义 bodyfont-family 后备栈:

/* 英语/西班牙语 */
body[lang="en"], body[lang="es"] {
  font-family: 'Inter', system-ui, sans-serif;
}

/* 中文 */
body[lang="zh"] {
  font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}

10 写在最后

性能优化是一个「挤牙膏」的过程:

  • 第一轮优化(缓存):5.8s → 3.9s
  • 第二轮优化(预加载):3.9s → 2.7s
  • 第三轮优化(图片):2.7s → 2.2s

每一轮看起来改动不大,但累积下来效果惊人。

如果你的多语言网站也有性能问题,不妨按这个顺序排查:

  1. 缓存是否生效?
  2. 关键资源是否预加载?
  3. LCP 元素是否优先加载?
  4. 有没有布局偏移?

希望这篇文章对你有帮助!

0 条回复
添加回复
你还需要 登录 后发表回复

登录后可发帖和回复

登录 注册
主题信息
作者: hazellin549
发布: 2026-02-20
点赞: 0
回复: 0