Skip to content

✍️ 필사 모드: 現代Webパフォーマンスの科学 深掘りガイド — Core Web Vitals、INP、LCP、CLS、RUM、Lighthouse、Critical Rendering Path、Speculation Rulesまで (2025)

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

TL;DR — 2024年はWebパフォーマンスの地殻変動だった。INP (Interaction to Next Paint) がFIDを置き換え、Core Web Vitals 3指標は LCP・CLS・INP へ再編。同年に Speculation Rules API (Chrome 121)、Partial Prerendering (Next.js 14)、HTTP/3 QUIC (全トラフィックの30%超)、Early Hints (103 Status) が標準化・一般化した。本稿は「なぜ自分のサイトが遅いのか」に答えるため、Critical Rendering Path の原理、Long Taskの分割RUM vs Lab Data の違い、Lighthouseが外しやすい理由Islands・Resumability (Qwik)、Image/Font最適化 (AVIF、font-display: optional)、2025年のパフォーマンスツールスタック (Vercel Analytics、SpeedCurve、Perfetto) までを整理する。

なぜWebパフォーマンスが再び「熱い話題」になったのか

Webパフォーマンスは2010年代前半に YSlow (Yahoo) と PageSpeed Insights (Google) で一般化したあと、長らく「インフラチームのチェックリスト」レベルに止まっていた。ところが2020年にGoogleが Core Web Vitals を検索ランキングシグナルとして公式発表し、2024年にINPがFIDを置き換えたことで、パフォーマンスは 検索順位・広告CVR・直帰率 を直接左右するビジネス指標になった。

数字で見ると:

  • Amazon: ページ読み込み100msの遅延で売上1%減 (2006年基準、2024年はさらに敏感)
  • Walmart: LCPが1秒短縮でCVR 2%向上
  • BBC: 遅延1秒ごとに10%離脱増
  • Vodafone: LCP 31%改善で販売CVR 8%向上 (2021ケーススタディ)

2024年の Chrome UX Report (CrUX、実ユーザーデータ) によると、世界のトップ100万サイトのうちCore Web Vitals 3指標すべてを通過したのは 42% のみ。対してReact/Vue/AngularなどのSPAフレームワーク採用サイトの通過率は 28% と、静的サイト (65%) に比べて大きく低い。パフォーマンスは「フレームワーク選択のコスト」でもある。

本稿の目的は Core Web Vitals 3指標の定義と計測法、そして なぜ同じコードがLabでは速くFieldでは遅いのかLong TaskとLayout Shiftをどう追跡・修正するか を原理から実務まで一息に整理することだ。パフォーマンスは「一つのテクニック」ではなく「レンダリングパイプライン全体の理解」から生まれる。

ブラウザレンダリングパイプライン — ピクセルに届くまでの旅

Webパフォーマンスを語るには、ブラウザがHTMLを受け取りピクセルを描くまでの Critical Rendering Path を知る必要がある。この経路で時間が漏れるあらゆる地点が「パフォーマンス問題」の原因だ。

1. NavigationURL入力 / リンククリック
2. DNS Lookup           — example.com93.184.216.34
3. TCP + TLS Handshake3-way + SSL (HTTP/1.1: ~300ms, HTTP/3: ~100ms)
4. HTTP RequestGET /
5. TTFBTime To First Byte (サーバー応答)
6. HTML ParsingDOMツリー構築 (パース中に外部リソース要求)
7. CSSOM ConstructionCSSパース、CSSOMツリー
8. Render TreeDOM + CSSOM → 画面に描くノードのみ
9. Layout (Reflow)      — 各ノードの位置/サイズ計算
10. Paintピクセル情報生成 (レイヤー単位)
11. CompositeGPUでレイヤー合成
12. 画面表示             — ユーザーが見る最初のピクセル (FCP)

