前端 Core Web Vitals 優化:Lighthouse 從 50 分到 95+ 的實戰之路

性能优化

為什麼 Core Web Vitals 直接影響 SEO 和收入

2026年,Google 已將 Core Web Vitals 作為搜尋排名的核心訊號超過三年。INP(Interaction to Next Paint)替代 FID 成為新指標,三大指標——LCP、CLS、INP——直接決定了你的網站能否獲得流量和轉換。

指標表現 SEO 排名影響 跳出率 轉換率
優秀(全部通過) 排名提升 15-30% 低於 35% 高於 3.5%
需改進(部分通過) 排名無明顯變化 40-60% 1.5-3%
較差(全部未通過) 排名下降 20-40% 高於 70% 低於 1%

一項針對電商網站的研究表明:LCP 每減少 100ms,轉換率提升 0.7%;CLS 每降低 0.1,使用者停留時間增加 15%。效能不再是技術指標,而是商業指標。


LCP 優化策略

LCP(Largest Contentful Paint)衡量最大內容元素的渲染時間。2026年的及格線是 2.5 秒以內,優秀標準是 1.8 秒以內。

圖片優化

圖片通常是 LCP 元素,優化它效果最顯著:

<picture>
  <source
    srcset="hero-image.avif?w=800 800w, hero-image.avif?w=1200 1200w, hero-image.avif?w=1600 1600w"
    type="image/avif"
  />
  <source
    srcset="hero-image.webp?w=800 800w, hero-image.webp?w=1200 1200w, hero-image.webp?w=1600 1600w"
    type="image/webp"
  />
  <img
    src="hero-image.jpg?w=1200"
    srcset="hero-image.jpg?w=800 800w, hero-image.jpg?w=1200 1200w, hero-image.jpg?w=1600 1600w"
    sizes="(max-width: 768px) 100vw, 1200px"
    alt="Hero banner"
    width="1200"
    height="600"
    fetchpriority="high"
    decoding="async"
    loading="eager"
  />
</picture>

關鍵點:AVIF 優先於 WebP 優先於 JPEG;使用 fetchpriority="high" 提升 LCP 圖片優先級;始終宣告 widthheight 避免 CLS;LCP 圖片不要 lazy load。

字型優化

自訂字型載入是 LCP 延遲的常見原因:

<link
  rel="preload"
  href="/fonts/inter-var-subset.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>
<style>
  @font-face {
    font-family: 'Inter';
    src: url('/fonts/inter-var-subset.woff2') format('woff2');
    font-weight: 100 900;
    font-display: swap;
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC;
  }
</style>

font-display: swap 確保文字始終可見;unicode-range 子集化減少字型體積 60-80%;preload 提前發起字型請求。

CSS 優化

關鍵 CSS 內聯,非關鍵 CSS 非同步載入:

