optimize について
Web UI の改善に取り組むチーム向けのパフォーマンス最適化スキルです。まず計測してボトルネックを特定し、読み込み、描画、アニメーション、React、ネットワークの負荷に対処します。
主な機能
- 01パフォーマンス計測とボトルネック分析
- 02画像、フォント、バンドルの最適化
- 03描画とアニメーションの指針
- 04React のプロファイリングと最適化
- 05ネットワークと読み込みの改善
活用シーン
- 01遅いインタラクションの診断
- 02初期ページ読み込み負荷の削減
- 03長いリストの描画改善
- 04レイアウトやアニメーション問題の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ aladicf/ better-react-web-ui --skill optimizeインストール後、Claude Code で/optimizeを入力して読み込まれたか確認します。
詳細
パフォーマンスの問題を特定して修正し、より速く、よりスムーズなユーザー体験を実現します。
画像のパフォーマンスの問題が、スクリーンショットのサイズ設定、アイコンの拡大縮小、トリミング方法、ユーザーがアップロードしたメディアの処理に関係する場合は、画像の処理を参照してください。
パフォーマンスの問題が、多数の折り返しテキストブロックの測定、可変高さの仮想化、繰り返し発生するリサイズ後の再レイアウト、テキスト量の多い UI での offsetHeight / getBoundingClientRect() のようなホットパスでの DOM 読み取りに起因する場合は、テキストレイアウトの予測を参照してください。
最適化作業が特に LCP、INP、CLS に重点を置いており、より詳しいサブパートの内訳や実環境とラボ環境での測定に関するガイダンスが必要な場合は、コアウェブバイタルのリファレンスを参照してください。
非常に長いリストの仮想化が必要で、使用するスタックがまだ決まっていない場合は、対応する React プロジェクトのヘッドレス仮想化レイヤーとして、TanStack Virtual をデフォルトで優先してください。プロジェクトですでに別の仮想化レイヤーを使用している場合は、まずそれを維持してください。
必須の事前準備
frontend-designを読み、コンテキスト収集プロトコルに従ってください。利用可能なコンテキストを再利用し、重要な情報が不足している場合に限って質問してください。さらに、対象デバイス、パフォーマンス上の制約、どのユーザー操作が遅く感じられるかも確認してください。
パフォーマンスの問題を評価する
現在のパフォーマンスを把握し、問題を特定します。
- 現在の状態を測定する:
- コアウェブバイタル:LCP、FID/INP、CLS のスコア
- 読み込み時間:操作可能になるまでの時間、初回コンテンツ描画
- 操作のレイテンシ:フィルター、検索、保存、パネルを開く操作、オートコンプリート、その他の繰り返し行う操作への応答時間
- バンドルサイズ:JavaScript、CSS、画像のサイズ
- 実行時パフォーマンス:フレームレート、メモリ使用量、CPU 使用率
- ネットワーク:リクエスト数、ペイロードサイズ、ウォーターフォール
- ボトルネックを特定する:
- 何が遅いですか?(初回読み込み、操作、アニメーションなど)
- 原因は何ですか?(大きな画像、処理コストの高い JavaScript、レイアウトの無駄な再計算など)
- どの程度の問題ですか?(気づく程度、不快に感じる程度、操作を妨げる程度など)
- 影響を受けるのは誰ですか?(すべてのユーザー、狭いレイアウトのユーザー、低速回線のユーザーなど)
重要:変更の前後で測定してください。時期尚早な最適化は時間を無駄にします。実際に重要な部分を最適化してください。
最適化戦略
体系的な改善計画を作成します。
操作速度におけるドハティの閾値
日常的な操作でも、ユーザーの流れを妨げないようにしてください。ユーザーがすぐに反応を確認でき、可能であれば操作が約 400ms 以内に完了したと感じられることを目指します。
処理がその時間を超える場合:
- 約 100ms 以内にすぐ反応を示します(押下、アクティブ、読み込み中、楽観的状態)
- すべての処理が終わるのを待たずに、早い段階で内容をストリーミングまたは一部表示します
- 一般的な操作経路で、次に表示されそうな画面やデータを先読みします
- 何も表示されない待ち時間や汎用的なスピナーより、スケルトン表示、楽観的 UI、段階的な描画を優先します
高頻度の操作では、ユーザーの意図を示して操作の流れを維持できる場合、スピナーを主な体験として頼らないでください。
読み込みパフォーマンス
画像を最適化する:
- 最新の形式(WebP、AVIF)を使用します
- 適切なサイズにします(300px で表示する画像に 3000px の画像を読み込まないでください)
- 画面下部にある画像には遅延読み込みを適用します
- 初期表示される画像は eager に読み込みます。使用しているフレームワークの画像処理の規約に従って、LCP になる可能性が高い画像を優先してください。ブラウザーによる画像読み込みを参照してください。
- レスポンシブ画像(
srcset、picture要素)を使用します - 画像を圧縮します(通常、品質 80~85%の差は視覚的にほとんど分かりません)
- 配信を高速化するため CDN を使用します
<img
src="hero.webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
loading="eager"
fetchpriority="high"
alt="Hero image"
/>
最新の画像形式(AVIF と WebP)の戦略:
AVIF は通常、同等の視覚品質で WebP よりファイルサイズを 30~50%小さくできます。WebP は基本的な環境でより広くサポートされています。両方をフォールバック付きで使用します。
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero image" loading="eager" fetchpriority="high">
</picture>
実践的なルール:
- サイズ削減の効果が最も大きい、ヒーロー画像、大きな背景画像、写真が多いギャラリーには AVIF を生成します
- より幅広いブラウザーに対応できるよう、フォールバックには WebP を使用します
- エンコードのオーバーヘッドがサイズ削減効果を上回る、小さなアイコンや単純なグラフィックには AVIF を配信しないでください
- 可能であれば、フォーマットのネゴシエーションを自動で処理する画像 CDN を使用します
- ローエンドデバイスで AVIF のデコード速度をテストします。ファイルサイズは小さくなりますが、WebP よりデコードが遅い場合があります
JavaScript バンドルを削減する:
- コード分割(ルート単位、コンポーネント単位)
- ツリーシェイキング(未使用コードを削除)
- 未使用の依存関係を削除
- 重要でないコードを遅延読み込み
- 大きなコンポーネントには動的インポートを使用
// Lazy load heavy component
const HeavyChart = lazy(() => import('./HeavyChart'));
Tailwind の出力を最適化する:
- ビルド時に使用中のユーティリティのみを生成できるよう、Tailwind のクラス名を静的に検出可能な状態に保ちます
- 大量の出力を生成する広範な safelist や、実行時に組み立てるクラス文字列は避けます
- その場しのぎのスタイル指定よりも、Tailwind のテーマトークン、ユーティリティ、バリアントを優先します
- CSS Containment ユーティリティや Tailwind の任意プロパティは、Containment の効果がある独立した領域にのみ使用します
フォントを最適化する:
font-display: swapまたはoptionalを使用します- フォントをサブセット化します(必要な文字のみ)
- 重要なフォントをプリロードします
- 適切な場合はシステムフォントを使用します
- 読み込むフォントウェイトを制限します
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
unicode-range: U+0020-007F; /* Basic Latin only */
}
フォントのサブセット化戦略:
サブセット化すると未使用のグリフが削除され、ファイルサイズを大幅に削減できます。Latin フォント全体では 200KB になる場合がありますが、Basic Latin にサブセット化すると同じフォントが 20-30KB になります。
方法:
- 静的サブセット化:言語または文字体系ごとに個別のフォントファイル(Latin、Cyrillic、CJK)を生成し、
unicode-rangeを使ってページに必要なものだけを配信します - 動的サブセット化:Google Fonts などのサービス、またはオンデマンドでスライスを生成するセルフホスト型のサブセット化ツールを使用します
- 重要なグリフのサブセット化:ファーストビューのヒーローテキストでは、見出しで使用する文字のみをサブセット化し、そのファイルをプリロードします
/* Latin-only subset for body text */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-latin.woff2') format('woff2');
font-display: swap;
unicode-range: U+0020-007F, U+00A0-00FF;
}
/* Extended subset for other scripts, loaded only when needed */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom-cyrillic.woff2') format('woff2');
font-display: swap;
unicode-range: U+0400-04FF;
}
サブセット化ツール:
pyftsubset(fonttools):細かく手動でサブセット化する場合に使用しますsubfont:ビルド時に自動でサブセット化しますglyphhanger:HTML/CSS で使用されているグリフのみを抽出します
Tailwind の配信戦略:
生成されたスタイルの配信は、React フレームワークと Tailwind のパイプラインに任せます。手書きのスタイル読み込みパターンを追加するのではなく、生成される Tailwind の出力を軽量に保ち、ルートを考慮したものにすることが目的です。
重要な理由:
- 生成された Tailwind の出力が大きいと、First Contentful Paint が遅れる可能性があります
- 広範な safelist や動的なクラス構築は、未使用のユーティリティ出力を生み出すことがよくあります
- フレームワークレベルのバンドルは、手動でのスタイルプリロードパターンよりも信頼性が高くなります
実装方法:
- ユーティリティクラスをリテラルで記述するか、既知のクラス文字列を使う
clsxなどのフレームワーク対応ヘルパーを使用します - safelist は範囲を絞り、内容を文書化します
- 大きなルートやアイランドを分割し、必要な場所でのみ React、メディア、コンポーネントライブラリのコードを読み込みます
- パフォーマンス改善作業では、生成された Tailwind の出力サイズを確認します
- React、Next.js、React Router、TanStack Start、Vite React、または Astro の React アイランドでは、フレームワーク推奨の Tailwind 統合を使用します
読み込み戦略を最適化する:
- 重要なリソースを優先します(重要でないリソースは async/defer にします)
- 重要なアセットをプリロードします
- 次に表示される可能性が高いページをプリフェッチします
- オフライン対応とキャッシュにはサービスワーカーを使用します
- 多重化には HTTP/2 または HTTP/3 を使用します
コンポーネント単位のレスポンシブ対応にコンテナークエリを使用する:
コンテナークエリを使うと、コンポーネントをビューポートではなく、それを囲むコンテナーのサイズに応じて調整できます。これにより、ページ全体のブレークポイントによる上書きを減らし、コンポーネントを再利用しやすくできます。
使用する場面:
- コンポーネントが複数のコンテキスト(サイドバー、メインカラム、モーダル)に表示される
- デバイスの幅ではなく、コンポーネントが利用できるスペースに応じてレイアウトを変える必要がある
- ページレベルでブレークポイントが増えすぎるのを避けたい
構文、実践的なパターン、ブラウザーサポートについては、コンテナークエリのリファレンスを参照してください。 パフォーマンスに関する注意:
- コンテナークエリーによる実行時コストはごくわずかです。メディアクエリーと同様にコンポジター上で実行されます
- 両方の寸法が本当に必要な場合を除き、
sizeよりcontainer-type: inline-sizeを優先します - すべての要素に包含コンテキストを作成するのは避け、コンポーネントがコンテナーをクエリーする必要がある場合に限って作成します
レンダリングのパフォーマンス
レイアウトスラッシングを避ける:
// ❌ Bad: Alternating reads and writes (causes reflows)
elements.forEach(el => {
const height = el.offsetHeight; // Read (forces layout)
el.style.height = height * 2; // Write
});
// ✅ Good: Batch reads, then batch writes
const heights = elements.map(el => el.offsetHeight); // All reads
elements.forEach((el, i) => {
el.style.height = heights[i] * 2; // All writes
});
レンダリングを最適化する:
- 独立した領域には、Tailwind の containment ユーティリティまたは
[contain:*]の任意プロパティを使用します - DOM の深さを最小限に抑えます(階層が浅いほど高速です)
- DOM のサイズを小さくします(要素を減らします)
- 長いリストには
content-visibility: autoを使用します - 非常に長いリストには仮想スクロールを使用します。アーキテクチャがまだ決まっていない場合は TanStack Virtual を優先し、プロジェクトに既存の仮想化スタックがある場合は、まずそれを維持します
- テキスト量が多く高さが可変のリストやカードでは、折り返しが実際のボトルネックである場合、DOM の高さを繰り返し実測するよりも、テキストの高さを予測して測定する方法を優先します
ペイントとコンポジットを削減する:
- アニメーションには
transformとopacityを使用します(GPU で高速化されます) - レイアウトプロパティ(width、height、top、left)のアニメーションは避けます
- 計測したアニメーションのボトルネックが改善する場合に限り、一時的かつ範囲を絞って
will-changeを追加します。その後は削除します。will-changeがないこと自体は問題ではありません - ペイント領域を最小限に抑えます(小さいほど高速です)
アニメーションのパフォーマンス
GPU アクセラレーション:
/* ✅ GPU-accelerated (fast) */
.animated {
transform: translateX(100px);
opacity: 0.5;
}
/* ❌ CPU-bound (slow) */
.animated {
left: 100px;
width: 300px;
}
60fps を滑らかに保つ:
- 1 フレームあたり 16ms(60fps)を目標にします
- JS アニメーションには
requestAnimationFrameを使用します - スクロールハンドラーにはデバウンスまたはスロットルを適用します
- 可能な場合は、Tailwind ユーティリティ、Tailwind 互換のキーフレーム、または WAAPI を使用します
- アニメーション中の長時間実行される JavaScript は避けます
Intersection Observer:
// Efficiently detect when elements enter viewport
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Element is visible, lazy load or animate
}
});
});
React の最適化
React 固有の最適化:
- メモ化を追加する前に、代表的な操作をプロファイルします。手動キャッシュを導入する前に、プロジェクトの React Compiler の設定に合わせます
- 変更のない props でコストの高い再レンダリングが繰り返され、計測によってボトルネックだと確認された場合は
memo()を使用します - 計測でコストが高いと確認された計算には
useMemo()を使用します。useCallback()は、関数の同一性を安定させることで最適化が実証される場合、または利用側で必要な場合に限って使用します - 長いリストを仮想化します
- ルートをコード分割します
- ID の変化によって計測可能な問題が生じない限り、インラインコールバックをそのまま使用します
- React DevTools Profiler を使用します
React フレームワークのパターン:
- 回避可能な再レンダリングを最小限に抑えます
- コストの高い処理にはデバウンスを適用します
- プロファイリングで効果が確認された場合に、計算値をメモ化します
- フレームワークが推奨するパターンを使用して、ルートとコンポーネントを遅延読み込みします
ネットワークの最適化
リクエストを減らす:
- 小さなファイルを結合します
- アイコンには SVG スプライトを使用します
- 重要な小さなアセットはインライン化します
- 使用していないサードパーティスクリプトを削除します
API を最適化する:
- ページネーションを使用します(すべてを一度に読み込まないでください)
- 必要なフィールドだけをリクエストするには GraphQL を使用します
- レスポンスを圧縮します(gzip、brotli)
- HTTP キャッシュヘッダーを設定します
- 静的アセットには CDN を使用します
低速な接続向けに最適化する:
- 接続状況に応じた適応型読み込み(navigator.connection)
- 楽観的 UI 更新
- リクエストの優先順位付け
- プログレッシブエンハンスメント
Core Web Vitals の最適化
Largest Contentful Paint(LCP < 2.5s)
- ヒーロー画像を最適化します
- 生成される Tailwind の出力をスリムに保ち、LCP 要素より前に不要なルートコードやアイランドコードを読み込まないようにします
- 重要なリソースをプリロードします
- CDN を使用します
- サーバーサイドレンダリングを使用します
First Input Delay(FID < 100ms)/INP(< 200ms)
- 長時間タスクを分割します
- 重要でない JavaScript の読み込みを遅らせます
- 負荷の高い計算には Web Workers を使用します
- JavaScript の実行時間を短縮します
Cumulative Layout Shift(CLS < 0.1)
- 画像と動画に寸法を設定します
- 既存のコンテンツより上にコンテンツを挿入しないでください
- Tailwind のアスペクト比ユーティリティを使用します
- 広告や埋め込みコンテンツ用のスペースを確保します
- レイアウトシフトを引き起こすアニメーションは避けます
/* Reserve space for image */
.image-container {
aspect-ratio: 16 / 9;
}
パフォーマンスのモニタリング
使用するツール:
- Chrome DevTools(Lighthouse、Performance パネル)
- WebPageTest
- Core Web Vitals(Chrome UX Report)
- バンドルアナライザー(webpack-bundle-analyzer)
- パフォーマンスモニタリング(Sentry、DataDog、New Relic)
主要な指標:
- LCP、FID/INP、CLS(Core Web Vitals)
- インタラクティブになるまでの時間(TTI)
- 初回コンテンツ描画(FCP)
- 合計ブロック時間(TBT)
- バンドルサイズ
- リクエスト数
重要:実際のネットワーク環境で、実際のデバイスを使って測定してください。高速な接続を使うローカルブラウザーの広い画面では、実際の状況を再現できません。
絶対に行わないこと:
- 測定せずに最適化する(早すぎる最適化)
- パフォーマンスのためにアクセシビリティを犠牲にする
- 最適化中に機能を壊す
will-changeをあらゆる場所で使う(新しいレイヤーが作られ、メモリーが消費されます)- ファーストビューのコンテンツを遅延読み込みする
- 重大な問題を無視してマイクロ最適化に取り組む(まず最大のボトルネックを最適化してください)
- 制約のあるブラウザーでのパフォーマンスを忘れる(ハードウェアの性能が低く、CPU の余裕が少なく、接続が遅い場合がよくあります)
改善を検証する
最適化の効果をテストしてください:
- 最適化前後の指標:Lighthouse のスコアを比較します
- 実ユーザーモニタリング:実際のユーザーに対する改善を追跡します
- さまざまなデバイス:低消費電力で粗いポインターを備えたハードウェアと、一般的な精密ポインターのブラウザーでテストします
- 低速な接続:3G に速度制限して、使用感をテストします
- リグレッションがないこと:機能が引き続き動作することを確認します
- ユーザーが感じる速度:実際に速く感じられますか?
パフォーマンスは機能の一つです。快適な体験は、応答性が高く、洗練されていて、よりプロフェッショナルに感じられます。体系的に最適化し、徹底的に測定して、ユーザーが感じるパフォーマンスを優先してください。