EXERCISE 90分

ストーリー

高橋アーキテクト
SpeedShopのフロントエンドパフォーマンスの監査報告が来た。Lighthouseスコアは45点。目標は90点以上だ

高橋アーキテクトが報告書を見せた。

高橋アーキテクト
Core Web Vitals、バンドル、画像、CDN。全ての知識を総動員してスコアを改善してほしい

ミッション概要

ミッションテーマ難易度
Mission 1Lighthouse結果の分析初級
Mission 2画像最適化の実装初級
Mission 3バンドルサイズの削減中級
Mission 4CLS修正中級
Mission 5CDN設計中級
Mission 6改善計画書の作成上級

Mission 1: Lighthouse結果の分析(10分)

以下のLighthouse結果から問題点を洗い出し、改善の優先順位を付けてください。

Performance Score: 45/100

Metrics:
  FCP (First Contentful Paint): 3.8s
  LCP (Largest Contentful Paint): 6.2s
  TBT (Total Blocking Time): 890ms
  CLS (Cumulative Layout Shift): 0.35
  Speed Index: 5.1s

Opportunities:
  - Eliminate render-blocking resources: Save 1.5s
  - Properly size images: Save 2.8s
  - Remove unused JavaScript: Save 1.2s
  - Serve images in next-gen formats: Save 0.8s
  - Enable text compression: Save 0.5s

Diagnostics:
  - Image elements do not have explicit width and height
  - Avoid enormous network payloads (Total: 5.2MB)
  - JavaScript execution time: 2.8s
  - Largest Contentful Paint element: <img src="hero-banner.jpg"> (1.8MB)
解答例
優先度問題改善内容期待効果
1LCPが6.2秒Hero画像(1.8MB)の最適化 + preloadLCP 3秒以下に
2画像サイズ未最適化WebP変換 + レスポンシブ画像2.8秒改善
3JS実行時間2.8秒Code-splitting + Tree-shakingTBT 200ms以下に
4レンダリングブロックCritical CSS + 非同期JSFCP 1.5秒改善
5CLS 0.35画像にwidth/height指定CLS 0.1以下に
6テキスト圧縮未設定gzip/brotli有効化0.5秒改善

最優先はLCP要因のHero画像。1.8MBは異常に大きく、200KB以下に最適化できる。


Mission 2: 画像最適化の実装(15分)

SpeedShopの商品画像を最適化するHTMLとCSSを書いてください。

要件

  • Hero画像: preload + WebP/AVIF対応
  • 商品一覧画像: lazy loading + レスポンシブ
  • 画像にwidth/heightを明示
解答例
<head>
  <!-- Hero画像のpreload(LCPを改善) -->
  <link
    rel="preload"
    as="image"
    href="/images/hero-800w.avif"
    type="image/avif"
    media="(max-width: 800px)"
  >
  <link
    rel="preload"
    as="image"
    href="/images/hero-1600w.avif"
    type="image/avif"
    media="(min-width: 801px)"
  >
</head>

<body>
  <!-- Hero画像: preload済み、遅延読み込みしない -->
  <picture>
    <source
      type="image/avif"
      srcset="/images/hero-800w.avif 800w, /images/hero-1600w.avif 1600w"
      sizes="100vw"
    >
    <source
      type="image/webp"
      srcset="/images/hero-800w.webp 800w, /images/hero-1600w.webp 1600w"
      sizes="100vw"
    >
    <img
      src="/images/hero-1600w.jpg"
      alt="SpeedShop セールバナー"
      width="1600"
      height="600"
      fetchpriority="high"
    >
  </picture>

  <!-- 商品一覧画像: lazy loading + レスポンシブ -->
  <div class="product-grid">
    <div class="product-card">
      <picture>
        <source
          type="image/avif"
          srcset="/images/product-001-300w.avif 300w,
                 /images/product-001-600w.avif 600w"
          sizes="(max-width: 600px) 300px, 600px"
        >
        <source
          type="image/webp"
          srcset="/images/product-001-300w.webp 300w,
                 /images/product-001-600w.webp 600w"
          sizes="(max-width: 600px) 300px, 600px"
        >
        <img
          src="/images/product-001-600w.jpg"
          alt="商品名"
          width="600"
          height="600"
          loading="lazy"
          decoding="async"
        >
      </picture>
    </div>
  </div>
</body>

Mission 3: バンドルサイズの削減(20分)

以下のバンドル分析結果を見て、削減計画を立ててください。

bundle.js: 2.4MB (parsed)
  - react: 42KB
  - react-dom: 120KB
  - moment.js: 290KB (全ロケール含む)
  - lodash: 72KB (全関数)
  - chart.js: 205KB
  - admin-panel: 180KB (管理者のみ使用)
  - utils: 15KB
  - app-code: 350KB
  - vendor-misc: 1,126KB
解答例
// 改善計画

// 1. moment.js → date-fns に置き換え(290KB → 25KB)
// Before
import moment from 'moment';
moment(date).format('YYYY-MM-DD');

// After
import { format } from 'date-fns';
format(date, 'yyyy-MM-dd');

// 2. lodash → 個別インポート(72KB → 5KB使用分のみ)
// Before
import _ from 'lodash';
_.debounce(fn, 300);

// After
import debounce from 'lodash/debounce';
debounce(fn, 300);

// 3. chart.js → lazy loading(205KB → 初期0KB)
// Before
import { Chart } from 'chart.js';

