Communitygithub.com

React パフォーマンス最適化

React 19/18 および Next.js 向けのパフォーマンス最適化パターン集。Vercel Engineering のベストプラクティスをベースに、優先度順に整理されたルールと決定木ガイダンスを提供します。

React パフォーマンス最適化 とは?

このスキルは、React 19/18 および Next.js アプリケーションのパフォーマンスを最適化するための包括的なルールセットです。Vercel Labs の `react-best-practices` リポジトリ (MIT ライセンス) をベースにしており、優先度の高い順に整理された 8 つのカテゴリで構成されています。ウォーターフォールの排除やバンドルサイズの最適化から、サーバーサイドのパフォーマンス、クライアントサイドのデータ取得、再レンダリングの削減、レンダリングパフォーマンス、JavaScript パフォーマンス、そして高度なパターンまでをカバーします。各ルールには、具体的なコード例(修正前・修正後)と、いつ適用すべきかという判断基準が含まれており、コードレビューやリファクタリングの際に即座に活用できます。このスキルは、Claude Code などの AI エージェントと連携して、アクティブなコードレビューや PR レビュー時にパフォーマンス問題を自動的に検出・修正するために設計されています。

対応✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/react-performance

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

React パフォーマンス最適化 は何をしますか?

React 19/18 および Next.js 向けのパフォーマンス最適化パターン集。Vercel Labs react-best-practices (MIT, v1.0.0) をベースにしています。このスキルは、ルールを優先度順に整理し、アクティブなコードレビューやリファクタリングのための決定木ガイダンスを提供します。

アクティベーションのタイミング

  • React/Next.js コードのパフォーマンスを記述またはレビューする場合
  • ページの読み込みが遅い、インタラクションが遅い、クライアントの CPU 使用率が高いなどの問題を診断する場合
  • バンドルサイズや Lighthouse Core Web Vitals の低下を監査する場合
  • サーバーコンポーネント / API ルートにおけるウォーターフォールを除去する場合
  • クライアントサイドの再レンダリングを削減する場合
  • 長いリスト、アニメーション、ハイドレーションを最適化する場合
  • app/、pages/、components/、またはデータレイヤーに影響を与える PR の最適化選択を監査する場合

優先度インデックス

優先度カテゴリプレフィックス重要となる場面
1 — クリティカルウォーターフォールの排除async-await の後に独立した await が続く場合
2 — クリティカルバンドルサイズ最適化bundle-初回ロード JS、ルートレベルのインポート、サードパーティライブラリ
3 — 高サーバーサイドパフォーマンスserver-RSC、サーバーアクション、API ルート、SSR
4 — 中-高クライアントサイドデータ取得client-SWR / TanStack Query / フック内の生の fetch
5 — 中再レンダリング最適化rerender-高頻度の状態更新、親子ファンアウト
6 — 中レンダリングパフォーマンスrendering-長いリスト、アニメーション、ハイドレーション
7 — 低-中JavaScript パフォーマンスjs-ホットループ、頻繁なアロケーション
8 — 低高度なパターンadvanced-エフェクト-イベント統合、安定した参照

1. ウォーターフォールの排除 (クリティカル)

「ウォーターフォールはパフォーマンスキラー第1位」 — 連続した await はネットワークレイテンシをそのまま加算します。

await 前に安価な条件チェック

リモートデータを待つ前に、同期条件(props、env、ハードコードされたフラグ)をチェックします。

// 誤り
async function Page({ id }: { id: string }) {
  const flag = await getFlag("show-page");
  if (!flag || !id) return null;
  const data = await getData(id);
  // ...
}

// 正しい — まず安価な同期条件でショートサーキット
async function Page({ id }: { id: string }) {
  if (!id) return null;
  const flag = await getFlag("show-page");
  if (!flag) return null;
  const data = await getData(id);
}

使用されるまで await を遅延

await を、そのデータを使用するブランチ内に移動します。

// 誤り — データが必要かどうかを判断する前に await している
const user = await getUser(id);
if (mode === "guest") return renderGuest();
return renderUser(user);

