Webサイトのパフォーマンス改善入門|画像最適化・遅延読み込み・CSS/JS読み込み改善で表示速度を上げる


はじめに

前回の記事ではVSCodeの便利な設定と拡張機能を紹介しました。今回はWebサイトのパフォーマンス改善を解説します。「サイトの表示が遅い」「GoogleのLighthouseスコアが低い」「モバイルで読み込みに時間がかかる」という悩みをお持ちの方は多いでしょう。表示速度はユーザー体験だけでなくSEO(検索順位)にも直結します。難しい知識がなくても今日から実践できる改善方法を順番に解説します。


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

1.1 表示速度がビジネスに与える影響

Googleの調査によると:

→ ページの読み込みが1秒遅くなるごとに
  コンバージョン率が約20%低下する

→ モバイルでのページ表示が3秒以上かかると
  53%のユーザーが離脱する

→ Googleの検索アルゴリズムは
  Core Web Vitals(表示速度の指標)を
  ランキング要因として使用している

つまり:
→ 遅いサイト = ユーザーが離脱する
→ 遅いサイト = 検索順位が下がる
→ 遅いサイト = 売上・問い合わせが減る

1.2 Core Web Vitals とは

Core Web Vitals(コアウェブバイタル):

Googleが定めるWebパフォーマンスの
3つの主要指標。

① LCP(Largest Contentful Paint)
   → ページの主要コンテンツが表示されるまでの時間
   → 目標:2.5秒以内
   → 改善方法:画像最適化・サーバー応答速度改善

② FID(First Input Delay)→ INP に移行
   INP(Interaction to Next Paint)
   → ユーザーの操作に対するページの応答速度
   → 目標:200ミリ秒以内
   → 改善方法:JavaScriptの最適化

③ CLS(Cumulative Layout Shift)
   → ページ読み込み中にコンテンツが
     どれだけ突然ずれるか
   → 目標:0.1以下
   → 改善方法:画像サイズの明示・フォントの最適化

2. 現状を計測する

2.1 Lighthouseでスコアを確認する

Lighthouse(ライトハウス):

Googleが提供する
Webパフォーマンス計測ツール。
Chrome DevToolsに組み込まれている。

使い方:

① Chrome でサイトを開く

② F12 でDevToolsを開く

③「Lighthouse」タブをクリックする

④「Analyze page load」ボタンをクリックする

⑤ しばらく待つとレポートが生成される

スコアの見方:
0 〜 49  → 赤:要改善
50 〜 89 → 黄:改善の余地あり
90〜100  → 緑:良好

計測するカテゴリ:
→ Performance(パフォーマンス)
→ Accessibility(アクセシビリティ)
→ Best Practices(ベストプラクティス)
→ SEO

⚠️ シークレットモードで計測する:
→ 拡張機能の影響を受けないためより正確な結果が出る

2.2 PageSpeed Insightsを使う

PageSpeed Insights:

GoogleのWebサービスとして提供されている
パフォーマンス計測ツール。
実際のユーザーデータ(フィールドデータ)も確認できる。

使い方:
① pagespeed.web.dev にアクセスする
② 計測したいURLを入力する
③「分析」をクリックする

→ モバイルとデスクトップ両方のスコアが確認できる
→ 改善の提案が具体的に表示される
→ 定期的にチェックして改善効果を確認する習慣をつける

3. 画像の最適化

3.1 適切な画像フォーマットを選ぶ

画像フォーマットの使い分け:

JPEG(.jpg):
→ 写真・グラデーションのある画像
→ 透過不要の場合
→ 圧縮率が高くファイルサイズを小さくできる

PNG(.png):
→ 透過が必要な場合
→ ロゴ・アイコン・イラスト
→ テキストを含む画像(文字がつぶれない)

WebP(.webp):
→ JPEGより約25〜35%小さいファイルサイズ
→ 透過にも対応している
→ 現代のブラウザはほぼすべて対応済み
→ 可能な限りWebPを使うのが現在の標準

AVIF(.avif):
→ WebPよりさらに小さいファイルサイズ
→ ブラウザのサポートが広がってきている
→ 対応ブラウザを確認して使う

