mcpField
R
react-performance

react-performance

github.com/s-hiraoku開発1992 回インストール4 週間前に更新
リポジトリ

react-performance について

ReactとNext.jsアプリ向けのパフォーマンス最適化スキルです。影響度順に、データ取得の滝、バンドルサイズ、サーバー処理、再レンダリング、描画コストを扱います。

主な機能

  1. 01優先度別の最適化指針
  2. 02逐次的なデータ取得の削減
  3. 03バンドルとインポートの最適化
  4. 04サーバー側の性能改善手法
  5. 05再レンダリングと描画の改善

活用シーン

  1. 01Reactページの逐次取得の特定
  2. 02初期バンドルサイズの削減
  3. 03サーバーでの重複データ取得の回避
  4. 04長いリストの描画改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/s-hiraoku/synapse-a2a --skill react-performance

インストール後、Claude Code で/react-performanceを入力して読み込まれたか確認します。

詳細

React のパフォーマンス

React および Next.js アプリケーションのパフォーマンスを、影響度に基づいて体系的に最適化します。

優先度 1:ウォーターフォールの解消(重大)

逐次的な非同期処理は、パフォーマンスを最も大きく損なう要因です。まずこれを修正してください。

await の遅延

宣言時ではなく、使用する時点まで await を遅らせます。

// BAD: Sequential — total time = fetch1 + fetch2
async function Page() {
  const user = await getUser();
  const posts = await getPosts(user.id);
  return <Feed user={user} posts={posts} />;
}

// GOOD: Parallel where possible
async function Page() {
  const userPromise = getUser();
  const postsPromise = getPosts(); // if independent
  const [user, posts] = await Promise.all([userPromise, postsPromise]);
  return <Feed user={user} posts={posts} />;
}
Suspense ストリーミング

時間のかかるデータを <Suspense> で囲み、シェルをすぐにレンダリングします。

export default function Page() {
  return (
    <main>
      <Header />              {/* instant */}
      <Suspense fallback={<Skeleton />}>
        <SlowDataSection />   {/* streams in */}
      </Suspense>
    </main>
  );
}
部分的な依存関係

プロミス間に部分的な依存関係がある場合は、独立した処理をすぐに開始します。

async function Dashboard() {
  const userPromise = getUser();
  const settingsPromise = getSettings(); // independent

  const user = await userPromise;
  const postsPromise = getPosts(user.id); // depends on user

  const [settings, posts] = await Promise.all([settingsPromise, postsPromise]);
  return <View user={user} settings={settings} posts={posts} />;
}

優先度 2:バンドルサイズ(重大)

直接インポート

本番コードでは、バレルファイルからインポートしないでください。

// BAD: Pulls entire library
import { Button } from '@/components';
import { format } from 'date-fns';

// GOOD: Tree-shakeable
import { Button } from '@/components/ui/button';
import { format } from 'date-fns/format';
動的インポート

初回レンダリング時に必要ない大きなコンポーネントは、コード分割します。

import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('@/components/chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false,  // skip SSR for client-only components
});

const Editor = dynamic(() => import('@/components/editor'), {
  loading: () => <EditorSkeleton />,
});
サードパーティスクリプトの読み込みを遅らせる

分析、ログ記録、重要度の低いスクリプトは、ハイドレーション後に読み込みます。

// BAD: Blocks hydration
import { analytics } from 'heavy-analytics';
analytics.init();

// GOOD: Load after hydration
useEffect(() => {
  import('heavy-analytics').then(({ analytics }) => analytics.init());
}, []);
操作の意図に応じたプリロード

ユーザーが操作の意図を示したとき(ホバー、フォーカス)にリソースをプリロードします。

function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
  const router = useRouter();
  return (
    <Link
      href={href}
      onMouseEnter={() => router.prefetch(href)}
      onFocus={() => router.prefetch(href)}
    >
      {children}
    </Link>
  );
}