// 正しい
if (mode === "guest") return renderGuest();
const user = await getUser(id);
return renderUser(user);

独立した処理には Promise.all

// 誤り — 逐次処理
const user = await getUser(id);
const posts = await getPosts(id);
const followers = await getFollowers(id);

// 正しい — 並列処理
const [user, posts, followers] = await Promise.all([
  getUser(id),
  getPosts(id),
  getFollowers(id),
]);

部分的な依存関係 — 早期に開始、遅延して await

// 正しい — すべての Promise を開始し、各結果が必要になった時点でのみ await する
const userP = getUser(id);
const postsP = getPosts(id);
const profile = await getProfile(id);
if (profile.private) return null;
const [user, posts] = await Promise.all([userP, postsP]);

ストリーミングのための Suspense

<Suspense> 境界をデータの近くに配置することで、より遅いサブツリーがストリーミングされている間も、ページが描画可能な部分を表示できるようにします。トレードオフ:コンテンツ到着時にレイアウトシフトが発生する可能性があるため、スペースを予約しておく(スケルトンや min-height)。

サーバーコンポーネント:コンポジションによる並列化

// 誤り — 1 つのコンポーネント内で兄弟の await が逐次実行される
export default async function Page() {
  const user = await getUser();
  const cart = await getCart();
  return <View user={user} cart={cart} />;
}

// 正しい — 子コンポーネントに分割し、React が並列実行
  export default async function Page() {
  return (
    <View>
      <UserSection />
      <CartSection />
    </View>
  );
}

2. バンドルサイズ最適化 (クリティカル)

直接インポート、バレルインポートは避ける

バレル index.ts ファイルは、たとえツリーシェイキングが大部分を削除しても、バンドラーにモジュールグラフ全体を解析させます。直接インポートにより、多くの実際のアプリケーションで初回ロード JS を 200〜800ms 節約できます。

// 誤り
import { Button, Card, Modal } from "@/components";

// 正しい
import { Button } from "@/components/Button";
import { Card } from "@/components/Card";
import { Modal } from "@/components/Modal";

Next.js 13.5 以上には Optimize Package Imports があり、リスト化されたパッケージに対してこれを自動化します。使用してください。リスト化されていないライブラリには手動の直接インポートが依然として必要です。

静的に分析可能なパス

// 誤り — バンドラー/トレース分析を無効にする
const mod = await import(`./pages/${name}`);

// 正しい — ブランチごとに明示的に
const mod = name === "home" ? await import("./pages/home") : await import("./pages/about");

重いコンポーネントの動的インポート

import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <Skeleton />,
  ssr: false, // クライアントオンリーの場合
});

サードパーティスクリプトの遅延

分析、ログ、サポートウィジェットはハイドレーションの後に読み込みます。next/script を strategy="afterInteractive" (デフォルト) または "lazyOnload" で使用します。

条件付きモジュール読み込み

if (user.role === "admin") {
  const { AdminPanel } = await import("./admin/AdminPanel");
  // ...
}

ホバー/フォーカス時のプリロード

ホバー時に <link rel="preload"> または import() をトリガーし、ユーザーがクリックするまでにバンドルがキャッシュにあるようにします。

3. サーバーサイドパフォーマンス (高)

サーバーアクションを API ルートと同様に認証する

すべての "use server" 関数は公開エンドポイントです。アクション内で認証と認可の両方を行ってください。呼び出し元のクライアントコンポーネントによるゲートに依存しないでください。

"use server";
export async function deleteUser(formData: FormData) {
  const session = await getSession();
  if (!session?.user) throw new Error("Unauthorized");
  const targetId = String(formData.get("id"));
  if (session.user.role !== "admin" && session.user.id !== targetId) {
    throw new Error("Forbidden");
  }
  await db.user.delete({ where: { id: targetId } });
}

React.cache() によるリクエスト単位の重複排除

import { cache } from "react";

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

React.cache は単一リクエスト内で重複排除を行います。同じレンダリング内の 3 つのサーバーコンポーネントから getUser("1") を呼び出しても、DB クエリは 1 回のみです。

