Article

図解でわかるlighthouse パフォーマンス|仕組み・活用・注意点

高田晃太郎
図解でわかるlighthouse パフォーマンス|仕組み・活用・注意点

【書き出し(300-500文字)】

2024年のChrome UX Reportでは、モバイルでCore Web Vitalsに合格するオリジンは約4割にとどまり、INP導入以降は合格率が低下した。¹² 検索流入・CVR・広告品質スコアに直結する「体感速度」は依然として競争優位の源泉だが、実プロダクトで安定して改善を積み重ねるには、実験→計測→回帰防止の閉ループが要る。Lighthouseはこの循環の中心に位置する検証ツールだが、スコアの意味・測定条件・CIへの組み込みを誤ると投資対効果が希薄になる。本稿は、Lighthouseの仕組みを分解し、現場導入で成果を出す手順・コード・ベンチマーク・リスクまでを一気通貫で提示する。検索のページエクスペリエンス評価やランキングの一要素としての位置づけ³、広告のランディングページ評価との関係⁴も踏まえ、実務での意思決定精度を高める。

前提と技術仕様の整理

まず、Lighthouse(v11+想定)の測定前提を正しく固定する。ここが曖昧だと比較不能になり、議論が空転する。

  • 前提条件:
    • 計測対象: SPA/MPAいずれも可(SSR/SSG含む)
    • 目的: パフォーマンスカテゴリの安定運用(CIゲート/回帰検知)
    • 環境: Node.js 18+、Chromium/Chrome 安定版、CIはGitHub Actions or GitLab CI
    • 計測モード: Mobile(Simulated throttling)⁵

技術仕様(デフォルト測定条件):

項目補足
フォームファクタMobileMoto G4エミュレーション相当⁵
NetworkRTT 150ms / Down 1.6Mbps / Up 0.75MbpsSimulated Throttling(Lantern)⁵
CPU Slowdown4x単一スレッドに対する遅延係数⁵
ストレージクリーン(キャッシュ・Service Worker無効)毎回初回訪問相当
計測回数1回(推奨は3-5回の中央値)変動対策
スコア重みFCP 10, SI 10, LCP 25, TBT 30, CLS 15, INP 10合計100(v11スコアリング)⁵

主要指標の目標レンジ(Core Web Vitals準拠):

指標目標(良好)重点改善の観点
LCP≤ 2.5s⁶画像最適化、初期HTML/critical CSS、プリロード
INP≤ 200ms²JS実行時間短縮、優先度制御、input応答
CLS≤ 0.1⁷サイズ指定、フォント/広告のレイアウト保護
TBT≤ 200ms(目安)⁵長タスク分割、コード分割、Web Worker
FCP≤ 1.8s(目安)⁵初期描画経路短縮、サーバ応答
SI相対指標レンダリングの段階的可視化

仕組みを正しく理解する(スコアの作られ方)

スコア計算と変動要因

Lighthouseは、トレース(Performance trace)とネットワークログを収集し、Lanternモデルで「シミュレーション」した指標を算出する。このため、DevToolsの実測スロットリングとは一致しない。スコアは各指標のLog-normal曲線に当てはめた正規化値の加重平均で計算される。⁵ 変動要因は主に以下に集約される。

  1. サーバおよびCDNの瞬間負荷(TTFBのブレ)
  2. JSバンドルのキャッシュ無効化による初回ダウンロード
  3. 第三者スクリプトの応答遅延
  4. クライアントマシンのリソース競合(CIワーカーのノイズ)

運用では、3~5回測定の中央値を採用し、ベースラインを更新するのが再現性の観点で最適だ。

ラボデータとフィールドデータ

  • ラボ(Lighthouse/PSIのラボ指標): コード変更の影響を迅速に検証できる。A/BテストやPR単位の回帰検知に向く。
  • フィールド(CrUX/Real User Monitoring): 実ユーザ環境の分布。プロダクションKPI。SLOの妥当性確認に用いる。⁸

両者は補完関係であり、ラボで改善仮説を検証し、フィールドで成果を確定させる。⁸

活用:実装と自動化(完全コードつき)

以下は、実務で機能する最小セットから拡張までを段階的に示す。