優先度 3:サーバーサイドのパフォーマンス(高)

リクエスト単位の重複排除

React.cache() を使って、単一リクエスト内のデータ取得を重複排除します。

import { cache } from 'react';

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

// Called in layout.tsx AND page.tsx — only one DB query
シリアライズの最小化

クライアントコンポーネントが実際に必要とするデータのみを渡します。

// BAD: Serializes entire user object
<ClientAvatar user={user} />

// GOOD: Pass only what's needed
<ClientAvatar name={user.name} avatarUrl={user.avatarUrl} />
レスポンスをブロックしないバックグラウンド処理

レスポンスをブロックすべきでない処理には after() を使います。

import { after } from 'next/server';

export async function POST(request: Request) {
  const data = await processRequest(request);

  after(async () => {
    await logAnalytics(data);
    await sendNotification(data);
  });

  return Response.json(data); // returns immediately
}

優先度 4:再レンダリングの防止(中)

派生状態

派生値はレンダリング中に計算し、エフェクト内では決して計算しません。

// BAD: Extra render cycle
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);

// GOOD: Derive during render
const [items, setItems] = useState([]);
const count = items.length;
安定した参照

コールバック形式の setState を使い、コールバックをレンダリングの外に切り出します。

// BAD: New function every render
<Button onClick={() => setCount(count + 1)} />

// GOOD: Stable reference
<Button onClick={() => setCount(c => c + 1)} />
プリミティブな依存値

誤検出を避けるため、依存配列にはプリミティブ値を使います。

// BAD: Object reference changes every render
useEffect(() => { ... }, [config]);

// GOOD: Primitive values are stable
useEffect(() => { ... }, [config.apiUrl, config.timeout]);
遅延状態初期化

初期状態の計算にコストがかかる場合は、初期化関数を渡します。

// BAD: Runs every render
const [data, setData] = useState(expensiveParse(raw));

// GOOD: Runs only on mount
const [data, setData] = useState(() => expensiveParse(raw));
コストの高いコンポーネントのメモ化

処理負荷の高い計算を、メモ化した子コンポーネントに切り出します。

const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  return items.map(item => <ComplexItem key={item.id} item={item} />);
});
後回しにできる更新には useTransition を使う

緊急性の低い状態更新には startTransition を使います。

const [isPending, startTransition] = useTransition();

function handleSearch(query: string) {
  setInputValue(query);                    // urgent: update input
  startTransition(() => setResults(search(query))); // deferrable: update results
}

優先度 5:レンダリングパフォーマンス(低~中)

コンテンツの表示制御

スクロール領域の長いリストには、CSS の content-visibility を適用します。

.list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px;
}
静的な JSX の外部化

props や state に依存しない JSX は、コンポーネントの外に切り出します。

// BAD: Recreated every render
function Layout({ children }) {
  return (
    <div>
      <footer><p>Copyright 2026</p></footer>
      {children}
    </div>
  );
}

// GOOD: Created once
const footer = <footer><p>Copyright 2026</p></footer>;
function Layout({ children }) {
  return <div>{footer}{children}</div>;
}
条件付きレンダリング

0 や "" がレンダリングされるのを避けるため、&& より三項演算子を使います。

// BAD: Renders "0" when count is 0
{count && <Badge count={count} />}

// GOOD: Explicit boolean check
{count > 0 ? <Badge count={count} /> : null}

クイックリファレンス

問題対策優先度
逐次的なデータ取得Promise.all() / Suspense重大
バレルインポート直接パスでのインポート重大
初期バンドルが大きいnext/dynamic重大
冗長な DB 呼び出しReact.cache()高
props の過剰なシリアライズプリミティブ値のみを渡す高
useEffect 内の派生状態レンダリング中に計算中
不安定なコールバックuseCallback / コールバック形式の setState中
長いリスト仮想化 + content-visibility中
緊急性の低い更新useTransition中

コメント