「Lighthouseスコアが低い」「ページ表示が遅い」——パフォーマンス改善は原因の特定から始まりますが、その分析と修正こそClaude Codeが得意とする領域です。Lighthouseのレポートを読み込ませれば改善箇所を即座に特定し、バンドル分析の結果から不要な依存を見つけ出し、React Profilerの出力から最適なメモ化戦略を提案してくれます。
この記事では、Claude Codeを使ったフロントエンドパフォーマンス最適化の具体的なワークフローを解説します。Lighthouse CI連携、バンドルサイズ削減、Core Web Vitals改善(LCP・CLS・INP)、コード分割、React Profilerとの連携まで、計測→分析→改善→検証のサイクルを回す実践方法をまとめました。
Lighthouse CI × Claude Codeで計測と改善を自動化する
LighthouseをCLIで実行し、そのJSON結果をClaude Codeに読ませて改善提案を自動生成するワークフローです。
# Lighthouse CLIをインストール npm install -g lighthouse @lhci/cli # JSONレポートを生成 lighthouse http://localhost:3000 --output=json --output-path=./lh-report.json # Claude Codeにレポートを読ませて改善提案させる > /add lh-report.json > このLighthouseレポートを解析してください。 > パフォーマンススコアが90未満の項目について、 > 具体的な改善コードを提示してください。
lighthouserc.jsでパフォーマンスゲートを設定する
module.exports = {
ci: {
collect: {
startServerCommand: "npm run start",
url: ["http://localhost:3000/", "http://localhost:3000/products"],
numberOfRuns: 3,
},
assert: {
preset: "lighthouse:recommended",
assertions: {
"categories:performance": ["error", { minScore: 0.9 }],
"categories:accessibility": ["error", { minScore: 1 }],
"largest-contentful-paint": ["error", { maxNumericValue: 2000 }],
"cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
"interaction-to-next-paint": ["error", { maxNumericValue: 200 }],
},
},
upload: {
target: "temporary-public-storage",
},
},
};
name: Lighthouse CI
on: [push, pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci && npm run build
- run: npm install -g @lhci/cli
- run: lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
バンドルサイズの分析と削減
バンドルサイズが大きいとLCPが悪化し、モバイルでの体験が大幅に劣化します。Claude Codeにバンドル分析結果を読ませると、不要な依存や重複パッケージを自動で特定できます。
Next.jsでのバンドル分析
# @next/bundle-analyzer をインストール
npm install @next/bundle-analyzer
# next.config.ts に追加
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer({ /* 既存設定 */ });
# 実行(ブラウザでツリーマップが開く)
ANALYZE=true npm run build
Vite 8でのバンドル分析
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
react(),
visualizer({
open: true,
gzipSize: true,
brotliSize: true,
template: "treemap",
}),
],
});
npm run build を実行し、ビルド出力のチャンクサイズを確認してください。 以下を分析して改善案を提示してください: 1. 50KBを超えるチャンクの一覧 2. tree-shakingが効いていないモジュール 3. dynamic importに移行できるモジュール 4. 軽量な代替ライブラリへの置換候補 (例: moment.js→date-fns, lodash→lodash-es の部分import) 5. 重複してバンドルされているパッケージ
Core Web Vitals 改善の実践
Core Web Vitalsは2024年3月にFID→INP(Interaction to Next Paint)に置き換えられ、インタラクション応答速度がランキングシグナルとして重要性を増しています。
| 指標 | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP(Largest Contentful Paint) | ≤ 2.5秒 | 2.5〜4.0秒 | > 4.0秒 |
| INP(Interaction to Next Paint) | ≤ 200ms | 200〜500ms | > 500ms |
| CLS(Cumulative Layout Shift) | ≤ 0.1 | 0.1〜0.25 | > 0.25 |
LCP改善 ── 画像最適化とプリロード
このページのLCPが3.2秒です。改善してください。 確認すべき項目: 1. LCP要素(通常はヒーロー画像)を特定 2. 画像フォーマットをAVIF/WebPに変換 3. fetchpriority="high" を付与 4. lazy loading がLCP画像に付いていれば削除 5. link rel="preload" でLCP画像をプリロード 6. next/image を使っている場合はpriority属性を追加
<!-- プリロードでLCP画像を先行取得 -->
<link rel="preload" as="image" href="/hero.avif" type="image/avif">
<!-- AVIF > WebP > JPEG のフォールバック -->
<picture>
<source srcset="/hero.avif" type="image/avif">
<source srcset="/hero.webp" type="image/webp">
<img src="/hero.jpg" alt="Hero" width="1200" height="600"
fetchpriority="high" decoding="async">
</picture>
loading="lazy"を付けるのはNGです。ファーストビューに表示される画像はlazy loadingをせず、fetchpriority="high"で優先取得させてください。CLS改善 ── レイアウトシフトの防止
/* 画像・動画: width/height属性を指定すればブラウザが自動でaspect-ratioを計算する */
/* HTML側: <img src="..." width="1200" height="600"> を必ず指定 */
img, video {
max-width: 100%;
height: auto;
}
/* Webフォントのレイアウトシフト防止 */
@font-face {
font-family: "CustomFont";
src: url("/font.woff2") format("woff2");
font-display: optional; /* swapよりシフトが少ない */
size-adjust: 100%;
}
/* 広告・動的コンテンツの最小サイズ確保 */
.ad-slot { min-height: 250px; }
.skeleton { aspect-ratio: 16 / 9; background: #f0f0f0; }
INP改善 ── インタラクション応答速度の最適化
INPはユーザーの操作(クリック・タップ・キー入力)から画面が更新されるまでの時間を計測します。長いJavaScriptタスクがメインスレッドをブロックすると悪化します。
// 長い処理を50件ごとに分割してメインスレッドに制御を返す
async function processLargeList(items: Item[]) {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
if (i % 50 === 0) {
await scheduler.yield(); // メインスレッドに制御を返す
}
}
}
// scheduler.yield()未対応ブラウザ用のポリフィル
function yieldToMain(): Promise<void> {
if ("scheduler" in globalThis && "yield" in scheduler) {
return scheduler.yield();
}
return new Promise((resolve) => setTimeout(resolve, 0));
}
import { useState, useTransition } from "react";
function SearchResults({ items }: { items: Item[] }) {
const [query, setQuery] = useState("");
const [filtered, setFiltered] = useState(items);
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
// 入力欄の更新は即座に反映(高優先度)
setQuery(e.target.value);
// 検索結果のフィルタリングは低優先度(INP悪化を防ぐ)
startTransition(() => {
setFiltered(items.filter((item) => item.name.includes(e.target.value)));
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <div>検索中...</div> : <ResultList items={filtered} />}
</>
);
}
コード分割とLazy Loadingの自動化
このプロジェクトのルーティング構成を確認し、 初期バンドルに含まれている全ページコンポーネントを特定してください。 各ページをReact.lazy + Suspenseでコード分割し、 ローディングUI(Skeleton)とErrorBoundaryも追加してください。
import { lazy, Suspense } from "react";
import { Routes, Route } from "react-router-dom";
import { ErrorBoundary } from "./ErrorBoundary";
import { PageSkeleton } from "./PageSkeleton";
// ルートベースのコード分割(各ページが別チャンクに分離される)
const Home = lazy(() => import("./pages/Home"));
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
const UserProfile = lazy(() => import("./pages/UserProfile"));
export function AppRoutes() {
return (
<ErrorBoundary fallback={<div>エラーが発生しました</div>}>
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</Suspense>
</ErrorBoundary>
);
}
React.lazyは不要です。重いコンポーネント(チャートライブラリ等)のみnext/dynamicで分割してください。React Profiler × Claude Codeで再レンダリングを最適化する
React DevToolsのProfilerで計測した結果をClaude Codeに分析させると、useMemo/useCallback/React.memoの適用判断を自動化できます。
React DevTools ProfilerでFlameGraphを確認しました。 再レンダリングコストが高いコンポーネントのTop5は: 1. ProductList: 45ms(propsは変更なし) 2. Chart: 32ms(dataプロップ変更なし) 3. Sidebar: 28ms(親の再レンダリングで連鎖) 4. CommentSection: 22ms(新しいオブジェクトリテラルをpropsに渡している) 5. Footer: 18ms(毎回再レンダリング) 各コンポーネントについて: - React.memo が有効か(props比較コスト vs レンダリングコスト) - useMemo/useCallback で防げる不要な再レンダリングがあるか - コンポーネント分割で解決すべきか 判断基準と具体的な修正コードを提示してください。
CLAUDE.mdにパフォーマンスバジェットを組み込む
Claude Codeにパフォーマンス意識を「常に」持たせるには、CLAUDE.mdにパフォーマンスバジェットを明記します。
## パフォーマンスバジェット ### Core Web Vitals 目標 - LCP: ≤ 2.5s(モバイル) / ≤ 2.0s(デスクトップ) - INP: ≤ 150ms - CLS: ≤ 0.05 ### バンドルサイズ上限 - 初期JS: ≤ 200KB (gzip) - 個別チャンク: ≤ 50KB (gzip) - 画像(LCP要素): ≤ 100KB ### コーディングルール - 新しいnpm依存追加前にbundlephobia.comでサイズを確認 - 50KB超のライブラリはdynamic importを検討 - LCP画像にlazy loadingを使わない。fetchpriority="high"を付与 - 画像はAVIF/WebP形式を優先 - リスト100件超はvirtualization(react-window等)を使用 - 重い処理はscheduler.yield()またはWeb Workerで分割 - React.memoは計測結果に基づいて適用(早すぎるメモ化は禁止) ### Lighthouseチェック - Performanceスコア90以上を維持 - PRマージ前にlhci autorunで自動検証
CLAUDE.mdの詳しい書き方はCLAUDE.md完全ガイドをご覧ください。
よくある質問
/add lh-report.jsonでレポートを渡すと、最もインパクトの大きい改善箇所を優先度順に提案してくれます。actualDuration > 16msかつprops未変更のコンポーネントだけがReact.memoの候補です。軽いコンポーネント(数ms以下)にReact.memoを付けると、比較処理のコストが上回って逆に遅くなる場合があります。「計測してから最適化」が原則です。startTransitionで低優先度に切り替えるのが最も即効性があります。startTransition(() => { heavyUpdate() })とするだけで、ユーザーの操作感が大きく改善します。さらに改善する場合はscheduler.yield()でロングタスクを分割してメインスレッドに制御を返しましょう。npm install @next/bundle-analyzerでインストールし、next.config.tsでwithBundleAnalyzerをラップしてください。ANALYZE=true npm run buildで実行するとブラウザにツリーマップが表示されます。Claude Codeに「バンドル分析を実行して50KB超のモジュールを特定して」と依頼すると一括で対応できます。imgタグにwidth/height属性を追加するか、CSSでaspect-ratioを指定してください。次に多いのはWebフォントの遅延読み込みによるテキストシフトです。font-display: optionalにすると、フォントが間に合わない場合はシステムフォントを維持し、シフトをゼロにできます。まとめ
- Lighthouse CI: JSONレポートをClaude Codeに読ませると改善箇所を自動特定。GitHub Actionsでスコア90未満をブロック
- バンドル分析: Next.js/@next/bundle-analyzer、Vite 8/rollup-plugin-visualizerの結果からClaude Codeが不要な依存を検出
- Core Web Vitals: LCP≤2.5s(画像最適化+preload)、INP≤200ms(useTransition+scheduler.yield)、CLS≤0.1(aspect-ratio+font-display)
- コード分割: React.lazy + SuspenseまたはNext.js dynamicでルートベース分割
- React Profiler: 再レンダリングコストをClaude Codeに分析させてメモ化戦略を自動判断
- CLAUDE.md: パフォーマンスバジェットを記載して日常開発で常に意識させる
フロントエンド開発全般はClaude Codeフロントエンド開発ガイド、テスト戦略はClaude Codeテスト完全ガイド、GitHub Actionsとの連携はClaude Code GitHub Actions完全ガイドもあわせてご覧ください。
