Chuyển đến nội dung chính

レッスン 20: パフォーマンスの最適化

コア Web Vitals の最適化。 React.lazy、動的インポート。バンドル分析、ツリーシェイク。メモ化パターン。画像/フォントの最適化、CDN 戦略。

💻 プログラミング — レッスン 20 レッスン 20: パフォーマンスの最適化

React と Next.js: 基本から高度まで

パート 6: テスト、展開、実稼働

xdev.asia

1. コアウェブバイタル

メトリック説明良い改善方法
LCP最大のコンテンツ豊富なペイント< 2.5秒優先画像、プリロードフォント
INP次のペイントへのインタラクション< 200msJS を減らし、トランジションを使用する
CLS累積レイアウトシフト< 0.1画像サイズを設定し、FOUT を回避する

2. 動的インポートとコード分割

import dynamic from 'next/dynamic';

// Lazy load heavy component
const CodeEditor = dynamic(() => import('@/components/CodeEditor'), {
  loading: () => <div>Loading editor...</div>,
  ssr: false, // Client-only component
});

// Conditional dynamic import
const AdminPanel = dynamic(() => import('@/components/AdminPanel'));

export default function Page({ isAdmin }: { isAdmin: boolean }) {
  return (
    <div>
      <CodeEditor />
      {isAdmin && <AdminPanel />}
    </div>
  );
}

3. メモ化パターン

import { memo, useMemo, useCallback } from 'react';

// memo — skip re-render if props unchanged
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
});

// useMemo — cache expensive computation
function Dashboard({ data }: { data: number[] }) {
  const stats = useMemo(() => ({
    total: data.reduce((a, b) => a + b, 0),
    average: data.reduce((a, b) => a + b, 0) / data.length,
    max: Math.max(...data),
  }), [data]);

  return <div>Total: {stats.total}</div>;
}

// useCallback — stable function reference
function Parent() {
  const handleClick = useCallback((id: string) => {
    console.log('Clicked:', id);
  }, []);

  return <ExpensiveChild onClick={handleClick} />;
}

4. バンドル分析

npm install -D @next/bundle-analyzer
// next.config.ts
import withBundleAnalyzer from '@next/bundle-analyzer';

const config = withBundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
})({
  // ...other config
});

export default config;
# Analyze bundle
ANALYZE=true npm run build

5. フォントの最適化

// app/layout.tsx
import { Inter, JetBrains_Mono } from 'next/font/google';

const inter = Inter({
  subsets: ['latin', 'vietnamese'],
  display: 'swap',
  variable: '--font-inter',
});

const mono = JetBrains_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-mono',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="vi" className={`${inter.variable} ${mono.variable}`}>
      <body className="font-sans">{children}</body>
    </html>
  );
}

6. ヘッダーのキャッシュ

// next.config.ts
const config = {
  async headers() {
    return [
      {
        source: '/images/:path*',
        headers: [
          { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
        ],
      },
      {
        source: '/api/:path*',
        headers: [
          { key: 'Cache-Control', value: 'public, s-maxage=60, stale-while-revalidate=300' },
        ],
      },
    ];
  },
};

7. React コンパイラー (実験的)

// next.config.ts
const config = {
  experimental: {
    reactCompiler: true, // Auto-memoize components
  },
};

次の記事: Docker と CI/CD — コンテナ化、GitHub Actions、Vercel デプロイメント。