実装手順(推奨):

  1. 測定条件の固定(config + flags)
  2. プログラマティック実行(Node API)
  3. 認証付きルートの監視(Puppeteer統合)
  4. CIゲート(PRでスコア/指標しきい値チェック)
  5. 通知・可視化(Slack/BI/Datadog)
  6. ベースライン更新・パフォーマンスバジェット運用

コード例1:Node APIでLighthouseを実行(JSON保存)

import fs from 'node:fs';
import path from 'node:path';
import lighthouse from 'lighthouse';
import chromeLauncher from 'chrome-launcher';

async function runLighthouse(url) {
  const chrome = await chromeLauncher.launch({
    chromeFlags: ['--headless', '--no-sandbox', '--disable-gpu']
  });
  const options = {
    port: chrome.port,
    output: 'json',
    logLevel: 'info',
    throttlingMethod: 'simulate',
    formFactor: 'mobile',
    screenEmulation: { mobile: true }
  };
  const config = { extends: 'lighthouse:default', settings: { onlyCategories: ['performance'] } };
  try {
    const runnerResult = await lighthouse(url, options, config);
    const reportJson = runnerResult.report;
    const outputPath = path.resolve(process.cwd(), 'lh-report.json');
    fs.writeFileSync(outputPath, reportJson);
    const score = runnerResult.lhr.categories.performance.score;
    console.log('Performance score:', Math.round(score * 100));
  } catch (e) {
    console.error('Lighthouse run failed', e);
    process.exitCode = 1;
  } finally {
    await chrome.kill();
  }
}

runLighthouse(process.argv[2] || 'https://example.com');

ポイント:

  • throttlingMethodはsimulateを明示。DevToolsのdevtools設定と混同しない。⁵
  • 出力JSONはCIアーティファクトとして保存。

コード例2:認証が必要なダッシュボードを計測(Puppeteer連携)

import puppeteer from 'puppeteer';
import lighthouse from 'lighthouse';
import fs from 'node:fs';

async function auditAuthenticated(url, credentials) {
  const browser = await puppeteer.launch({
    headless: true,
    args: ['--remote-debugging-port=9222', '--no-sandbox']
  });
  try {
    const page = await browser.newPage();
    await page.goto(credentials.loginUrl, { waitUntil: 'networkidle0' });
    await page.type('#email', credentials.email);
    await page.type('#password', credentials.password);
    await Promise.all([
      page.click('button[type=submit]'),
      page.waitForNavigation({ waitUntil: 'networkidle0' })
    ]);

    const options = { port: 9222, output: 'json', logLevel: 'error' };
    const { lhr, report } = await lighthouse(url, options);
    fs.writeFileSync('lh-auth.json', report);
    console.log('LCP(ms):', Math.round(lhr.audits['largest-contentful-paint'].numericValue));
  } catch (err) {
    console.error('Audit failed', err);
    process.exitCode = 1;
  } finally {
    await browser.close();
  }
}

auditAuthenticated('https://app.example.com/dashboard', {
  loginUrl: 'https://app.example.com/login',
  email: process.env.EMAIL,
  password: process.env.PASSWORD,
});

ポイント:

  • 認証状態の再現で「本番に近い」ラボ計測が可能。
  • ログイン後リダイレクト完了まで待機し、測定対象URLを走査。

コード例3:PageSpeed Insights APIでLighthouse JSONを取得(Python)

import os
import time
import json
import requests

API = "https://www.googleapis.com/pagespeedonline/v5/runPagespeed"

def fetch_lighthouse(url: str, strategy: str = "mobile") -> dict:
    params = {
        "url": url,
        "strategy": strategy,
        "category": "PERFORMANCE",
        "locale": "ja",
        "key": os.environ.get("PSI_API_KEY", "")
    }
    try:
        res = requests.get(API, params=params, timeout=120)
        res.raise_for_status()
        data = res.json()
        return data.get("lighthouseResult", {})
    except requests.RequestException as e:
        raise SystemExit(f"PSI request failed: {e}") from e

if __name__ == "__main__":
    start = time.time()
    lhr = fetch_lighthouse("https://example.com")
    score = lhr["categories"]["performance"]["score"]
    lcp = lhr["audits"]["largest-contentful-paint"]["numericValue"]
    print(json.dumps({"score": score, "lcp": lcp, "elapsed_ms": int((time.time()-start)*1000)}))