SVG(.svg):
→ ロゴ・アイコン・図解
→ 拡大縮小しても画質が劣化しない
→ アニメーションもできる
→ シンプルな図形はSVGが最適

3.2 画像のサイズを適切にする

html

<!-- ❌ NG:大きな画像をCSSで縮小して表示する -->
<!-- 3000×2000pxの画像を500px幅で表示 -->
<img src="photo-3000w.jpg" style="width: 500px;">

<!-- ✅ OK:表示サイズに合った画像を用意する -->
<!-- 表示幅の2倍(Retinaディスプレイ対応)まで用意する -->
<img src="photo-1000w.jpg" width="500" height="333">

<!-- ✅ さらに良い:srcsetで画面サイズに応じて切り替える -->
<img
    src="photo-800w.jpg"
    srcset="
        photo-400w.jpg  400w,
        photo-800w.jpg  800w,
        photo-1200w.jpg 1200w
    "
    sizes="
        (max-width: 640px)  400px,
        (max-width: 1024px) 800px,
        1200px
    "
    width="800"
    height="533"
    alt="写真の説明"
>

3.3 pictureタグでWebPと旧フォーマットを使い分ける

html

<!--
    picture タグで
    ブラウザが対応していればWebP・
    対応していなければJPEGを使う
-->
<picture>
    <!-- WebPに対応しているブラウザはこちらを使う -->
    <source
        type="image/webp"
        srcset="
            photo-400w.webp  400w,
            photo-800w.webp  800w,
            photo-1200w.webp 1200w
        "
        sizes="(max-width: 640px) 400px, 800px"
    >
    <!-- 対応していない場合のフォールバック -->
    <img
        src="photo-800w.jpg"
        srcset="
            photo-400w.jpg  400w,
            photo-800w.jpg  800w,
            photo-1200w.jpg 1200w
        "
        sizes="(max-width: 640px) 400px, 800px"
        width="800"
        height="533"
        alt="写真の説明"
    >
</picture>

3.4 画像を圧縮する

画像圧縮ツールの紹介:

オンラインツール:
① Squoosh(squoosh.app)
   → Googleが提供する無料の画像圧縮ツール
   → WebP・AVIF への変換もできる
   → 圧縮前後を並べて品質を確認できる
   → 1枚ずつの圧縮に最適

② TinyPNG / TinyJPG(tinypng.com)
   → ドラッグ&ドロップで簡単に圧縮
   → 複数ファイルの一括処理ができる
   → 無料で月20枚まで

npmツール(自動化する場合):
npm install -D imagemin imagemin-webp

目安となる圧縮率:
→ JPEG:品質70〜85%が視覚的に遜色なく
  大幅にファイルサイズを削減できる
→ PNG:Squooshで圧縮すると
  60〜70%削減できることが多い
→ WebP:JPEGの25〜35%のサイズになる

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

4.1 画像の遅延読み込み

html

<!--
    loading="lazy" を追加するだけで
    画面外の画像を遅延読み込みできる。
    ブラウザの標準機能なので追加のJSは不要。
-->

<!-- ✅ スクロールで画面に近づいたとき読み込まれる -->
<img
    src="photo.jpg"
    alt="写真"
    width="800"
    height="533"
    loading="lazy"
>

<!--
    注意点:
    ファーストビューにある重要な画像には
    loading="lazy" を付けない。

    → ヒーロー画像・ロゴ・最初に見える画像は
      loading="eager"(デフォルト)のままにする

    → スクロールしないと見えない画像だけに適用する
-->

<!-- ❌ NG:ヒーロー画像に lazy を付けると
   表示が遅くなり LCP スコアが悪化する -->
<img
    src="hero.jpg"
    alt="ヒーロー画像"
    loading="lazy"
>

<!-- ✅ OK:ヒーロー画像は fetchpriority="high" を付ける -->
<img
    src="hero.jpg"
    alt="ヒーロー画像"
    fetchpriority="high"
>

4.2 iframeの遅延読み込み

html

<!-- Google Maps・YouTube動画のiframeにも有効 -->

<!-- Google Maps -->
<iframe
    src="https://www.google.com/maps/embed?..."
    width="600"
    height="450"
    loading="lazy"
    allowfullscreen
    referrerpolicy="no-referrer-when-downgrade"
