LESSON 30分

「60fpsを維持するには、各フレームを16.67ms以内に処理する必要がある」と佐藤CTOは言った。「ブラウザのレンダリングパイプラインを理解すれば、何を最適化すべきか見えてくる。」

1. Critical Rendering Path

HTML → DOM → CSSOM → Render Tree → Layout → Paint → Composite

            Blocking CSS/JS

レンダリングブロックの排除

<!-- CSS: クリティカルCSSをインラインに、残りは非同期ロード -->
<style>/* Critical CSS inline */</style>
<link rel="preload" href="/styles/main.css" as="style"
      onload="this.rel='stylesheet'" />

<!-- JS: defer または async -->
<script src="/app.js" defer></script>
<!-- defer: DOMパース完了後に実行(順序保証あり) -->
<!-- async: ダウンロード完了次第実行(順序保証なし) -->

2. SSR / SSG / ISR

戦略TTFBLCPインタラクティブ適用場面
CSR速い遅い遅いSPA(管理画面)
SSRやや遅い速いやや遅い動的コンテンツ
SSG速い速い速い静的コンテンツ
ISR速い速い速い定期更新コンテンツ
// Next.js の ISR(Incremental Static Regeneration)
export async function getStaticProps() {
  const products = await fetchProducts();
  return {
    props: { products },
    revalidate: 60, // 60秒ごとに再生成
  };
}

// App Router の場合
export const revalidate = 60;

export default async function ProductsPage() {
  const products = await fetchProducts();
  return <ProductList products={products} />;
}

3. Hydration 最適化

// Partial Hydration: インタラクティブな部分だけ Hydrate
// Astro の islands アーキテクチャ
// <Counter client:visible /> → ビューポートに入ったら Hydrate
// <Search client:idle /> → メインスレッドがアイドル時に Hydrate
// <Newsletter client:load /> → ページロード時に即 Hydrate

// React Server Components(RSC)
// サーバーコンポーネント: JS バンドルに含まれない
// クライアントコンポーネント: 'use client' を付与
// Progressive Hydration パターン
import { lazy, Suspense } from 'react';

// 画面外のコンポーネントは遅延Hydrate
const Comments = lazy(() => import('./Comments'));
const RelatedProducts = lazy(() => import('./RelatedProducts'));

function ProductPage({ product }: { product: Product }) {
  return (
    <>
      {/* 即座にインタラクティブ */}
      <ProductDetail product={product} />
      <AddToCartButton productId={product.id} />

      {/* スクロール時にHydrate */}
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments productId={product.id} />
      </Suspense>
      <Suspense fallback={<RelatedSkeleton />}>
        <RelatedProducts categoryId={product.categoryId} />
      </Suspense>
    </>
  );
}

4. Virtual DOM 最適化

// React の re-render を最小化する

// 1. memo で不要な再レンダリングを防止
const ProductCard = React.memo(function ProductCard({ product }: { product: Product }) {
  return <div>{product.name} - ¥{product.price}</div>;
});

// 2. useMemo で計算結果をキャッシュ
function ProductList({ products, filter }: Props) {
  const filtered = useMemo(
    () => products.filter(p => p.category === filter),
    [products, filter]
  );
  return filtered.map(p => <ProductCard key={p.id} product={p} />);
}

// 3. リスト仮想化で大量のDOM要素を削減
import { FixedSizeList } from 'react-window';

function VirtualList({ items }: { items: Product[] }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={80}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          <ProductCard product={items[index]} />
        </div>
      )}
    </FixedSizeList>
  );
}

5. Web Worker 活用

// 重い計算をメインスレッドから分離
// worker.ts
self.onmessage = (e: MessageEvent<{ products: Product[]; query: string }>) => {
  const { products, query } = e.data;

  // 重い検索・ソート処理をワーカーで実行
  const results = products
    .filter(p => p.name.includes(query) || p.description.includes(query))
    .sort((a, b) => calculateRelevance(b, query) - calculateRelevance(a, query));

  self.postMessage({ results });
};

// main.ts
const searchWorker = new Worker(new URL('./worker.ts', import.meta.url));

function searchProducts(query: string) {
  searchWorker.postMessage({ products: allProducts, query });
  searchWorker.onmessage = (e) => {
    setSearchResults(e.data.results);
  };
}

まとめ

トピック要点
Critical Rendering PathCSS/JSのブロッキングを排除し初期描画を高速化
SSR/SSG/ISRコンテンツ特性に応じたレンダリング戦略を選択
Hydration最適化Partial/Progressive Hydrationで不要なJSを削減
Virtual DOMmemo, useMemo, リスト仮想化で再レンダリングを最小化
Web Worker重い計算をメインスレッドから分離しINPを改善

チェックリスト

  • Critical Rendering Pathのボトルネックを特定できる
  • SSR/SSG/ISRの使い分けを判断できる
  • Hydration最適化の手法を知っている
  • React.memo / useMemo / react-window を活用できる
  • Web Workerで重い処理を分離できる

次のステップへ

レンダリングパフォーマンスを学んだ。次は 演習 で、フロントエンドの最適化を実践しよう。

推定読了時間: 30分