ポイント:

  • PSIはGoogle側インフラで実行されるため、CIノイズの影響を受けにくい。⁹
  • APIクォータ管理とキャッシュ(ETag)を設計する。

コード例4:LHRをSlackへ通知(TypeScript)

import fs from 'node:fs';
import axios from 'axios';

type LHR = {
  categories: { performance: { score: number } };
  audits: Record<string, { numericValue?: number; displayValue?: string }>;
};

async function notifySlack(file = 'lh-report.json') {
  const lhr: LHR = JSON.parse(fs.readFileSync(file, 'utf-8'));
  const score = Math.round(lhr.categories.performance.score * 100);
  const lcp = Math.round(lhr.audits['largest-contentful-paint'].numericValue || 0);
  const inp = Math.round(lhr.audits['interaction-to-next-paint'].numericValue || 0);
  const tbt = Math.round(lhr.audits['total-blocking-time'].numericValue || 0);

  const text = `Lighthouse: ${score}\nLCP: ${lcp} ms | INP: ${inp} ms | TBT: ${tbt} ms`;

  try {
    await axios.post(process.env.SLACK_WEBHOOK_URL as string, { text });
    console.log('Sent to Slack');
  } catch (e) {
    console.error('Slack notification failed', e);
    process.exit(1);
  }
}

notifySlack();

ポイント:

  • チームにしきい値逸脱を即時通知。回帰を最短で検知。

コード例5:配信最適化(Expressでimmutableキャッシュ)

import express from 'express';
import compression from 'compression';
import path from 'node:path';

const app = express();
app.use(compression({ level: 6 }));
app.use((req, res, next) => {
  res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
  next();
});
app.use('/static', express.static(path.join(process.cwd(), 'public'), {
  setHeaders: (res, filePath) => {
    if (/\.(js|css|woff2|png|svg)$/.test(filePath)) {
      res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    }
  }
}));

app.get('/healthz', (_req, res) => res.status(200).send('ok'));

app.listen(3000, () => console.log('server on 3000'));

ポイント:

  • キャッシュ安定化はLCP/TBT双方に効く(ネットワークI/O削減+初期パース負荷低減)。⁶

コード例6:Next.js middlewareでpreload/preconnect

import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  const res = NextResponse.next();
  res.headers.set('Link', [
    '</fonts/myfont.woff2>; rel=preload; as=font; type="font/woff2"; crossorigin',
    '<https://cdn.example.com>; rel=preconnect; crossorigin',
  ].join(','));
  return res;
}

ポイント:

  • 先読み・接続の前倒しでLCPとFCPを直接短縮。⁶
  • フォントはpreloadとdisplay: swapの併用、Font Loading APIの活用などが有効。¹⁰

ベンチマークと運用(成果に結びつける)

検証対象: EC商品詳細ページ(SSR、初期JS 240KB gzip、Hero画像 160KB、第三者タグ 2本)

測定条件: Lighthouse Mobile/Simulated、3回実行の中央値、CI専用Runner(2vCPU固定)

施策:

  • 画像の適応配信(AVIF + srcset + preload)
  • クリティカルCSS抽出 + non-criticalの遅延読み込み
  • JSコード分割(上位ルート単位)と長タスク分割(scheduler.yield相当)
  • CDN preconnect + early hints(103)

結果(中央値):

指標BeforeAfter改善率
LCP3.8s2.1s-44.7%
INP280ms160ms-42.9%
TBT450ms120ms-73.3%
CLS0.120.04-66.7%
Score5889+31pt

計測安定性:

  • 反復係数(σ/μ): 0.06(LCP), 0.08(TBT)
  • 再現性を担保するため、ワーカーの同時実行数は1、CPUピン止めを採用

ビジネス効果(概算):

  • CVR弾性: LCP 1s短縮でCVR +0.3~0.7pt(社内/公開研究の中央値に基づく参考値。実サイトにより差が大きい)³
  • サンプル: 月商1億円、CVR 2.0%→2.5%で月次+2,500万円流入(広告費一定)
  • 導入期間: 計測基盤1週、改善スプリント1~2週、合計2~3週で初期効果が顕在化

注意点と失敗しない運用

