Webパフォーマンス最適化入門|Core Web Vitals改善の実践テクニック

kento_morota 16分で読めます

「ページの表示が遅い」「スクロールがカクつく」——Webサイトのパフォーマンス問題は、ユーザー体験の低下だけでなく、SEO順位やコンバージョン率にも直接影響します。Googleの調査によると、ページの読み込み時間が1秒から3秒に増加すると、直帰率が32%上昇すると報告されています。

本記事では、Webパフォーマンス最適化の基礎知識からCore Web Vitalsの改善手法まで、実務で即活用できる具体的なテクニックを体系的に解説します。

Webパフォーマンスの基本と計測指標

パフォーマンス最適化に取り組む前に、何を計測し、どの指標を改善すべきかを理解しましょう。

Core Web Vitalsとは

Core Web Vitalsは、Googleが定義する「Web上でのユーザー体験の品質を示す重要な指標」です。2021年からSEOのランキング要因に含まれており、Webサイトの品質を測る上で最も重要な指標となっています。

LCP(Largest Contentful Paint):ページ内で最も大きなコンテンツ(画像やテキストブロック)が表示されるまでの時間です。ユーザーが「ページが表示された」と感じるタイミングに相当します。目標値は2.5秒以内です。

INP(Interaction to Next Paint):ユーザーの操作(クリック、タップ、キー入力)に対して、画面が反応するまでの時間です。以前のFID(First Input Delay)に代わる指標として導入されました。目標値は200ミリ秒以内です。

CLS(Cumulative Layout Shift):ページの読み込み中に要素が予期せず移動する「レイアウトシフト」の累積量です。広告やWebフォントの読み込みで文章が突然ズレる現象がこれに該当します。目標値は0.1以下です。

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

Google PageSpeed Insights:URLを入力するだけで、Core Web Vitalsを含むパフォーマンススコアと改善提案が得られます。実際のユーザーデータ(フィールドデータ)とシミュレーション結果(ラボデータ)の両方を確認できます。

Chrome DevTools(Lighthouseタブ):ブラウザの開発者ツールに組み込まれたLighthouseで、ローカル環境でも詳細なパフォーマンス分析が可能です。

Chrome DevTools(Performanceタブ):ページの読み込みやユーザー操作時の詳細なタイムラインを記録し、ボトルネックを特定できます。

Web Vitals拡張機能:Chrome拡張機能で、閲覧中のページのCore Web Vitalsをリアルタイムで確認できます。

Google Search Console:実際のユーザーデータに基づいたCore Web Vitalsのレポートを確認できます。サイト全体の状況を把握するのに最適です。

LCP(Largest Contentful Paint)の改善

LCPは、ページの主要コンテンツが表示されるまでの時間を示します。ユーザーが最初に目にする「メインコンテンツ」の表示速度を改善する施策を解説します。

画像の最適化

LCPの対象となる要素の多くは画像です。画像の最適化は、LCP改善で最も効果的な施策です。

次世代画像フォーマットの使用:WebPやAVIFは、JPEGやPNGに比べて30〜50%小さいファイルサイズで同等の画質を実現します。

<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="ヒーロー画像" width="1200" height="600">
</picture>

適切なサイズの画像を配信:srcset属性とsizes属性を使って、デバイスの画面幅に応じた最適なサイズの画像を配信します。

<img
  src="hero-800.jpg"
  srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
  sizes="(max-width: 600px) 400px, (max-width: 1024px) 800px, 1200px"
  alt="ヒーロー画像"
  width="1200"
  height="600"
>

LCP画像のプリロード:ファーストビューに表示されるLCP画像は、<link rel="preload">で事前読み込みを指定します。

<link rel="preload" as="image" href="hero.webp" type="image/webp">

lazy loadingの適切な使用:LCPの対象となるファーストビューの画像にはloading="lazy"を付けてはいけません。遅延読み込みはファーストビュー外の画像にのみ適用しましょう。逆に、LCP画像にはfetchpriority="high"を付けて優先度を上げます。

<!-- ファーストビューの画像(LCP対象)-->
<img src="hero.webp" alt="ヒーロー画像" fetchpriority="high" width="1200" height="600">

<!-- ファーストビュー外の画像 -->
<img src="below-fold.webp" alt="記事内画像" loading="lazy" width="800" height="400">

サーバーレスポンスの高速化