クロスリクエストデータの LRU キャッシュ

リクエストごとに変化しないデータ(設定、ルックアップテーブル)は、React の外部で LRU キャッシュまたは unstable_cache を使用してキャッシュします。

RSC props での重複シリアライゼーションを避ける

サーバーコンポーネントが同じデータを複数のクライアントコンポーネントにレンダリングする場合、データはコンシューマごとに 1 回シリアライズされます。クライアントコンポーネントを引き上げ、子を渡します。

静的な I/O をモジュールスコープに引き上げる

// 正しい — モジュール読み込み時に 1 回実行
const fontData = readFileSync(fontPath);

export async function Page() {
  return <Banner font={fontData} />;
}

RSC/SSR でのミュータブルなモジュールレベル状態は禁止

サーバー上のモジュール状態はすべてのリクエスト間で共有されます — ユーザー間の競合状態を引き起こします。代わりにリクエストスコープのストレージ(headers()、cookies()、非同期コンテキスト)を使用します。

クライアントコンポーネントに渡すデータを最小化する

クライアントが必要とするものだけをシリアライズします。フィールドを削除し、ページネーションし、DB レイヤーでカラムを射影します。

アイテムごとに Promise.all でネストされたフェッチを並列化

const users = await getUsers();
const enriched = await Promise.all(
  users.map(async (u) => ({ ...u, posts: await getPostsFor(u.id) })),
);

非ブロッキング処理に after() を使用

Next.js 15 の after() は、レスポンスが送信された後に処理(ログ、キャッシュウォーミング、分析)を実行します。

import { after } from "next/server";
export async function GET() {
  const data = await getData();
  after(() => logAnalytics(data));
  return Response.json(data);
}

4. クライアントサイドデータ取得 (中-高)

SWR / TanStack Query による重複排除

複数のコンポーネントが useUser(id) を呼び出す場合、1 つのネットワークリクエストと 1 つのキャッシュエントリを共有する必要があります。共有データには SWR または TanStack Query を使用してください — 独自の useEffect + fetch を決して作成しないでください。

グローバルイベントリスナーの重複排除