各段階で起こる主要ボトルネック:

  1. DNS + TCP + TLS — 最初のバイトを受け取るまでのRTT (Round Trip Time) が3–4回。ここを HTTP/3 QUIC (0-RTT resumption) が攻める。
  2. TTFB — サーバー応答時間。SSRならDB + レンダリング、静的ファイルならCDNキャッシュヒットの可否が決め手。
  3. HTML Parsing Blocking<script> タグはデフォルトでパースを止める。async/deferで解消。
  4. CSSOM Blocking — CSSは レンダリングブロッキングリソース。CSSロード完了までRender Treeは組まれない。
  5. LayoutoffsetHeight 読み取りのような 同期強制レイアウト (Forced Reflow) がパフォーマンスキラー。
  6. Paint / Compositewill-change: transformcontain: layout でGPUレイヤー分離を誘導。

React/VueなどのJSフレームワークを使うと、ここに JSダウンロード・パース・実行・Hydration が加わる。この追加段階がSPAがCore Web Vitalsで不利な根本原因だ。

Core Web Vitals 3指標 (2024–2025)

Googleは2020年に Core Web Vitals をユーザー体験の3本柱と定義した:

  • ローディング (Loading) — LCP
  • 相互作用性 (Interactivity) — FID → INP (2024.03置換)
  • 視覚的安定性 (Visual Stability) — CLS

LCP — Largest Contentful Paint (Loading)

定義: ビューポート内で最も大きなコンテンツ要素 (画像、動画ポスター、テキストブロック) が画面に描かれる時点。

基準: <2.5s = Good、2.5–4.0s = Needs Improvement、>4.0s = Poor。

計測: LargestContentfulPaint PerformanceObserver API。

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('LCP element:', entry.element)
    console.log('LCP time:', entry.startTime)
    console.log('LCP render time:', entry.renderTime)
    console.log('LCP size:', entry.size)
  }
}).observe({ type: 'largest-contentful-paint', buffered: true })

LCPが遅くなる4大主犯:

  1. 遅いTTFB — サーバー応答が1秒かかるとLCP <2.5s 通過は不可能。
  2. レンダリングブロッキングリソース<link rel="stylesheet"> の遅延がLCPをそのまま遅らせる。
  3. リソースロード時間 — LCP画像はlazy-loadしてはいけない。fetchpriority="high" を使う。
  4. クライアントサイドレンダリング — ReactはJSダウンロード後Hydrationが終わってからLCP要素がDOMに現れる。

LCP最適化チェックリスト:

  • LCP画像に fetchpriority="high" + loading="eager" + decoding="async"
  • <link rel="preload" as="image" href="/hero.webp" imagesrcset="..." fetchpriority="high">
  • Above-the-foldコンテンツはインラインCSSで
  • フォントは font-display: optional または swap
  • CDN + HTTP/3 + Brotli圧縮
  • SSRまたはSSG (CSRを避ける)

CLS — Cumulative Layout Shift (視覚的安定性)

定義: ページロード中に予期せずレイアウトが動いた度合い。動いた要素の「影響比率」×「移動距離」を累積。

基準: <0.1 = Good、0.1–0.25 = Needs Improvement、>0.25 = Poor。

CLS公式: impact fraction × distance fraction

  • Impact Fraction: ビューポートに対する移動要素の占有比
  • Distance Fraction: 移動距離 / ビューポートサイズ

CLS主犯5つ:

  1. サイズ未指定の画像<img> のwidth/height欠落 → 画像ロード後にレイアウトがずれる。
  2. サイズ未指定の広告/埋め込み — AdSense、YouTube embed、iframe。
  3. FOIT/FOUT — フォント読み込み前後で書体が変わりテキスト高さが変化。
  4. 動的コンテンツ挿入 — 上部にバナー/通知を挿入。
  5. Webフォントロード遅延font-display: swap がCLSを起こす (皮肉)。

CLS最適化:

<!-- 画像サイズ明示 -->
<img src="/hero.jpg" width="1200" height="630" alt="..." />

<!-- CSS aspect-ratio で領域予約 -->
<style>
  .embed { aspect-ratio: 16 / 9; }
</style>

<!-- Font fallbackサイズ調整 -->
<style>
  @font-face {
    font-family: 'Inter';
    src: url('inter.woff2') format('woff2');
    font-display: optional;  /* CLS防止、フォント未到達なら fallback 維持 */
    size-adjust: 107%;       /* fallback とサイズを一致させる */
  }
</style>