<style>
  /* Critical CSS - 內聯在 <head> 中 */
  .hero{display:flex;align-items:center;justify-content:center;min-height:60vh;background:linear-gradient(135deg,#667eea,#764ba2)}
  .hero h1{font-size:clamp(2rem,5vw,4rem);color:#fff;margin:0}
  .hero p{font-size:clamp(1rem,2vw,1.5rem);color:rgba(255,255,255,0.9)}
</style>
<link rel="preload" href="/styles/non-critical.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="/styles/non-critical.css" /></noscript>

伺服器端優化

CDN + Edge Caching + Streaming 組合:

// Next.js Streaming SSR 範例
export default async function Page() {
  const data = await fetch('https://api.example.com/hero', {
    next: { revalidate: 3600 }
  }).then(r => r.json());

  return (
    <main>
      <Suspense fallback={<HeroSkeleton />}>
        <HeroSection data={data} />
      </Suspense>
      <Suspense fallback={<ContentSkeleton />}>
        <DeferredContent />
      </Suspense>
    </main>
  );
}

Edge 快取讓 LCP 請求在 50ms 內回傳;Streaming 讓首屏不阻塞在慢資料上。


CLS 修復策略

CLS(Cumulative Layout Shift)衡量視覺穩定性。2026年及格線是 0.1 以內,優秀標準是 0.05 以內。

佈局穩定性技術

.card {
  contain: layout style paint;
  content-visibility: auto;
  contain-intrinsic-size: 0 320px;
}

.ad-slot {
  min-height: 250px;
  background: #f5f5f5;
}

.skeleton {
  min-height: 200px;
  animation: shimmer 1.5s infinite;
}

contain 屬性隔離佈局影響範圍;content-visibility: auto 跳過螢幕外內容渲染;min-height 為動態內容預留空間。

圖片尺寸宣告

<!-- 錯誤:缺少尺寸 -->
<img src="product.jpg" alt="Product" />

<!-- 正確:宣告尺寸 -->
<img
  src="product.jpg"
  alt="Product"
  width="400"
  height="300"
  style="width: 100%; height: auto;"
/>

<!-- 響應式寬高比 -->
<img
  src="product.jpg"
  alt="Product"
  style="aspect-ratio: 4/3; width: 100%; height: auto;"
/>

動態內容處理

function loadComments(postId) {
  const container = document.getElementById('comments');
  // 預留最小高度
  container.style.minHeight = '300px';

  fetch(`/api/comments/${postId}`)
    .then(r => r.json())
    .then(comments => {
      // 一次性更新,避免多次回流
      const fragment = document.createDocumentFragment();
      comments.forEach(comment => {
        const el = document.createElement('div');
        el.className = 'comment-item';
        el.textContent = comment.text;
        fragment.appendChild(el);
      });
      container.innerHTML = '';
      container.appendChild(fragment);
      container.style.minHeight = '';
    });
}

字型載入 CLS 防護

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var-subset.woff2') format('woff2');
  font-display: optional;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

/* 或使用 size-adjust 統一回退字型和自訂字型尺寸 */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107.06%;
  ascent-override: 90%;
  descent-override: 22%;
}

font-display: optional 徹底避免字型交換導致的 CLS;size-adjust 讓回退字型與自訂字型尺寸一致。


INP 優化策略

INP(Interaction to Next Paint)衡量互動回應速度。2026年及格線是 200ms 以內,優秀標準是 100ms 以內。

事件處理器優化

// 錯誤:同步長任務阻塞主執行緒
searchInput.addEventListener('input', (e) => {
  const results = heavyFilter(e.target.value, allData); // 可能耗時 500ms
  renderResults(results);
});

// 正確:防抖 + yield to main thread
searchInput.addEventListener('input', debounce(async (e) => {
  const value = e.target.value;

  // 使用 scheduler.yield 讓出主執行緒
  await scheduler.yield();

  const results = heavyFilter(value, allData);
  renderResults(results);
}, 150));

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

主執行緒管理

async function processLargeDataset(data) {
  const CHUNK_SIZE = 50;
  const results = [];

  for (let i = 0; i < data.length; i += CHUNK_SIZE) {
    const chunk = data.slice(i, i + CHUNK_SIZE);
    results.push(...processChunk(chunk));

    // 每 50 筆讓出主執行緒
    if (i % (CHUNK_SIZE * 10) === 0) {
      await new Promise(resolve => setTimeout(resolve, 0));
    }
  }

  return results;
}

Web Workers 處理重計算

// main.js
const worker = new Worker('/workers/search-worker.js');

worker.postMessage({ type: 'SEARCH', query: userInput, data: largeDataset });

worker.onmessage = (event) => {
  if (event.data.type === 'SEARCH_RESULT') {
    renderResults(event.data.results);
  }
};

// workers/search-worker.js
self.onmessage = (event) => {
  if (event.data.type === 'SEARCH') {
    const results = performHeavySearch(event.data.query, event.data.data);
    self.postMessage({ type: 'SEARCH_RESULT', results });
  }
};

function performHeavySearch(query, data) {
  return data.filter(item =>
    item.name.toLowerCase().includes(query.toLowerCase())
  );
}

requestAnimationFrame 與 Scheduler API

// 2026 年推薦:Scheduler API
async function handleScroll() {
  const pendingUpdates = collectScrollUpdates();

  // 讓出主執行緒,確保下一幀繪製
  await scheduler.yield();

  // 在合適的時機執行視覺更新
  requestAnimationFrame(() => {
    applyScrollUpdates(pendingUpdates);
  });
}

// 相容方案
function yieldToMain() {
  return new Promise(resolve => {
    if ('scheduler' in window && 'yield' in scheduler) {
      scheduler.yield().then(resolve);
    } else {
      setTimeout(resolve, 0);
    }
  });
}

測量與監控體系

Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci && npm run build
      - name: Lighthouse CI
        uses: treosh/lighthouse-ci-action@v12
        with:
          urls: |
            http://localhost:3000/
          uploadArtifacts: true
          budgetPath: ./lighthouse-budget.json
          configPath: ./lighthouserc.json
// lighthouse-budget.json
[
  {
    "path": "/*",
    "options": {
      "first-contentful-paint": ["warn", { "maxNumericValue": 1800 }],
      "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
      "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
      "interactive": ["warn", { "maxNumericValue": 200 }]
    }
  }
]

web-vitals 函式庫

import { onLCP, onCLS, onINP } from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    delta: metric.delta,
    id: metric.id,
    url: location.href,
    timestamp: Date.now()
  });

  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/vitals', body);
  } else {
    fetch('/api/vitals', { body, method: 'POST', keepalive: true });
  }
}

onLCP(sendToAnalytics);
onCLS(sendToAnalytics);
onINP(sendToAnalytics);