CDNの活用:CloudflareやAWS CloudFrontなどのCDNを使って、ユーザーに物理的に近いサーバーからコンテンツを配信します。

TTFBの改善:TTFB(Time to First Byte)は、サーバーがリクエストを受けてから最初のバイトを返すまでの時間です。サーバーサイドのキャッシュ、データベースクエリの最適化、適切なホスティング選択で改善します。

静的サイト生成(SSG):Astro、Next.js、Hugoなどの静的サイトジェネレーターを使って、HTMLを事前に生成しておくことで、サーバーサイドの処理時間をゼロに近づけられます。

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

CSSやJavaScriptがHTMLの解析をブロックし、LCPの表示を遅延させることがあります。

クリティカルCSSのインライン化:ファーストビューの表示に必要なCSSだけを<style>タグでインライン化し、残りのCSSは非同期で読み込みます。

JavaScriptのdefer/async:ファーストビューの表示に不要なJavaScriptはdefer属性で遅延実行します。

<!-- レンダリングをブロックしない -->
<script src="app.js" defer></script>

<!-- 非同期で読み込み、準備でき次第実行 -->
<script src="analytics.js" async></script>

INP(Interaction to Next Paint)の改善

INPは、ユーザーの操作に対する応答性を示します。クリックやタップに対して、画面が素早く反応するための施策を解説します。

JavaScriptの実行時間を削減する

コード分割(Code Splitting):JavaScriptバンドルを機能ごとに分割し、必要な時に必要なコードだけを読み込みます。

// React.lazyを使った動的インポート
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));

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

不要なJavaScriptの削除:Chrome DevToolsの「Coverage」タブで、使用されていないJavaScriptやCSSを特定できます。Tree Shakingが正しく機能しているか確認しましょう。

重い処理のWeb Worker移行:メインスレッドで重い計算処理を実行すると、ユーザー操作への応答が遅れます。Web Workerを使って別スレッドで実行することで、メインスレッドの負荷を軽減できます。

長いタスクの分割

ブラウザのメインスレッドで50ミリ秒以上かかるタスクは「ロングタスク」と呼ばれ、INPの悪化原因になります。

// 悪い例:大量のデータを一度に処理
function processAllItems(items) {
  items.forEach(item => {
    // 重い処理
    heavyProcessing(item);
  });
}

// 良い例:処理を分割してブラウザに制御を返す
async function processItemsInChunks(items, chunkSize = 50) {
  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);
    chunk.forEach(item => heavyProcessing(item));
    // ブラウザに制御を返す
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

scheduler.yield()(対応ブラウザの場合)やrequestIdleCallback()を使うことで、ブラウザがアイドル状態の時に処理を実行する方法もあります。

イベントハンドラの最適化

デバウンスとスロットル:スクロール、リサイズ、キー入力などの頻繁に発火するイベントには、デバウンスやスロットルを適用します。

// デバウンス:最後の入力から300ms後に実行
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

const handleSearch = debounce((query) => {
  fetchSearchResults(query);
}, 300);

CLS(Cumulative Layout Shift)の改善

CLSは、ページ内の要素が予期せず移動する量を示します。ユーザーが読んでいるテキストや押そうとしているボタンが突然ズレるのを防ぐ施策を解説します。

画像・動画のサイズ指定

画像や動画の表示領域が読み込み前に確保されていないと、コンテンツが読み込まれた瞬間にレイアウトシフトが発生します。

<!-- 悪い例:サイズ指定なし -->
<img src="photo.jpg" alt="写真">

<!-- 良い例:width/height属性を指定 -->
<img src="photo.jpg" alt="写真" width="800" height="600">

<!-- CSSのaspect-ratioを使用 -->
<style>
  .responsive-image {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }
</style>
<img src="photo.jpg" alt="写真" class="responsive-image">

widthheight属性を指定すると、ブラウザが画像の読み込み前にアスペクト比を計算し、適切なスペースを確保してくれます。

Webフォントによるレイアウトシフトの防止

Webフォントの読み込み中にシステムフォントが表示され、読み込み完了後にWebフォントに切り替わる際にレイアウトシフトが発生することがあります。

/* font-display: swapの使用 */
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap; /* フォント読み込み中はフォールバックを表示 */
}

/* size-adjustでフォールバックフォントのサイズを調整 */
@font-face {
  font-family: 'CustomFont-Fallback';
  src: local('Hiragino Kaku Gothic ProN');
  size-adjust: 105%;
  ascent-override: 95%;
}