<!-- Skeleton / Placeholder -->
<div class="skeleton" style="min-height: 400px;">Loading...</div>

INP — Interaction to Next Paint (2024.03公式化)

定義: ユーザーがクリック/タップ/キーボード入力をしたとき、次のフレームが描かれるまでの時間 — ページ全体の寿命で最悪 (最も遅い) 相互作用 が基準 (98パーセンタイル近傍)。

基準: <200ms = Good、200–500ms = Needs Improvement、>500ms = Poor。

INPがなぜFIDを置き換えたか:

  • FID (First Input Delay) は最初の入力の「開始遅延」しか測らない (入力→ハンドラ開始)。
  • しかし実際のUX問題は 入力後→画面反映まで の全時間。長いJS、再レンダリング、Layout、Paintをすべて含める必要がある。
  • FIDはほとんどのサイトが <100ms で通過 → 弁別力がない。INPははるかに厳しい。

INP公式 (簡略):

INP = max(interactions) where interaction_time = 
  (input delay) + (processing time) + (presentation delay)

INP計測:

import { onINP } from 'web-vitals'

onINP((metric) => {
  console.log('INP:', metric.value, 'ms')
  console.log('Attribution:', metric.attribution)
  // attribution: { interactionType, eventTarget, loafTime, ... }
})

INPが悪化する7大主犯:

  1. Long Task — 50ms超えのメインスレッドブロッキングJS。
  2. 大きなReactコンポーネントの再レンダリング — state更新→サブツリー全体再レンダリング。
  3. 同期ネットワーク要求 — event handler内でfetchをawait。
  4. 大規模DOM — 数千ノードのLayout再計算。
  5. 重いCSSセレクタ:has()、複雑なnth-child。
  6. 同期third-partyスクリプト — 広告、分析ツール。
  7. ResizeObserver/MutationObserver暴走 — コールバックが同期Layoutを引き起こす。

INP最適化 — Long Taskの分割:

// 悪い — 1000アイテムを一度に処理 (800ms Long Task)
function processItems(items) {
  items.forEach(item => expensiveWork(item))
}

// 良い — scheduler.yield() (2024標準化)
async function processItems(items) {
  for (const item of items) {
    expensiveWork(item)
    await scheduler.yield()  // メインスレッドへ譲る
  }
}

// 代替 — setTimeoutで譲る (旧ブラウザ)
function processItemsYield(items, i = 0) {
  const deadline = performance.now() + 10
  while (i < items.length && performance.now() < deadline) {
    expensiveWork(items[i++])
  }
  if (i < items.length) {
    setTimeout(() => processItemsYield(items, i), 0)
  }
}

React INP特化 — useTransition:

import { useTransition, useState } from 'react'

function SearchBox() {
  const [isPending, startTransition] = useTransition()
  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])

  function handleChange(e) {
    setQuery(e.target.value)  // 緊急更新 (input value)
    startTransition(() => {
      setResults(expensiveSearch(e.target.value))  // 低優先度
    })
  }
  // ...
}

RUM vs Lab Data — Lighthouseがよく外す理由

Webパフォーマンスデータには大きく2種類ある:

Lab Data (合成モニタリング、Synthetic)

  • LighthouseWebPageTestPageSpeed Insights (Labタブ)
  • 統制された環境で1回計測
  • 利点: 再現可能、リグレッション検知が容易、CI/CD統合可能
  • 欠点: 実ユーザーのネットワーク/デバイス/相互作用と異なる

Field Data / RUM (Real User Monitoring)

  • Chrome UX Report (CrUX)、Vercel AnalyticsSentry PerformanceNew Relic BrowserSpeedCurve
  • 実ユーザーブラウザからPerformance APIで収集
  • 利点: 実UXを反映、デバイス/ネットワーク分布を反映
  • 欠点: ノイズが多い、デバッグ困難 (どの相互作用がINP 80msかを追跡する必要)