// 誤り — すべてのコンポーネントが独自のリスナーを追加
useEffect(() => {
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

// 正しい — フック + グローバルサブジェクトによる単一の共有リスナー
const useScroll = createScrollHook(); // 内部的にはシングルトンサブジェクト

スクロール用パッシブリスナー

window.addEventListener("scroll", handler, { passive: true });

スクロールのスムーズさが向上します。リスナーは preventDefault() を呼び出せません。

localStorage: バージョン管理 + 最小化

  • 常に version フィールドを保存し、スキーマ変更時にバージョンを上げ、古いデータを移行または破棄する
  • ペイロードは小さく保つ — localStorage は同期処理であり、メインスレッドをブロックします

5. 再レンダリング最適化 (中)

コールバック内でのみ使用する状態を購読しない

// 誤り — count が変わるたびに再レンダリング
const count = useStore((s) => s.count);
const handler = () => doSomething(count);

// 正しい — 呼び出し時に 1 回だけ読み取る
const handler = () => {
  const count = useStore.getState().count;
  doSomething(count);
};

負荷の高い処理をメモ化されたコンポーネントに抽出

// 正しい — 子は `items` が変更されたときのみ再レンダリング
const Heavy = memo(function Heavy({ items }: { items: Item[] }) {
  return <Chart data={transform(items)} />;
});

デフォルトの非プリミティブ props をホイスト

// 誤り — レンダリングごとに新しい配列が作成され、memo が機能しない
<List items={items ?? []} />

// 正しい
const EMPTY: Item[] = [];
<List items={items ?? EMPTY} />

エフェクト内のプリミティブな依存関係

// 誤り — レンダリングごとに新しいオブジェクト参照
useEffect(() => {}, [{ id, name }]);

// 正しい — プリミティブ
useEffect(() => {}, [id, name]);

生の値ではなく、派生された真偽値を購読

// 誤り — カートの変更すべてで再レンダリング
const cart = useStore((s) => s.cart);
const hasItems = cart.length > 0;

// 正しい — 空でなくなったときのみ再レンダリング
const hasItems = useStore((s) => s.cart.length > 0);

useEffect ではなくレンダリング中に派生させる

// 誤り
const [full, setFull] = useState("");
useEffect(() => setFull(`${first} ${last}`), [first, last]);

// 正しい
const full = `${first} ${last}`;

安定したコールバックのための関数型 setState

// 正しい
const increment = useCallback(() => setCount((c) => c + 1), []);

高価な値のための遅延状態初期化子

const [tree] = useState(() => parseTree(largeInput));

単純なプリミティブに memo は不要

useMemo(() => x + 1, [x]) はオーバーヘッドです。Memo はオブジェクトの同一性と高コストな計算に価値を発揮します。

独立した依存関係を持つフックを分割

// 誤り — どちらかのソースが変更されると両方のセレクターが再実行
const { a, b } = useSomething(source1, source2);

// 正しい
const a = useA(source1);
const b = useB(source2);

インタラクションロジックをイベントハンドラーに移動

イベントハンドラーはユーザーアクション時にのみ実行されます — useEffect は依存関係が変更されるたびに再実行されます。

緊急でない更新には startTransition

const [pending, startTransition] = useTransition();
startTransition(() => setFilters(newFilters));

高コストなレンダリングには useDeferredValue

const deferredQuery = useDeferredValue(query);
const results = useMemo(() => expensiveSearch(deferredQuery), [deferredQuery]);

一時的な頻繁な値には useRef

頻繁に変更されるが再レンダリングをトリガーすべきでない値(タイムスタンプ、最後のキー、アキュムレーター)に使用します。

コンポーネント内でコンポーネントを定義しない

// 誤り — Inner は Outer がレンダリングされるたびに新しいコンポーネントになる
function Outer() {
  const Inner = () => <span />;
  return <Inner />;
}

レンダリングのたびに新しい Inner 型が作成され、差分検出が無効になり、子がアンマウントされます。

6. レンダリングパフォーマンス (中)

SVG ではなくラッパーをアニメーション

SVG の周りの <div> ラッパーを変換するのは GPU アクセラレーションされます。SVG 自体を変換するとペイントがトリガーされます。

長いリストには content-visibility: auto

.row { content-visibility: auto; contain-intrinsic-size: auto 80px; }

ブラウザは画面外のレンダリングをスキップします。数百行のリストで大きな効果があります。

静的な JSX をホイスト

const STATIC_HEADER = <h1>Title</h1>;
function Page() {
  return <>{STATIC_HEADER}<Body /></>;
}

SVG: 座標精度を下げる

d="M10.123456,20.654321" → d="M10.12,20.65"。各桁がバイト数を消費します。視覚的な違いはサブピクセルレベルです。

インラインスクリプトによるハイドレーションフリッカーの防止

ハイドレーション前に必要な値(テーマ、ロケール)は、React がマウントする前に document.documentElement.dataset.* を設定する <script> をインラインにします。

期待されるハイドレーション不一致は狭く抑える

<time suppressHydrationWarning>{new Date().toLocaleString()}</time>

既知で分岐するリーフノードにのみ使用してください — 他の子を含むツリーでは決して使用しないでください。

マウント/アンマウントの代わりに <Activity> で表示/非表示

React 19 の <Activity mode="visible|hidden"> は、ツリーの状態とエフェクトをマウントしたまま非表示にします。タブやアコーディオンのアンマウント/再マウントよりも安価です。

条件付きレンダリングには && の代わりに三項演算子

// 誤り — `0` がテキストノードとしてレンダリングされる
{count && <Badge>{count}</Badge>}

// 正しい
{count > 0 ? <Badge>{count}</Badge> : null}

ローディング状態には useTransition

アクションと startTransition を組み合わせます。次の状態を計算している間、React は以前の UI を isPending として表示します。

React DOM リソースヒント

import { preload, preconnect } from "react-dom";
preload("/api/critical", { as: "fetch" });
preconnect("https://api.example.com")

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

関連スキル