// After
const ChartComponent = lazy(() => import('./components/Chart'));

// 4. admin-panel → Code-splitting(180KB → 初期0KB)
const AdminPanel = lazy(() => import('./pages/AdminPanel'));

// 5. vendor-misc → 精査して不要ライブラリを除去

// 削減結果の見積もり
const results = {
  before: '2.4MB',
  after: {
    initialBundle: '550KB', // react + react-dom + utils + app-code
    lazyChunks: '385KB',    // chart.js + admin-panel
    removed: '362KB',       // moment → date-fns, lodash最適化
    vendorOptimized: '500KB', // vendor精査
  },
  improvement: '初期バンドル: 2.4MB → 550KB (77%削減)',
};

Mission 4: CLS修正(15分)

以下のHTMLのCLS問題を特定し、修正してください。

<!-- 問題のあるHTML -->
<div class="page">
  <header>SpeedShop</header>

  <!-- 問題1: 画像サイズ未指定 -->
  <img src="/banner.jpg" alt="バナー">

  <!-- 問題2: 動的に高さが変わるコンテンツ -->
  <div id="ad-space"></div>

  <!-- 問題3: Webフォント切り替え -->
  <h1 style="font-family: 'CustomFont', sans-serif;">セール開催中</h1>

  <div class="products">...</div>
</div>
解答例
<!-- 修正後のHTML -->
<head>
  <!-- フォントのpreload -->
  <link rel="preload" href="/fonts/CustomFont.woff2" as="font" type="font/woff2" crossorigin>
  <style>
    @font-face {
      font-family: 'CustomFont';
      src: url('/fonts/CustomFont.woff2') format('woff2');
      font-display: swap;
      /* size-adjust で代替フォントとのサイズ差を最小化 */
      size-adjust: 105%;
    }

    /* 広告スペースの事前確保 */
    .ad-space {
      min-height: 250px;
      background: #f0f0f0;
    }
  </style>
</head>

<body>
  <div class="page">
    <header>SpeedShop</header>

    <!-- 修正1: width/heightを明示 + aspect-ratio -->
    <img
      src="/banner.jpg"
      alt="バナー"
      width="1200"
      height="300"
      style="aspect-ratio: 4/1; width: 100%; height: auto;"
    >

    <!-- 修正2: 広告スペースの高さを事前確保 -->
    <div id="ad-space" class="ad-space"></div>

    <!-- 修正3: font-display: swap + preload で対応済み -->
    <h1 style="font-family: 'CustomFont', sans-serif;">セール開催中</h1>

    <div class="products">...</div>
  </div>
</body>

修正のポイント:

  1. 画像: width/height属性でブラウザが事前にスペースを確保
  2. 動的コンテンツ: min-heightで広告スペースを事前に確保
  3. フォント: font-display: swap + preload + size-adjust

Mission 5: CDN設計(10分)

SpeedShopのCDN設定(キャッシュルール)を設計してください。

解答例
パスパターンTTL圧縮備考
/assets/*.{js,css}1年brotliファイル名にハッシュ含む
/images/*24時間-画像フォーマット自動変換
/fonts/*1年-immutableヘッダー
/api/products/*1分gzipクエリ文字列をキーに含む
/api/search*30秒gzipクエリ文字列をキーに含む
/api/cart/*0gzipキャッシュしない(Cookie転送)
/api/orders/*0gzipキャッシュしない
/*.html5分brotlimust-revalidate

Mission 6: 改善計画書の作成(20分)

全ミッションの結果をまとめた改善計画書を作成してください。

解答例
=== SpeedShop フロントエンドパフォーマンス改善計画 ===

現状: Lighthouse 45点
目標: Lighthouse 90点以上

Phase 1: 即効性のある改善(1週間)
  1. Hero画像最適化(1.8MB → 150KB)→ LCP改善
  2. gzip/brotli圧縮の有効化 → 全体転送量削減
  3. 画像にwidth/height追加 → CLS改善
  期待効果: Lighthouse 65点

Phase 2: バンドル最適化(2週間)
  4. moment.js → date-fns 置き換え
  5. lodash 個別インポート化
  6. AdminPanel のCode-splitting
  7. chart.js のLazy Loading
  期待効果: Lighthouse 80点

Phase 3: インフラ最適化(1週間)
  8. CDN導入と設定
  9. Critical CSS のインライン化
  10. Webフォントの最適化
  期待効果: Lighthouse 90点以上

KPI:
  - LCP: 6.2秒 → 2.0秒以下
  - TBT: 890ms → 200ms以下
  - CLS: 0.35 → 0.1以下
  - 初期バンドル: 2.4MB → 500KB以下

達成度チェック

ミッションテーマ完了
Mission 1Lighthouse分析[ ]
Mission 2画像最適化[ ]
Mission 3バンドル削減[ ]
Mission 4CLS修正[ ]
Mission 5CDN設計[ ]
Mission 6改善計画書[ ]

チェックリスト

  • Lighthouse結果を読み解き、優先順位を付けられる
  • 画像の最適化(フォーマット、サイズ、読み込み方法)を実装できる
  • バンドルサイズの削減計画を立てられる
  • CLSの原因を特定して修正できる
  • CDNのキャッシュルールを設計できる

次のステップへ

お疲れさまでした。フロントエンドパフォーマンスの実践力が身についたはずです。

次のセクションでは、Step 5の理解度チェックです。


推定所要時間: 90分