Lighthouseスコアと実スコアが異なる理由:

  1. Lighthouseは Moto G Power + 4Gシミュレーション 固定。実ユーザーはiPhone 15 + 5Gかもしれない。
  2. Lighthouseはページロードのみ計測 → INPはセッション全体の相互作用ベース → Labでは計測不可。
  3. Lighthouseは 1回計測 → 実際は分布。GoogleはCrUXの 75パーセンタイル を基準にする。
  4. Lighthouseには Cookie/ログインなし → 実際は認証済みユーザー画面が異なる。
  5. Lighthouseは viewport固定 360×640 → 実デバイス幅分布と異なる。

推奨戦略:

  • Lab Data (Lighthouse): PRごとのリグレッションテスト (Lighthouse CI)、上限設定
  • RUM: プロダクションモニタリング、p75 / p95指標の追跡、国別/デバイス別ドリルダウン
  • 両者が一致しないとき RUMを信じよ

2025年のRUMスタック

プロバイダ特徴価格 (参考)
Vercel Speed InsightsNext.js統合、Core Web Vitals + Custom Events10,000 events無料
Google CrUX月次公開データ、BigQuery無料
Sentry Performanceエラー + パフォーマンス統合$26/moから
SpeedCurve競合比較、カスタムダッシュボード$149/moから
New Relic BrowserAPM統合Free tier
Cloudflare Web Analyticsサーバーレス、プライバシー優先無料
Pingdom RUM地理分布に強み$14.95/moから

リソース優先度とロード戦略

ブラウザは100個のリソースを同時に受けるわけではない。Fetch PriorityPreload ScannerHTTP/2 PriorityHTTP/3 Priority が複雑に絡んで決める。

Resource Hints — ブラウザに先に知らせる

<!-- DNSを事前解決 -->
<link rel="dns-prefetch" href="https://api.example.com" />

<!-- 接続 (DNS + TCP + TLS) を先に開く -->
<link rel="preconnect" href="https://api.example.com" crossorigin />

<!-- リソース事前ダウンロード (現在ページ用) -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high" />
<link rel="preload" href="/main.js" as="script" />
<link rel="preload" href="/inter.woff2" as="font" type="font/woff2" crossorigin />

<!-- 次ページの事前取得 (low priority) -->
<link rel="prefetch" href="/next-page.html" />

<!-- ページ全体の事前レンダリング (Speculation Rulesに置換) -->
<link rel="prerender" href="/next-page.html" />  <!-- Deprecated -->

Fetch Priority API (Chrome 101+、Safari 17+、Firefox 132+)

<!-- LCP画像 -->
<img src="/hero.webp" fetchpriority="high" />

<!-- スクロール下領域の画像 -->
<img src="/below-fold.jpg" fetchpriority="low" loading="lazy" />

<!-- fetch() API -->
<script>
  fetch('/critical.json', { priority: 'high' })
  fetch('/analytics.json', { priority: 'low' })
</script>

Speculation Rules API — 2024標準化

既存の <link rel="prefetch">prerender の限界を越え、CSSセレクタベース でユーザーが 訪問する可能性が高いリンク を事前prerender。

<script type="speculationrules">
{
  "prerender": [{
    "urls": ["/product/1", "/product/2"],
    "eagerness": "moderate"
  }],
  "prefetch": [{
    "where": { "href_matches": "/product/*" },
    "eagerness": "conservative"
  }]
}
</script>

eagerness レベル:

  • immediate — 即座 (aggressive)
  • eager — ヒント発見即時
  • moderate — リンクにhover/touchなど
  • conservative — リンククリック直前

Chrome 121+、実質的にLCP 0ms が可能 (prerenderされたページへの遷移時に即時表示)。

Early Hints (HTTP 103)

サーバーが最終応答 (200) の前に 103 Early Hints ステータスコードで Link: </main.css>; rel=preload ヒントを先に送る技術。

HTTP/1.1 103 Early Hints
Link: </main.css>; rel=preload; as=style
Link: </hero.webp>; rel=preload; as=image

HTTP/1.1 200 OK
Content-Type: text/html
...

Cloudflare、Fastly、Next.js (14.1+) がサポート。TTFBを待たずに主要リソースを先に取得 → LCP 200–400ms短縮可能。

画像最適化 — 全Webサイト帯域の50%

Chrome UX Reportによると平均Webページで 画像は全バイトの48%。画像最適化だけでLCPを1秒以上短縮できる。