></iframe>

<!-- YouTube -->
<iframe
    src="https://www.youtube.com/embed/VIDEO_ID"
    width="560"
    height="315"
    loading="lazy"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
    allowfullscreen
></iframe>

4.3 JavaScriptでの遅延読み込み(IntersectionObserver)

javascript

/*
    IntersectionObserver を使って
    要素が画面に入ったタイミングで
    コンテンツを読み込む。
*/

// data-src に実際の画像URLを入れておく
// <img data-src="photo.jpg" alt="..." class="lazy-image">

const lazyImages = document.querySelectorAll( '.lazy-image' );

const imageObserver = new IntersectionObserver(
    ( entries ) => {
        entries.forEach( ( entry ) => {
            if ( !entry.isIntersecting ) return;

            const img = entry.target;
            img.src = img.dataset.src; // data-src を src にセット

            if ( img.dataset.srcset ) {
                img.srcset = img.dataset.srcset;
            }

            img.classList.remove( 'lazy-image' );
            imageObserver.unobserve( img ); // 監視を解除する
        } );
    },
    {
        rootMargin: '200px', // 画面から200px手前で読み込み開始
    }
);

lazyImages.forEach( ( img ) => imageObserver.observe( img ) );

5. CSSの読み込み最適化

5.1 CSSはheadで読み込む

