performance について
実ユーザーデータで優先度を決め、ブラウザのトレースで原因を調べる、開発者向けのパフォーマンス最適化スキルです。変更前後の測定を重視し、静的な調査結果は仮説として扱います。
主な機能
- 01フィールドとラボの性能ベースライン
- 02Core Web Vitals の優先順位付け
- 03トレースによるボトルネック診断
- 04読み込みと実行時の最適化ガイド
- 05変更前後の測定手順
活用シーン
- 01Core Web Vitals の悪化原因を特定
- 02ベースラインと比較して変更を検証
- 03実行可能なページがないプロジェクトを調査
MCP 対応
Chrome DevTools MCP が使える場合、`performance_start_trace` と `performance_analyze_insight` で性能トレースを記録・分析します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ addyosmani/ web-quality-skills --skill performanceインストール後、Claude Code で/performanceを入力して読み込まれたか確認します。
同梱ファイルreferences/MEASUREMENT.mdreferences/RUM.md
詳細
パフォーマンス最適化
実ユーザーのシグナルを優先順位付けに、ブラウザーのトレースを原因の診断に活用する、エビデンスに基づくパフォーマンス最適化です。読み込み速度、実行時の応答性、リソースの配信に重点を置きます。
仕組み
- ページを実行できる場合は、編集を始める前に測定ワークフローを読み、フィールドとラボの両方でベースラインを確立します。
- 実ユーザーのCore Web Vitalsで低い指標を優先します。DevToolsのパフォーマンストレースと、そこに表示される関連性の高いインサイトを使って原因を特定します。
- 測定されたボトルネックに関連するコードまたはアセットだけを調べて変更します。
- 同等のラボ測定を再実行し、変更前後の値、条件、不確実性を報告します。十分な新しいユーザーデータが集まるまでは、フィールドでの検証は保留です。
実行できるページがない場合は静的検査を行いますが、指摘事項は測定済みの回帰ではなく、仮説と呼びます。影響の大きい各仮説を検証するためのコマンドまたはブラウザーのワークフローを記載してください。
パフォーマンストレースを記録し、関連性の高いインサイトを表示できるブラウザーツールを優先してください。Chrome DevTools MCPでは、performance_start_traceとperformance_analyze_insightを使用します。パフォーマンス以外のLighthouseカテゴリを対象とするlighthouse_auditを経由してパフォーマンスを測定しないでください。
初期パフォーマンス予算
予算は、製品の対象デバイス、ネットワーク、ページ種別、ユーザージャーニーを反映する必要があります。以下の値は、一般的なコンテンツページまたはコマースページ向けの初期的なガードレールであり、あらゆる場合に適用できる合否基準ではありません。既存のプロジェクト予算が定義されている場合は、それを維持してください。
| リソース | 予算 | 理由 |
|---|---|---|
| ページ全体の容量 | < 1.5 MB | 制約のある対象ネットワークでの転送時間とデータ通信料を抑えます。代表的なページを使って調整してください |
| JavaScript(圧縮後) | < 300 KB | 解析と実行のコストを抑えます |
| CSS(圧縮後) | < 100 KB | レンダリングを妨げる処理を抑えます |
| 画像(ファーストビュー) | < 500 KB | LCPに関わる可能性が高いリソースを保護します |
| フォント | < 100 KB | 重要なフォントの転送量を抑えます |
| サードパーティー | < 200 KB | 製品側で制御できないコードの容量を抑えます |
クリティカルレンダリングパス
サーバーの応答
- TTFB < 800ms。 Time to First Byteは短くする必要があります。CDN、キャッシュ、効率的なバックエンドを活用してください。
- 圧縮を有効にします。 テキストアセットにはGzipまたはBrotliを使用します。Brotliを推奨します(15~20%小さくなります)。
- HTTP/2またはHTTP/3。 多重化により、接続のオーバーヘッドを減らせます。
- エッジキャッシュ。 可能な場合は、CDNエッジでHTMLをキャッシュします。
- 測定によりドキュメントのレイテンシが確認された場合は、Early Hints(HTTP 103)を検討します。 トレースでHTMLの生成が遅く、重要なサブリソースが安定している場合は、同じリクエストに対する通常の最終応答を送る前に、
Linkヘッダーを含む中間応答103を送信します。HTTP/2以降を使用してください。CDNは、先行する200応答のLinkヘッダーから103を生成する場合があります。または、オリジン/エッジハンドラーから直接送信できます。未対応のクライアントは最終応答へ進みますが、現在のブラウザーとインフラストラクチャの対応状況を確認してください。ヒントは、効果が実証された重要なプリロードまたはプリコネクトに限定してください。不正確なヒントは帯域幅を無駄にします。Cloudflareは、人工的な画像中心のテストでLCPが20~30%改善したと報告しています。これはベンダーの事例研究であり、期待できる削減幅としてではなく参考情報として扱い、自身の環境で測定してください。MDNの103の実装例とCloudflareの調査を参照してください。
リソースの読み込み
必要なオリジンに事前接続する:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
重要なリソースを事前読み込みする:
トレース上で発見が遅れていることが確認できるリソースのみを事前読み込みします。事前読み込みはどれも帯域幅を消費するため、不要な高優先度リクエストは LCP を遅らせる可能性があります。
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
Speculation Rules API を使って、次に行われそうなナビゲーションを事前レンダリングする:
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}]
}
</script>
moderate は、eager モードより強い意図のシグナルを待ちます。予測の的中率、転送バイト数、サーバーコストを測定してください。誤った事前レンダリングは、概ね使われないナビゲーションに相当します。トレードオフと、分析に必要な prerenderingchange によるゲーティングについては、core-web-vitals → LCP を参照してください。
重要でない CSS の読み込みを遅らせる:
<style>/* Above-fold styles */</style>
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
JavaScript の最適化
必須ではないスクリプトの読み込みを遅らせる:
<script src="/critical.js"></script>
<script defer src="/app.js"></script>
<script async src="/analytics.js"></script>
<script type="module" src="/app.mjs"></script>
コード分割のパターン:
// Route-based splitting
const Dashboard = lazy(() => import('./Dashboard'));
// Component-based splitting
const HeavyChart = lazy(() => import('./HeavyChart'));
// Feature-based splitting
if (user.isPremium) {
const PremiumFeatures = await import('./PremiumFeatures');
}
ツリーシェイキングのベストプラクティス:
// ❌ Imports entire library
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ Imports only what's needed
import debounce from 'lodash/debounce';
debounce(fn, 300);
画像の最適化
フォーマットの選択
| フォーマット | 用途 | ブラウザーのサポート |
|---|---|---|
| AVIF | 写真、最高の圧縮率 | 92%+ |
| WebP | 写真、優れたフォールバック | 97%+ |
| PNG | 透過画像を含むグラフィック | ほぼすべて |
| SVG | アイコン、ロゴ、イラスト | ほぼすべて |
レスポンシブ画像
<picture>
<source
type="image/avif"
srcset="hero-400.avif 400w,
hero-800.avif 800w,
hero-1200.avif 1200w"
sizes="(max-width: 600px) 100vw, 50vw">
<source
type="image/webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, 50vw">
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
width="1200"
height="600"
alt="Hero image"
loading="lazy"
decoding="async">
</picture>
LCP 画像の優先度
<img
src="hero.webp"
fetchpriority="high"
loading="eager"
decoding="sync"
alt="Hero">
<img
src="product.webp"
loading="lazy"
decoding="async"
alt="Product">
フォントの最適化
読み込み戦略
/* System font stack as fallback */
body {
font-family: 'Custom Font', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
/* Prevent invisible text */
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* or optional for non-critical */
font-weight: 400;
font-style: normal;
unicode-range: U+0000-00FF; /* Subset to Latin */
}
重要なフォントの事前読み込み
<link rel="preload" href="/fonts/heading.woff2" as="font" type="font/woff2" crossorigin>
可変フォント
/* One file instead of multiple weights */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
キャッシュ戦略
Cache-Control ヘッダー
# HTML (short or no cache)
Cache-Control: no-cache, must-revalidate
# Static assets with hash (immutable)
Cache-Control: public, max-age=31536000, immutable
# Static assets without hash
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
# API responses
Cache-Control: private, max-age=0, must-revalidate
Service worker によるキャッシュ
// Cache-first for static assets
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image' ||
event.request.destination === 'style' ||
event.request.destination === 'script') {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('static-v1').then((cache) => cache.put(event.request, clone));
return response;
});
})
);
}
});
ランタイムのパフォーマンス
レイアウトの連続再計算を避ける
// ❌ Forces multiple reflows
elements.forEach(el => {
const height = el.offsetHeight; // Read
el.style.height = height + 10 + 'px'; // Write
});
// ✅ Batch reads, then batch writes
const heights = elements.map(el => el.offsetHeight); // All reads
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'; // All writes
});
コストの高い処理をデバウンスする
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
// Debounce scroll/resize handlers
window.addEventListener('scroll', debounce(handleScroll, 100));
requestAnimationFrame を使う
// ❌ May cause jank
setInterval(animate, 16);
// ✅ Synced with display refresh
function animate() {
// Animation logic
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
長いリストを仮想化する
// For lists > 100 items, render only visible items
// Use libraries like react-window, vue-virtual-scroller, or native CSS:
.virtual-list {
content-visibility: auto;
contain-intrinsic-size: 0 50px; /* Estimated item height */
}
View Transitions でナビゲーションをスムーズにする
View Transitions API を使うと、ブラウザーは単一の GPU 合成スナップショットを使って、2 つの DOM 状態の間をクロスフェード(またはカスタムアニメーション)できます。二重レンダリングやレイアウトの連続再計算は発生せず、スナップショットは CLS にも加算されません。
同一ドキュメント(SPA スタイル)— 2026 年のベースライン:
// Wrap the DOM mutation that swaps the view
function navigate(newView) {
if (!document.startViewTransition) return swapDOM(newView);
document.startViewTransition(() => swapDOM(newView));
}
ドキュメント間(MPA スタイル)— Chromium では安定版、それ以外では段階的な機能強化:
/* On both source and destination pages */
@view-transition { navigation: auto; }
これで統合は完了です。同一オリジンのナビゲーションは自動的にフェードするようになります。サムネイルがヒーロー画像に拡大する場合など、特定の要素を共有要素トランジションの対象にするには、それらに同じ view-transition-name を指定します。
.product-thumb[data-id="42"], .product-hero { view-transition-name: product-42; }
これを上記の Speculation Rules と組み合わせると、即時かつアニメーション付きのナビゲーションを実現できます。
サードパーティスクリプト
読み込み戦略
// ❌ Blocks main thread
<script src="https://analytics.example.com/script.js"></script>
// ✅ Async loading
<script async src="https://analytics.example.com/script.js"></script>
// ✅ Delay until interaction
<script>
document.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
const script = document.createElement('script');
script.src = 'https://widget.example.com/embed.js';
document.body.appendChild(script);
observer.disconnect();
}
});
observer.observe(document.querySelector('#widget-container'));
});
</script>
ファサードパターン
<div class="youtube-facade"
data-video-id="abc123"
onclick="loadYouTube(this)">
<img src="/thumbnails/abc123.jpg" alt="Video title">
<button aria-label="Play video">▶</button>
</div>
計測
URL を実行できる場合は、計測ワークフローを使用してください。Chrome DevTools MCP のルーティング、CrUX とフォールバックの情報源、再現可能なラボ条件、および簡潔なエビデンス形式について説明しています。
| 指標 | 種類 | 解釈 |
|---|---|---|
| p75 の LCP、INP、CLS | フィールド | ユーザーの成果を示す Core Web Vitals。合否の優先順位付けに使用します |
| トレース内の LCP、CLS | ラボ | 1 回のナビゲーションで再現可能な診断値 |
| TBT | ラボ | メインスレッドのブロッキングに関する診断値で、INP の大まかな代替指標です。フィールドの INP ではありません |
| FCP、Speed Index | ラボ | 読み込みに関する診断値であり、Core Web Vitals ではありません |
生の PerformanceObserver スニペットは現在のブラウザーセッションでは役立ちますが、それだけでは実ユーザーデータにはなりません。本番環境のテレメトリを求められた場合は、ファーストパーティ RUM のリファレンスを読み、独自実装の指標より web-vitals を優先してください。
参考資料
Core Web Vitals に特化した最適化については、Core Web Vitals を参照してください。