フォーマット選定

フォーマットサポート特徴圧縮率
JPEG100%写真、非可逆基準
PNG100%透明度、可逆サイズ大
WebP97% (IE除く)Google、25–35%小JPEG比25–35%小
AVIF93%AV1コーデックベース、50%小JPEG比50%小
JPEG XLSafariのみ (実験的)将来候補AVIFと同等

2025年推奨: <picture> でAVIF → WebP → JPEG fallback。

<picture>
  <source srcset="/hero.avif" type="image/avif" />
  <source srcset="/hero.webp" type="image/webp" />
  <img src="/hero.jpg" width="1200" height="630" alt="..." loading="lazy" decoding="async" />
</picture>

Responsive Images — srcset + sizes

<img 
  src="/hero-800.jpg"
  srcset="/hero-400.jpg 400w,
          /hero-800.jpg 800w,
          /hero-1600.jpg 1600w,
          /hero-2400.jpg 2400w"
  sizes="(max-width: 768px) 100vw, 
         (max-width: 1200px) 50vw, 
         33vw"
  width="800" height="600"
  alt="Hero"
/>

現代CDN — 自動フォーマット変換

  • Cloudinary — URLベース変換 (w_800,f_auto,q_auto)
  • imgix — 動的パラメータ
  • Cloudflare Images — $5/月 100k画像
  • Next.js Image<Image /> コンポーネント (AVIF/WebP自動)
  • Vercel Image Optimization — ビルドタイム + オンデマンド

Lazy Loading

<!-- ネイティブlazy loading (Chrome 77+) -->
<img src="/hero.jpg" loading="lazy" />

<!-- IntersectionObserverベースのカスタム -->
<script>
  const images = document.querySelectorAll('img[data-src]')
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.src = entry.target.dataset.src
        observer.unobserve(entry.target)
      }
    })
  }, { rootMargin: '200px' })  // 200px手前で事前ロード
  images.forEach(img => observer.observe(img))
</script>

注意: LCP画像は 絶対にlazy loadingしないloading="eager" + fetchpriority="high"

フォント最適化 — FOIT/FOUTとCLSの主犯

Webフォントはダウンロードまでテキストが見えない (FOIT) か、fallbackで見えていて突然変わる (FOUT) — いずれもUXを損なう。

font-display 戦略

@font-face {
  font-family: 'Inter';
  src: url('inter.woff2') format('woff2');
  font-display: swap;      /* FOUT: fallback表示後に交換 — CLS発生 */
  font-display: optional;  /* 100ms以内に届かなければ fallback維持 — CLS 0 */
  font-display: block;     /* 3秒まで待機 — FOIT */
  font-display: fallback;  /* 100ms + 3秒 */
}

推奨: LCPテキストは font-display: optional + size-adjust でfallbackのサイズを合わせる。

Font Fallbackサイズ合わせ (size-adjust)

@font-face {
  font-family: 'Inter';
  src: url('inter.woff2') format('woff2');
  font-display: optional;
}

@font-face {
  font-family: 'Inter-fallback';
  src: local('Arial');
  size-adjust: 107.4%;   /* ArialをInterのサイズに調整 */
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Inter', 'Inter-fallback', sans-serif;
}

ツール: Font Style MatcherFontaine (Vite plugin)。

Preload + Subset

<!-- 必須フォントpreload -->
<link rel="preload" href="/inter-latin.woff2" as="font" type="font/woff2" crossorigin />

Subset: 日本語/韓国語フォント (Noto Sans JP、Pretendard) は全グリフで3–10MB。unicode-range で必要領域のみロード。

@font-face {
  font-family: 'Pretendard';
  src: url('Pretendard-KR.woff2') format('woff2');
  unicode-range: U+AC00-D7A3, U+1100-11FF, U+3130-318F;  /* ハングル領域のみ */
}

JavaScriptロード戦略

JSはWebパフォーマンス最大の敵であり友だ。現代SPAは平均 400KB gzipped JS をロード — モバイルでパース/コンパイルだけで >800ms

async vs defer vs module

<!-- Blocking (絶対使わない) -->
<script src="/main.js"></script>

