performance について
このスキルは、Lighthouse監査に基づくWebパフォーマンス最適化の指針を提供します。開発者がボトルネックを特定し、Core Web Vitalsに沿って優先順位を決め、変更前後の指標で改善を測定できます。
主な機能
- 01コードとアセットのボトルネック特定
- 02Core Web Vitalsに基づく優先順位付け
- 03読み込みとレンダリングの最適化
- 04ランタイム効率の改善
- 05変更前後の結果測定
活用シーン
- 01ページ読み込みの遅さの診断
- 02JavaScriptやアセットの負荷削減
- 03レンダリングと操作応答性の改善
- 04Lighthouse指標による変更評価
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davila7/ claude-code-templates --skill performanceインストール後、Claude Code で/performanceを入力して読み込まれたか確認します。
詳細
パフォーマンス最適化
Lighthouse のパフォーマンス監査に基づく詳細なパフォーマンス最適化です。読み込み速度、ランタイム効率、リソース最適化に焦点を当てます。
仕組み
- コードとアセットにおけるパフォーマンスのボトルネックを特定します
- Core Web Vitals への影響度に基づいて優先順位を付けます
- コード例とともに具体的な最適化を提案します
- 最適化前後の指標で改善効果を測定します
パフォーマンスバジェット
| リソース | バジェット | 根拠 |
|---|---|---|
| ページの総容量 | < 1.5 MB | 3G では約 4 秒で読み込み |
| JavaScript(圧縮後) | < 300 KB | パースと実行時間 |
| CSS(圧縮後) | < 100 KB | レンダリングのブロック |
| 画像(ファーストビュー) | < 500 KB | LCP への影響 |
| フォント | < 100 KB | FOIT/FOUT の防止 |
| サードパーティ | < 200 KB | 制御できないレイテンシ |
クリティカルレンダリングパス
サーバーレスポンス
- TTFB < 800ms。 Time to First Byte は高速であるべきです。CDN、キャッシュ、効率的なバックエンドを使用してください。
- 圧縮を有効にします。 テキストアセットには Gzip または Brotli を使用します。Brotli が推奨されます(15〜20% 小さくなります)。
- HTTP/2 または HTTP/3。 多重化により接続のオーバーヘッドが減ります。
- エッジキャッシュ。 可能な場合は CDN のエッジで HTML をキャッシュします。
リソースの読み込み
必要なオリジンへの事前接続:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
重要なリソースのプリロード:
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
重要でない 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 */
}
サードパーティスクリプト
読み込み戦略
// ❌ 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>
計測
主要な指標
| 指標 | 目標 | ツール |
|---|---|---|
| LCP | < 2.5s | Lighthouse, CrUX |
| FCP | < 1.8s | Lighthouse |
| Speed Index | < 3.4s | Lighthouse |
| TBT | < 200ms | Lighthouse |
| TTI | < 3.8s | Lighthouse |
テストコマンド
# Lighthouse CLI
npx lighthouse https://example.com --output html --output-path report.html
# Web Vitals library
import {onLCP, onINP, onCLS} from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
参考資料
Core Web Vitals 固有の最適化については、Core Web Vitals を参照してください。
コメント
Markdown 対応