font-display: swapを使うと、フォント読み込み中にフォールバックフォントが表示され、読み込み完了後にWebフォントに切り替わります。size-adjustでフォールバックフォントのサイズを調整することで、切り替え時のレイアウトシフトを最小化できます。

動的コンテンツの配置

広告、バナー、通知バーなどの動的に挿入されるコンテンツは、CLSの主要な原因です。

表示領域を事前に確保する:広告枠のサイズが決まっている場合は、CSSでmin-heightを指定して領域を確保しておきます。

既存コンテンツの上に挿入しない:動的コンテンツは、既存のコンテンツを押し下げる位置ではなく、画面の固定位置(sticky、fixed)に配置するか、ユーザーの操作に応じて表示するようにします。

transform アニメーションを使用する:要素の位置を変更する際は、top/left/marginではなくtransform: translate()を使うことで、レイアウトシフトとしてカウントされません。

その他の重要な最適化テクニック

Core Web Vitals以外にも、全体的なパフォーマンスを向上させるテクニックを紹介します。

リソースヒントの活用

<!-- DNSの事前解決 -->
<link rel="dns-prefetch" href="https://api.example.com">

<!-- 接続の事前確立(DNS + TCP + TLS) -->
<link rel="preconnect" href="https://fonts.googleapis.com">

<!-- リソースの事前読み込み -->
<link rel="preload" as="font" href="/fonts/custom.woff2" type="font/woff2" crossorigin>

<!-- 次のページの事前読み込み -->
<link rel="prefetch" href="/next-page.html">

preconnectは、Google FontsやCDN、APIサーバーなど、確実にリクエストが発生する外部ドメインに対して使用するのが効果的です。

キャッシュ戦略

ブラウザキャッシュ:静的アセット(画像、CSS、JS)に適切なCache-Controlヘッダーを設定し、ブラウザにキャッシュさせます。ファイル名にハッシュ値を含めることで、内容が変わった時だけキャッシュが更新される仕組みにします。

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

Service Worker:後述するPWAの技術ですが、Service Workerを使ったキャッシュ戦略で、リピートユーザーの体験を大幅に改善できます。

バンドルサイズの最適化

Bundle Analyzerの活用:webpack-bundle-analyzerやrollup-plugin-visualizerを使って、バンドルに含まれるモジュールのサイズを視覚化します。意外に大きなライブラリが含まれていることがあります。

軽量な代替ライブラリの検討:momentjs(約300KB)をday.js(約2KB)に、lodash全体をlodash-esの個別インポートに置き換えるなど、軽量な代替を検討しましょう。

まとめ:継続的なパフォーマンス改善を実践しよう

本記事では、Webパフォーマンス最適化の基礎からCore Web Vitals各指標の改善テクニックまで解説しました。

パフォーマンス改善のアプローチを整理します。

まず計測する:PageSpeed InsightsやLighthouseで現状のスコアを確認し、最も改善効果の高い項目から着手しましょう。推測ではなく、データに基づいた改善が重要です。

LCPから優先的に改善する:画像の最適化、プリロード、レンダリングブロックの排除は、比較的少ない工数で大きな効果が得られます。

CLSは予防する:画像のサイズ指定やフォントの最適化は、新規開発時から習慣化しておくことで、後からの修正を防げます。

INPはコード品質と連動する:JavaScriptの実行時間削減は、コード分割やリファクタリングなど、コード品質の改善とセットで取り組みましょう。

継続的にモニタリングする:Google Search ConsoleやReal User Monitoring(RUM)ツールで、実際のユーザーデータを継続的に監視し、パフォーマンスの劣化を早期に検出しましょう。

パフォーマンス改善は一度で完了するものではなく、継続的に取り組むべき課題です。CI/CDパイプラインにLighthouseを組み込み、パフォーマンスの回帰を自動検出する仕組みを構築することをおすすめします。

#パフォーマンス#Core Web Vitals#最適化
共有:
無料メルマガ

週1回、最新の技術記事をお届け

AI・クラウド・開発の最新記事を毎週月曜にメールでお届けします。登録は無料、いつでも解除できます。

プライバシーポリシーに基づき管理します

起業準備に役立つ情報、もっとありますよ。

まずは話だけ聞いてもらう