<!-- Async: ダウンロード完了即実行、順序保証なし -->
<script src="/analytics.js" async></script>

<!-- Defer: ダウンロード並列、HTMLパース完了後に実行、順序保証 -->
<script src="/main.js" defer></script>

<!-- Module: デフォルトでdefer、順序保証 -->
<script src="/app.js" type="module"></script>

Code Splitting

Webpack/Rollup/esbuildすべてサポート。ルート/コンポーネント単位でJSを分割し初期ロード量を削減。

// React.lazy
const Heavy = React.lazy(() => import('./HeavyComponent'))

function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <Heavy />
    </Suspense>
  )
}

// Next.js dynamic
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./Chart'), { ssr: false })

Tree Shaking

ESMのimport構文を解析して未使用コードを除去。side-effect free 宣言が必須。

// package.json
{
  "sideEffects": false,
  "exports": {
    ".": "./dist/index.js"
  }
}

Third-Party Scripts — 最大の敵

Google Analytics、Facebook Pixel、Intercomなど3rd partyスクリプト1つでINP 500msを生む。

Partytown (Builder.io) — 3rd partyスクリプトを Web Worker で実行。

<script src="https://cdn.jsdelivr.net/npm/@builder.io/partytown/lib/partytown.js"></script>
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=GA_ID"></script>

Next.js Scriptコンポーネント:

import Script from 'next/script'

<Script src="https://analytics.example.com" strategy="lazyOnload" />
<Script src="https://critical.example.com" strategy="beforeInteractive" />

Long Taskとメインスレッド予算

Long Task: 50ms超でメインスレッドをブロックするJS作業。INPを壊す最大原因。

Long Task検知

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.warn('Long Task:', entry.duration, 'ms', entry.attribution)
  }
}).observe({ type: 'longtask', buffered: true })

Long Animation Frames (LoAF) — 2024年新規

Long Taskの限界を補う新API。フレーム単位で レンダリング + スクリプト + Layout + Paint 時間を分析。

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log('LoAF:', {
      duration: entry.duration,
      scripts: entry.scripts,    // どのスクリプトが何ms取ったか
      blockingDuration: entry.blockingDuration,
    })
  }
}).observe({ type: 'long-animation-frame', buffered: true })

scheduler.yield() — メインスレッドへ譲る

async function bulkWork(items) {
  for (const item of items) {
    process(item)
    if (navigator.scheduling?.isInputPending()) {
      await scheduler.yield()  // 入力待ちがあれば即譲る
    }
  }
}

Web Worker — CPUオフロード

// main.js
const worker = new Worker('/worker.js')
worker.postMessage({ cmd: 'hash', data: largeString })
worker.onmessage = (e) => console.log('hashed:', e.data)

// worker.js
self.onmessage = async (e) => {
  const buf = new TextEncoder().encode(e.data.data)
  const hash = await crypto.subtle.digest('SHA-256', buf)
  self.postMessage(Array.from(new Uint8Array(hash)))
}

Hydration問題 — SPAの根本コスト

React/Vue/AngularのようなSPAは Hydration — サーバーでレンダリングされたHTMLにJSを付けて相互作用可能にする過程 — がINPを壊す。

Hydrationの6段階コスト (Addy Osmani)

  1. JSダウンロード — 200–500KB gzipped
  2. JSパース + コンパイル
  3. Reactツリー再構築 (サーバーHTMLとは無関係)
  4. イベントリスナー付与
  5. useState/useEffect実行
  6. Commit

モバイルでこれ全体が 1–3秒 かかる。その間ユーザークリックは無視される。

解決策1: Partial Hydration (Islands)

AstroMarkoFresh (Deno) のアプローチ。ページの大半は静的HTML、相互作用が必要な部分のみ を島 (island) としてHydrate。

---
// Astroファイル
import Counter from './Counter.tsx'
---
<html>
  <body>
    <h1>静的コンテンツ (Hydrateしない)</h1>
    <Counter client:visible />  <!-- ビューポート進入時Hydrate -->
    <Counter client:idle />     <!-- アイドル状態のとき -->
    <Counter client:load />     <!-- 即時 -->
  </body>
