V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hazellin549
V2EX  ›  前端开发

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

  •  
  •   hazellin549 · 5 天前 · 598 次点击

    为什么你的英文版比中文版慢 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. 有没有布局偏移?

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

    1 条回复    2026-02-21 12:52:30 +08:00
    seven777
        1
    seven777  
       4 天前 via iPhone
    Next.js v16 latest ?
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   4055 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 00:14 · PVG 08:14 · LAX 16:14 · JFK 19:14
    ♥ Do have faith in what you're doing.