html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ページタイトル</title>

    <!--
        ✅ CSSはheadの中で読み込む
        → レンダリングブロックになるが
          スタイルなしのHTMLが一瞬表示される
          FOUC(Flash of Unstyled Content)を防ぐ
    -->
    <link rel="stylesheet" href="css/style.css">

    <!--
        重要なCSS(ファーストビューのスタイル)は
        インラインで書くとさらに速くなる
    -->
    <style>
        /* ファーストビューに必要な最低限のCSS */
        body { margin: 0; font-family: sans-serif; }
        .header { height: 80px; background: #fff; }
        .hero { height: 100vh; }
    </style>
</head>

5.2 不要なCSSを削除する(PurgeCSS)

PurgeCSS:
実際に使われていないCSSを自動で削除するツール。
Tailwind CSSと組み合わせて使われることが多い。

Viteプロジェクトへの導入:
npm install -D @fullhuman/postcss-purgecss

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

→ 本番ビルド時に使われていないCSSを削除する
→ CSSのファイルサイズを大幅に削減できる
→ Tailwind CSSは特に効果が大きい

5.3 CSSのミニファイ

ミニファイ(Minify):

コードから不要なスペース・コメント・改行を削除して
ファイルサイズを小さくすること。

Viteは本番ビルドで自動的にミニファイしてくれる:
npm run build
→ dist/assets/ に圧縮されたCSSが生成される

手動でミニファイする場合:
→ cssnano を使う
→ オンラインツール(cssminifier.com など)を使う

一般的な削減率:
→ 10〜30%のファイルサイズ削減が見込める

6. JavaScriptの読み込み最適化

6.1 defer・asyncで読み込みを制御する

html

<!--
    scriptタグの読み込み方法の違い:

    通常(defer・asyncなし):
    → HTMLのパースを止めてJSを実行する
    → ページの表示が遅くなる
    → 現在はほぼ使わない

    defer:
    → HTMLのパースと並行してJSを取得する
    → HTMLのパースが完了してからJSを実行する
    → 実行順序が保証される
    → 基本的にこれを使う(bodyの閉じタグ直前と同等)

    async:
    → HTMLのパースと並行してJSを取得する
    → 取得完了したらすぐにJSを実行する
    → 実行順序は保証されない
    → 他のスクリプトに依存しないものに使う
-->

<head>
    <!-- ✅ defer:ほとんどのケースでこれを使う -->
    <script defer src="js/main.js"></script>

    <!-- ✅ async:解析ツール・広告など独立したスクリプトに -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=GA_ID"></script>

    <!-- ✅ type="module":ES Modulesを使うときはdeferが自動適用 -->
    <script type="module" src="js/main.js"></script>
</head>

<!-- ❌ 避けたい:bodyの閉じタグ直前に書く古い方法 -->
<!-- → deferを使えばheadに書いても同等の効果が得られる -->

6.2 コード分割(Code Splitting)

javascript

/*
    コード分割:
    JavaScriptを複数のファイルに分割して
    必要なときだけ読み込む手法。

    Viteは自動的にコード分割してくれる。
    動的importを使うとさらに細かく制御できる。
*/

// ❌ 初期読み込みに全部含まれる
import Chart from 'chart.js';
import { format } from 'date-fns';

// ✅ 必要になったときだけ読み込む(動的import)
// → チャートを表示するボタンをクリックしたときだけ読み込む
document.querySelector( '#show-chart' )?.addEventListener( 'click', async () => {
    const { default: Chart } = await import( 'chart.js' );
    const chart = new Chart( document.querySelector( '#chart' ), {
        /* 設定 */
    } );
} );

// モーダルを開いたときだけ読み込む
document.querySelector( '[data-modal]' )?.addEventListener( 'click', async () => {
    const { Modal } = await import( './components/Modal.js' );
    const modal = new Modal( '#contact-modal' );
    modal.show();
} );

6.3 不要なライブラリを減らす

javascript

/*
    ライブラリ全体をimportするのではなく
    必要な部分だけimportする。
    (ツリーシェイキング)
*/

// ❌ lodash全体をimport(70KB以上)
import _ from 'lodash';
const result = _.groupBy( data, 'category' );

// ✅ 必要な関数だけimport(数KB)
import groupBy from 'lodash/groupBy';
const result = groupBy( data, 'category' );

// ✅ さらに良い:標準APIで代替できないか検討する
// → groupBy は Object.groupBy() で代替できる場合がある

// ============================================

// date-fnsも同様
// ❌ 全体をimport
import * as dateFns from 'date-fns';

// ✅ 必要な関数だけimport
import { format, parseISO } from 'date-fns';

7. HTMLの最適化

7.1 preload・prefetchで先読みする

html

<head>
    <!--
        preload:
        このページで確実に使うリソースを優先して読み込む。
        → ヒーロー画像
        → 最初に表示されるフォント
        → 重要なスタイルシート
    -->
    <link
        rel="preload"
        href="fonts/NotoSansJP-Regular.woff2"
        as="font"
        type="font/woff2"
        crossorigin
    >
    <link
        rel="preload"
        href="images/hero.webp"
        as="image"
    >

    <!--
        prefetch:
        次のページで使いそうなリソースを
        アイドル時間に先読みしておく。
        → ナビゲーション先のページのCSS・JS
    -->
    <link rel="prefetch" href="/about/style.css">
    <link rel="prefetch" href="/about/main.js">

    <!--
        preconnect:
        外部ドメインへの接続を事前に確立する。
        → Google Fonts・CDNなど
    -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
</head>

7.2 width・heightを必ず指定してCLSを防ぐ

html

<!--
    CLS(Cumulative Layout Shift):
    画像が読み込まれるときにレイアウトがずれる問題。
    width・height を指定することで防げる。
-->

<!-- ❌ NG:サイズ未指定 → 画像読み込み時にレイアウトがずれる -->
<img src="photo.jpg" alt="写真">

<!-- ✅ OK:width・height を指定する -->
<!-- → ブラウザがスペースを確保してからレイアウトする -->
<img
    src="photo.jpg"
    alt="写真"
    width="800"
    height="533"
>

<!--
    CSSでmax-widthを使う場合でもwidth・heightは必要:
    → アスペクト比をブラウザが計算できるようになる
-->
<style>
img {
    max-width: 100%;
    height:    auto; /* アスペクト比を維持 */
}
</style>

8. フォントの最適化

8.1 Webフォントの最適化

html

<!-- Google Fonts の最適化された読み込み方 -->
<head>
    <!-- preconnect でDNSルックアップを事前に行う -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

    <!--
        display=swap を付ける:
        → フォントが読み込まれるまでシステムフォントで表示する
        → フォントが読み込まれ次第切り替わる
        → テキストが最初から表示されるのでUXが向上する
    -->
    <link
        href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap"
        rel="stylesheet"
    >
</head>
css

/* CSSでのフォント設定 */
body {
    /*
        フォールバックフォントを正しく設定する:
        → WebフォントとシステムフォントのCLSを最小化する
        → fallback・optional で調整できる
    */
    font-family: 'Noto Sans JP', 'Hiragino Kaku Gothic ProN',
                 'Hiragino Sans', Meiryo, sans-serif;
}

/* フォントの事前読み込み(セルフホスティング) */
@font-face {
    font-family: 'NotoSansJP';
    src:         url( '/fonts/NotoSansJP-Regular.woff2' ) format( 'woff2' );
    font-weight: 400;
    font-style:  normal;
    font-display: swap; /* ← これを必ず指定する */
}

9. キャッシュとサーバー設定

9.1 ブラウザキャッシュの設定

ブラウザキャッシュ:

一度読み込んだリソースをブラウザに保存して
2回目以降のアクセスで再利用する仕組み。

.htaccessでの設定(Apache):

# CSSとJSを1年間キャッシュ
<FilesMatch "\.(css|js)$">
    Header set Cache-Control "max-age=31536000, immutable"
</FilesMatch>

# 画像を1ヶ月キャッシュ
<FilesMatch "\.(jpg|jpeg|png|gif|webp|svg|ico)$">
    Header set Cache-Control "max-age=2592000"
</FilesMatch>

# HTMLはキャッシュしない(常に最新を取得)
<FilesMatch "\.html$">
    Header set Cache-Control "no-cache"
</FilesMatch>

→ Viteではビルド時にファイル名にハッシュが付く
  例:main-abc123.js
→ ファイルが変わればURLが変わるため
  長期キャッシュを安全に設定できる

9.2 gzip・Brotli圧縮

gzip・Brotli圧縮:

テキストファイル(HTML・CSS・JS)を
サーバーで圧縮して転送する仕組み。

効果:
→ CSS・JSのファイルサイズが60〜80%削減される
→ サーバー側の設定で対応できる

確認方法:
→ Chrome DevTools → Network タブ
→ レスポンスヘッダーの「Content-Encoding: gzip」を確認

主要なサーバー・ホスティングの対応:
→ Vercel・Netlify・CloudFlare:自動対応
→ さくらインターネット・ロリポップ:設定で対応可能
→ レンタルサーバーの管理画面を確認する

10. 実践チェックリスト

✓ Lighthouseでパフォーマンススコアを計測して現状を把握しているか

✓ ヒーロー画像などのファーストビューの画像をWebPに変換して圧縮しているか

✓ width と height 属性をすべての <img> タグに指定してCLSを防いでいるか

✓ ファーストビュー以外の画像に loading="lazy" を追加しているか

✓ ヒーロー画像に fetchpriority="high" を付けて優先読み込みしているか

✓ <script> タグに defer を付けてHTMLのパースをブロックしていないか

✓ Google Fontsに display=swap を指定してテキストが最初から表示されるようにしているか

✓ <link rel="preconnect"> で外部ドメインへの接続を事前確立しているか

✓ Viteなどのバンドラーを使って本番ビルドでCSSとJSをミニファイしているか


まとめ

今回はWebサイトのパフォーマンス改善の基本を解説しました。ポイントをまとめると:

  • 表示速度はユーザー体験・離脱率・SEOすべてに直結するためLighthouseとPageSpeed Insightsで定期的に計測する習慣をつける
  • 画像は最大のファイルサイズ削減のチャンスでWebPへの変換・適切なサイズ指定・Squooshでの圧縮の3セットを徹底する
  • すべての img タグに width と height を指定することでCLSを防ぎ・ファーストビュー外の画像には loading="lazy" を追加する
  • ヒーロー画像には fetchpriority="high" を付けてLCPスコアを改善する・loading="lazy" は付けない
  • <script> タグには defer を付けてHTMLのパースをブロックしないようにする
  • 動的import(import())でコードを分割して初期読み込みのJavaScriptを減らす
  • Google Fontsには display=swap を指定してテキストが最初から表示されるようにする
  • <link rel="preconnect"> と <link rel="preload"> で重要なリソースを先読みしてLCPを改善する

次の記事では、CSSアニメーションとトランジションの実装方法を解説します。transition と animation の使い分け・パフォーマンスを意識したGPUレンダリング・prefers-reduced-motion によるアクセシビリティ対応まで紹介します。お楽しみに!

コメント

タイトルとURLをコピーしました