</html>

解決策2: Resumability (Qwik)

Qwikの革新: Hydration自体を無くし、HTMLにserializeされた状態をユーザー相互作用時点で再開 (resume)。

// Qwikコンポーネント
export default component$(() => {
  const count = useSignal(0)
  return (
    <button onClick$={() => count.value++}>
      {count.value}
    </button>
  )
})

HTMLにハンドラURLを埋め込む:

<button on:click="app.js#Counter_onClick[0]">0</button>

JS 0KBでスタート、クリック時点で該当ハンドラJSをlazy load。TTI = LCP を実現。

解決策3: React Server Components + Streaming

React 18 + Next.js 14。Server ComponentsはJSバンドルに含まれない → クライアントバンドルが縮小。Streamingで <Suspense> 境界まで先にレンダリング → LCP短縮。

解決策4: Selective Hydration

React 18の基本機能。<Suspense> 境界を優先度ベースでHydrate。ユーザーがクリックした領域を優先処理。

HTTP/3、QUIC、そしてネットワーク層

2024年、HTTP/3は全トラフィックの 30% を超えた (W3Techs)。TCP上で動くHTTP/2と異なり、HTTP/3は UDPベースのQUICプロトコル 上で動く。

HTTP/1.1 → HTTP/2 → HTTP/3

バージョン基盤多重化Head-of-Line Blocking0-RTT
HTTP/1.1TCPX (6 connections/origin)YesNo
HTTP/2TCPYesTCPレベル YesNo
HTTP/3UDP (QUIC)YesNoYes

HTTP/3のコア利得:

  1. 0-RTT Resumption — 以前の接続鍵を再利用、初回要求からデータ送信
  2. Connection Migration — WiFi → セルラー切替でも接続維持 (Connection ID)
  3. HOL Blocking解消 — あるストリームのパケット喪失が他ストリームをブロックしない
  4. 暗号化必須 — TLS 1.3内蔵、平文不可

実計測 (Cloudflare 2024資料):

  • Google Search: HTTP/3で中央値応答時間3%短縮、上位10%区間10%短縮
  • Facebook: 動画rebuffering 20%減
  • Akamai: モバイルTTFB 12%改善

CDN + Edgeコンピューティング

CloudflareFastlyAWS CloudFrontVercel Edge NetworkBunny.net。コンテンツをユーザー近傍にキャッシュして遅延を最小化。

2025年トレンド:

  • Edge Workers — Cloudflare Workers、Deno Deploy、Vercel Edge Functions。V8 Isolateベースで数msのコールドスタート。
  • Regional Edge Cache — 従来のOrigin → Edge 2段階を3段階に (Origin → Regional → Edge)。
  • Smart Placement (Cloudflare) — ユーザーベースではなく Origin近傍 に配置してDB遅延を最小化。

2025年のパフォーマンスツールスタック

計測ツール

  • Chrome DevTools Performance — 最も基本。2024年に Performance Insights パネル追加でCore Web Vitalsのリアルタイム分析。
  • Lighthouse — Chrome内蔵、CI用 lighthouse-ci、Vercel/Netlify統合。
  • WebPageTest — 詳細分析、Filmstrip、接続詳細。無料 + 有料プラン。
  • PageSpeed Insights — Lab (Lighthouse) + Field (CrUX) 統合ビュー。
  • Chrome UX Report — 月次公開、BigQueryで競合比較。

プロファイラ

  • SpeedScopehttps://www.speedscope.app、フレームグラフ可視化、Chrome Performanceプロファイルをインポート。
  • Perfetto — Chrome DevToolsとChromium内部トレース、UI共有。
  • React DevTools Profiler — コンポーネントレンダリング時間分解。
  • Next.js Build Analyzer@next/bundle-analyzer、バンドルサイズ可視化。

RUM

  • Vercel Speed Insights + Web Analytics — Next.js標準。
  • Sentry Performance — エラー + RUM統合。
  • New Relic Browser — APM連携。
  • Cloudflare Web Analytics — 無料、プライバシー優先。
  • SpeedCurve — 競合比較に強み。