Real User Monitoring (RUM)

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      name: entry.name,
      duration: entry.duration,
      startTime: entry.startTime,
      entryType: entry.entryType
    });

    reportToAnalytics({
      metric: entry.name,
      value: entry.duration,
      path: location.pathname,
      connection: navigator.connection?.effectiveType,
      deviceMemory: navigator.deviceMemory
    });
  }
});

observer.observe({
  type: 'largest-contentful-paint',
  buffered: true
});
observer.observe({ type: 'layout-shift', buffered: true });
observer.observe({ type: 'event', buffered: true });

5 個常見陷阱及解決方案

陷阱 1:過度使用 lazy loading

<!-- 錯誤:LCP 圖片使用 lazy loading -->
<img src="hero.jpg" loading="lazy" alt="Hero" />

<!-- 正確:LCP 圖片使用 eager + fetchpriority -->
<img src="hero.jpg" loading="eager" fetchpriority="high" alt="Hero" />

LCP 元素絕不能 lazy load,否則會延遲 200-500ms。

陷阱 2:忽略字型 CLS

字型載入後文字尺寸變化導致佈局偏移。始終使用 size-adjustfont-display: optional

陷阱 3:第三方指令碼未優化

<!-- 使用 Partytown 將第三方指令碼移至 Web Worker -->
<script type="text/partytown" src="https://analytics.example.com/script.js"></script>
<script>
  partytown = {
    forward: ['dataLayer.push', 'gtag']
  };
</script>

陷阱 4:未處理動態內容的佈局偏移

廣告、推薦列表等非同步內容必須預留 min-height,否則每次載入都會產生 CLS。

陷阱 5:在主執行緒執行重計算

搜尋、排序、過濾等操作超過 50ms 就應移至 Web Worker,否則 INP 必然超標。


10 個錯誤排查清單

# 錯誤現象 可能原因 排查方法
1 LCP > 4s LCP 圖片未優化 DevTools Performance 面板檢視 LCP 元素,檢查圖片格式和尺寸
2 LCP 圖片載入慢 缺少 CDN 或快取策略 檢查 Response Headers 中 cache-controlcf-cache-status
3 CLS > 0.25 圖片缺少 width/height Lighthouse CLS 審計項檢視具體偏移元素
4 CLS 偏移來自字型 font-display 設定不當 檢查 @font-facefont-display
5 INP > 500ms 事件處理器中有長任務 DevTools Long Animation Frames 面板定位
6 INP 偶發超標 第三方指令碼阻塞 Performance 面板按 URL 過濾,識別第三方指令碼
7 FCP 正常但 LCP 慢 關鍵資源載入順序 Coverage 面板檢查未使用 CSS/JS,Network 面板檢查瀑布圖
8 行動端 INP 遠差於桌面 觸控事件處理不當 檢查 touchstart/touchend 事件是否有 passive: true
9 頁面捲動卡頓 scroll 事件處理過重 檢查是否使用 requestAnimationFrame 節流
10 優化後 Lighthouse 分數不穩定 測試環境不一致 使用 Lighthouse CI 確保一致的測試條件

真實案例:電商首頁從 50 分到 95+

某電商平臺首頁最佳化前後的 Core Web Vitals 資料:

指標 最佳化前 最佳化後 改善幅度
Lighthouse 總分 50 96 +92%
LCP 5.2s 1.4s -73%
FID 280ms 45ms -84%
CLS 0.35 0.03 -91%
INP 450ms 85ms -81%
TTFB 1.8s 0.2s -89%
首屏 JS 體積 850KB 180KB -79%
首屏 CSS 體積 320KB 28KB -91%

最佳化步驟:

  1. 圖片遷移 AVIF + CDN:LCP 從 5.2s 降至 3.1s
  2. 關鍵 CSS 內聯 + 非關鍵 CSS 非同步:LCP 降至 2.3s
  3. 字型子集化 + font-display: optional:CLS 從 0.35 降至 0.12
  4. 圖片尺寸宣告 + min-height 預留:CLS 降至 0.03
  5. 搜尋邏輯遷移 Web Worker:INP 從 450ms 降至 120ms
  6. scheduler.yield + 防抖最佳化:INP 降至 85ms
  7. Edge Caching + Streaming SSR:TTFB 從 1.8s 降至 0.2s,LCP 最終 1.4s

推薦工具

最佳化過程中,以下線上工具可以幫助你高效完成工作:


總結:Core Web Vitals 最佳化不是一次性工作,而是持續的過程。從 LCP 的圖片和字型最佳化,到 CLS 的佈局穩定性保障,再到 INP 的主執行緒管理,每個指標都需要系統性的策略。記住:效能最佳化的目標不是追求滿分,而是為使用者提供流暢的體驗。當你的 Lighthouse 從 50 提升到 95+,帶來的不僅是更好的 SEO 排名,更是實實在在的轉換率提升。

本站提供瀏覽器本地工具,免註冊即可試用 →

#Core Web Vitals#LCP优化#CLS修复#INP优化#性能指标#2026