Webパフォーマンス最適化の実践テクニック


なぜパフォーマンスが重要か

Webサイトの読み込み速度は、ユーザー体験、SEO、コンバージョン率に直接影響します。Googleの調査によると、ページの読み込み時間が1秒から3秒に増えると、直帰率が32%増加するとされています。

Core Web Vitalsの理解

Googleが重視する3つの指標:

LCP (Largest Contentful Paint)

最大のコンテンツが表示されるまでの時間

  • 目標: 2.5秒以内

FID (First Input Delay)

ユーザーの最初の操作に応答するまでの時間

  • 目標: 100ms以内

CLS (Cumulative Layout Shift)

視覚的な安定性

  • 目標: 0.1以下

画像の最適化

1. 適切なフォーマットの選択

<!-- WebP形式を優先、フォールバックも用意 -->
<picture>
  <source srcset="image.webp" type="image/webp">
  <source srcset="image.jpg" type="image/jpeg">
  <img src="image.jpg" alt="説明">
</picture>

2. 遅延読み込み (Lazy Loading)

<!-- ネイティブのlazy loading -->
<img src="image.jpg" loading="lazy" alt="説明">

<!-- 重要な画像は即座に読み込む -->
<img src="hero.jpg" loading="eager" alt="ヒーロー画像">

3. 画像サイズの最適化

// Next.jsのImageコンポーネント
import Image from 'next/image';

function MyComponent() {
  return (
    <Image
      src="/photo.jpg"
      alt="写真"
      width={800}
      height={600}
      quality={75} // 75%の品質で十分
      placeholder="blur" // ぼかしプレースホルダー
    />
  );
}

JavaScriptの最適化

1. コード分割

// React.lazy()による動的インポート
import { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<div>読み込み中...</div>}>
      <HeavyComponent />
    </Suspense>
  );
}

2. バンドルサイズの削減

// 必要なものだけインポート
// ❌ 悪い例
import _ from 'lodash';

// ✅ 良い例
import debounce from 'lodash/debounce';

// または、よりモダンな代替ライブラリを使用
import { debounce } from 'radash';

3. Tree Shaking

// package.jsonでsideEffectsを明示
{
  "name": "my-package",
  "sideEffects": false, // または ["*.css"]
}

CSSの最適化

1. クリティカルCSSのインライン化

<head>
  <style>
    /* Above-the-fold CSS をインライン化 */
    .header { background: #000; color: #fff; }
    .hero { height: 100vh; }
  </style>

  <!-- その他のCSSは非同期で読み込み -->
  <link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>

2. 未使用CSSの削除

// PostCSS + PurgeCSSの設定
module.exports = {
  plugins: [
    require('@fullhuman/postcss-purgecss')({
      content: ['./src/**/*.html', './src/**/*.jsx'],
      defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
    })
  ]
}

フォントの最適化

1. フォントの読み込み戦略

/* font-displayでフォールバック動作を制御 */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap; /* すぐにフォールバックフォントを表示 */
}

2. プリロード

<head>
  <link
    rel="preload"
    href="/fonts/custom.woff2"
    as="font"
    type="font/woff2"
    crossorigin
  >
</head>

キャッシング戦略

1. HTTPキャッシュヘッダー

# Nginxの設定例
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
  expires 1y;
  add_header Cache-Control "public, immutable";
}

2. Service Workerによるキャッシング

// service-worker.js
const CACHE_NAME = 'v1';
const urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => cache.addAll(urlsToCache))
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => response || fetch(event.request))
  );
});

リソースヒント

<head>
  <!-- DNS解決を事前に実行 -->
  <link rel="dns-prefetch" href="https://api.example.com">

  <!-- 接続を事前に確立 -->
  <link rel="preconnect" href="https://api.example.com">

  <!-- 重要なリソースを先読み -->
  <link rel="preload" href="/critical.css" as="style">

  <!-- 次のページを先読み -->
  <link rel="prefetch" href="/next-page.html">
</head>

パフォーマンス計測ツール

1. Lighthouse

# CLIでLighthouseを実行
npm install -g lighthouse
lighthouse https://example.com --view

2. Web Vitalsライブラリ

import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';

getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);

3. Performance API

// ナビゲーションタイミングの計測
const perfData = performance.getEntriesByType('navigation')[0];
console.log('DOMContentLoaded:', perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart);
console.log('Load:', perfData.loadEventEnd - perfData.loadEventStart);

// リソースタイミングの計測
const resources = performance.getEntriesByType('resource');
resources.forEach(resource => {
  console.log(`${resource.name}: ${resource.duration}ms`);
});

まとめ

Webパフォーマンスの最適化は、ユーザー体験の向上に直結する重要な取り組みです。上記のテクニックを組み合わせることで、大幅な改善が見込めます。

重要なのは、計測→改善→計測のサイクルを回し続けること。定期的にパフォーマンスをチェックし、継続的に改善していきましょう!