最適化ツール

  • Next.js Image + Vercel Image Optimization — AVIF/WebP自動。
  • Sharp (Node.js) — サーバーサイド画像変換。
  • Partytown — 3rd partyスクリプトWorker隔離。
  • Fontaine (Vite plugin) — fallbackフォント自動生成。
  • Critical (Addy Osmani) — Critical CSS抽出。

実戦最適化チェックリスト (2025)

実Webサイトを最適化する順序:

  1. RUM導入 — Vercel Speed Insightsまたはweb-vitalsライブラリで実指標計測
  2. CDN + HTTP/3 + Brotli — ネットワーク層
  3. サーバーTTFB 200ms以下 — DBクエリ最適化、SSRキャッシュ、Edge Function
  4. LCP画像最適化 — AVIF + fetchpriority="high" + preload
  5. Critical CSSインライン + 残りはdeferredmedia="print" ハックまたはCriticalライブラリ
  6. Font Loadingfont-display: optional + size-adjust fallback
  7. JS Code Splitting — ルート単位 + React.lazy
  8. 3rd Party Scripts監査 — Partytown、next/script strategy="lazyOnload"
  9. CLS除去 — 画像/iframeのwidth/height、Adスロットのaspect-ratio、フォントfallbackマッチング
  10. INP最適化 — Long Taskの分割 (scheduler.yield)、useTransition、Web Worker
  11. Speculation Rules — 予測可能な次ページをprerender
  12. リグレッション防止 — Lighthouse CI、Performance Budget (webpack/rollup plugin)

よくある10のアンチパターン

  1. LCP画像に loading="lazy" を付与 — LCPが永続遅延。
  2. フォント戦略なしのカスタムフォント — FOIT 3秒、空白画面。
  3. React全Hydration + 静的サイト — Astro/Next SSGを使わずNext.js CSR。
  4. Third-partyスクリプトのblockingロード — GA/GTMをheadに素で入れる。
  5. クライアントでMarkdownレンダリング — サーバーで事前HTML化すべき。
  6. Lighthouseスコアのみモニタリング — RUMなしでは実ユーザー体験に盲目。
  7. Layout Thrashingfor ループ内で offsetHeight を繰り返し読み書き。
  8. 巨大画像オリジナルロード — 4K画像を200pxサムネイルに使用。
  9. 同期 fetch をevent handlerでawait — INPが大きく悪化。
  10. Hydration中のstate更新 — 無限Hydration/再レンダリングループ。

次回予告 — データベースの新しい波 — PostgreSQL、pgvector、HNSW、AI時代のDB戦略

Webパフォーマンス最適化の終着点は通常 データベース だ。どれだけCDNを上手く使っても、DBクエリが遅ければTTFBが壊れる。2023–2025年のデータベース界最大の事件は PostgreSQLによるベクトルデータベース征服 だった。pgvector 拡張がPinecone、Weaviate、Qdrantのような専用ベクトルDBを脅かし、「万能DBとしてのPostgreSQL」時代を開いた。

次回は:

  • PostgreSQLがなぜ再び1位か — StackOverflow 2024開発者調査1位
  • pgvectorとHNSWインデックス — ベクトル検索の数学と実際
  • pgvector vs Pinecone vs Weaviate vs Qdrant — 性能/機能/コスト比較
  • PostgreSQL 17の飛躍 — Logical Replication、Incremental Backup
  • Supabase、Neon、PlanetScale、CockroachDB — クラウドPostgreSQLエコシステム
  • JSON、JSONB、GINインデックス — NoSQL機能の完全統合
  • MVCC原理 — 楽観的同時実行制御の優雅さ
  • Citus、TimescaleDB、PostGIS — 拡張エコシステム
  • PostgreSQL + AI — RAGパイプライン実戦

を扱う。「一つのDBですべて」が現実となった時代、その背景と実戦設計を見ていく。Webパフォーマンスの旅がデータ層に続く理由を追ってみよう。

현재 단락 (1/490)

Webパフォーマンスは2010年代前半に **YSlow** (Yahoo) と **PageSpeed Insights** (Google) で一般化したあと、長らく「インフラチームのチェックリス...

작성 글자: 0원문 글자: 19,970작성 단락: 0/490