よくある落とし穴

  • 計測条件ドリフト: DevTools/CLI/PSIで条件が異なる。設定をレポートと同梱し、CIからのみ参照。⁵
  • キャッシュ・Service Worker影響: Lighthouseは初回訪問前提。既存SWが意図せず適用されないようにURLを隔離。
  • 認証/地域差: ログイン後や地域CDNはURL・ヘッダで分離して計測。Accept-Language/Geoヘッダ固定。
  • 第三者タグ: ラボで落ちなくとも本番で変動が大きい。遅延読み込み・プロキシ・コンセンサス管理を導入。
  • 1回測定信仰: 中央値運用と許容レンジ(±5%)を定義。ベースライン更新は週次に限定。⁸

ガバナンス(パフォーマンスバジェット)

  • 予算項目例: LCP≤2200ms、TBT≤150ms、JS初期<=150KB、画像初回<=200KB
  • 運用: PR時にLighthouseを回し、逸脱時はCIをFail。例外は一時的ホワイトリストとJira連携。

バジェット定義例(lighthouserc.jsonのsettings.budgets; 参考として記述):

{
  "ci": {
    "collect": { "numberOfRuns": 3 },
    "assert": {
      "assertions": {
        "categories:performance": ["error", {"minScore": 0.85}],
        "largest-contentful-paint": ["error", {"maxNumericValue": 2200}],
        "total-blocking-time": ["error", {"maxNumericValue": 150}]
      }
    }
  }
}

実装チェックリスト(導入から定着まで)

  1. KPIと対象URLの定義(匿名化/安定URL、ログイン後を含めるか判定)
  2. 計測基盤の固定(Node版 or PSI、3回中央値、Mobile)
  3. CI統合(PRコメント・Fail条件・アーティファクト保存)
  4. 可視化(Slack/ダッシュボード/履歴比較)
  5. 改善バックログ(LCP/INP/TBT起点の具体施策単位で管理)
  6. 定例レビュー(週次でベースライン更新、月次でROI評価)

ベストプラクティス要約

  • ネットワーク: HTTP/2+、画像のAVIF/WebP、preload/preconnect、Early Hints活用
  • JS: クリティカル分割、長タスク回避(requestIdleCallback/scheduler)、優先度(fetchpriority)
  • レンダリング: critical CSS、フォントpreload+display:swap、CLS抑制の寸法指定¹⁰
  • サーバ: TTFB短縮(キャッシュ・Edge SSR)、圧縮(Brotli q11/動的はq5~7)
  • ガバナンス: 3回中央値+許容レンジ、予算でゲート、通知と履歴管理

【まとめ(300-500文字)】

Lighthouseは「測る→直す→守る」を自動化する核であり、スコアの意味・測定条件・CIの一体運用がROIを左右する。モバイル想定のシミュレーションを固定し、3回中央値とバジェットで回帰を防げば、LCP/INP/TBTは安定して下がる。コード例のとおり、Node/Puppeteer/PSIを組み合わせた導入は2~3週間で定着する。次の一手として、貴社のKPIに直結するURL選定と、PRでの自動コメント・Fail基準の設定から着手してほしい。最初のスコア改善が出れば、顧客体験と収益の両輪で効果が積み上がる。今、どのURLから測定を始めるか。³⁴

参考文献

  1. HTTP Archive. The Web Almanac 2023: Core Web Vitals. https://almanac.httparchive.org/en/2023/core-web-vitals
  2. web.dev. INP: Interaction to Next Paint. https://web.dev/inp/
  3. Google Search Central Blog. Evaluating page experience for a better web. https://developers.google.com/search/blog/2020/05/evaluating-page-experience
  4. Google Ads ヘルプ. ランディング ページの利便性(ページ速度などの要素を含む). https://support.google.com/google-ads/answer/9238823
  5. Chrome for Developers. Lighthouse performance scoring. https://developer.chrome.com/docs/lighthouse/performance/performance-scoring
  6. web.dev. Largest Contentful Paint (LCP). https://web.dev/articles/lcp/
  7. web.dev. Cumulative Layout Shift (CLS). https://web.dev/articles/cls/
  8. web.dev. Differences between lab and field data. https://web.dev/articles/lab-and-field-data-differences/
  9. PageSpeed Insights ドキュメント. About PageSpeed Insights (v5). https://developers.google.com/speed/docs/insights/v5/about
  10. web.dev. Font best practices for the web. https://web.dev/articles/font-best-practices/