Webデザイン
投稿日:

サイトの軽量化を図る!─ サステナブルWebコーディングの実践ノウハウ

あなたのサイト、軽くできていますか?

こんにちは。株式会社ファストコーディングのWebディレクター、働くおかんです。最近はGoogle検索やBing検索からのみならず、ChatGPT等のAIで表示されるかどうかのAIO対策が注目されていますね。

AIO対策で語られるのは実際にはSEO対策と同様の構造化タグ追加や適切なマークアップ制作に寄りがちですが、実は表示速度も大事なことをご存じでしょうか?

またページ表示速度はAIOやSEO対策だけではなくて、Webサイトのユーザビリティにも直結し、最近では「サステナブルWeb」と呼ばれる分野でも重視されているお話です。そこで今回はページの表示速度を中心とした軽量化の重要性とその実践方法についてお、「サステナブルWebコーディング」としてお話しします。

なぜ表示速度と軽量化が大事なの?

ページの表示速度は、ユーザー体験そのものに直結します。表示が遅いと、ユーザーはそのサイトをすぐに離れがちです。これは「バウンス率」として計測できますが、この数値が高まるとSEOにも悪影響が及びます。速やかにサイトが表示されれば、ユーザーが情報を取得しやすくなり、信頼感を与えることにつながります。

Googleもページの読み込み速度をランキング要因の一つとしています。これにより、読み込みが速いサイトは検索結果で上位に表示されやすくなります。つまり、Webサイトの軽量化は、ユーザー体験とSEOの両面で欠かせない要素です。

サイトを軽くするためのテクニック

表示を速くして“体感の軽さ”を上げるには、フロントと配信の両面から手を打つのが近道です。ここでは、まず入れるべき定番の6テクニックをまとめてみました。


1) 画像の最適化と配信(WebP/AVIF+適切サイズ)

説明
画像はWebサイトの中でも最も重たいリソースです。フォーマットを WebP/AVIF に切り替えつつ、デバイスに合わせて“必要なサイズだけ”配信すると、読み込みが大きく短縮されます。

※動画が一番重い、と思うかもしれませんが、最近の動画は基本的にプログレッシブ配信(ちょっとずつ配信)になりますので、実は画像が一番重くなりがちです。

ポイント

  • 元画像を圧縮(画質は80前後から検討/写真はWebP/AVIF、ロゴ等はSVG形式がおすすめです)
  • srcset / sizes過不足のない解像度を配る(srcsetやsizesを使えば、閲覧者の画面の解像度に合わせたサイズの画像を配信できます)
  • <picture>フォールバック(pictureタグで囲うことで、万一AVIFが使えないブラウザでも、WebPを使う。それがだめでもJPEG/PNG、という風に、バグが出ないように実装することが可能です)

実装例

<picture>
  <source srcset="/img/hero.avif 1x, /img/hero@2x.avif 2x" type="image/avif">
  <source srcset="/img/hero.webp 1x, /img/hero@2x.webp 2x" type="image/webp">
  <img src="/img/hero.jpg"
       srcset="/img/hero_768.jpg 768w, /img/hero_1280.jpg 1280w, /img/hero_1600.jpg 1600w"
       sizes="(max-width: 768px) 92vw, (max-width: 1280px) 80vw, 1200px"
       width="1600" height="900" alt="ページのキービジュアル">
</picture>

2) 遅延読み込み(画像・iframe・下部コンポーネント)

説明
ファーストビューに不要なリソースは、見える瞬間まで読まないのが鉄則。初期ロードを最小化して体感速度を上げます。

ポイント

  • 画像に loading="lazy" / decoding="async"属性を追加する
  • iframeにも loading="lazy"を追加、可能ならサムネ+クリック再生で表示するようにする
  • 下部ウィジェットは IntersectionObserver で動的に生成する

実装例

<img src="/img/gallery-01.webp"
     alt="ギャラリー"
     width="1200" height="800"
     loading="lazy" decoding="async">

<!-- iframeもlazy -->
<iframe src="https://www.youtube.com/embed/xxxx"
        title="動画"
        loading="lazy"
        referrerpolicy="strict-origin-when-cross-origin"
        allowfullscreen></iframe>

<!-- IntersectionObserverで遅延対象:画像(幅・高さはCLS対策で指定) -->
<img class="js-lazy" data-src="/img/gallery-01.webp" alt="ギャラリー" width="1200" height="800" />
/* 遅延対象にわかりやすい初期スタイル(任意) */
.js-lazy, .js-widget { opacity: 0; transition: opacity .25s ease; }
.is-loaded { opacity: 1; }

// IntersectionObserver で“見えたら”ローディング
(function () {
  if (!('IntersectionObserver' in window)) {
    // 古いブラウザ向け(必要ならPolyfillを読み込む)
    // https://github.com/w3c/IntersectionObserver/tree/main/polyfill
    return;
  }

  const lazyTargets = document.querySelectorAll('.js-lazy, .js-widget');

  const onIntersect = (entries, observer) => {
    entries.forEach(entry => {
      if (!entry.isIntersecting) return;

      const el = entry.target;

      // 画像 or iframe(data-src を本来の src に差し替え)
      if (el.matches('.js-lazy')) {
        const src = el.getAttribute('data-src');
        if (src) el.setAttribute('src', src);
        el.addEventListener('load', () => el.classList.add('is-loaded'), { once: true });
      }

      // 任意ウィジェット(見えたら初期化スクリプトを読み込む)
      if (el.matches('.js-widget')) {
        const scriptUrl = el.getAttribute('data-script');
        const cfgText   = el.getAttribute('data-config') || '{}';

        if (scriptUrl) {
          const s = document.createElement('script');
          s.src = scriptUrl;
          s.async = true;
          s.onload = () => {
            try {
              const cfg = JSON.parse(cfgText);
              // グローバル初期化関数を想定(実装に合わせて変更)
              if (window.initReviewWidget) {
                window.initReviewWidget(el, cfg);
              }
              el.classList.add('is-loaded');
            } catch (e) { /* no-op */ }
          };
          document.head.appendChild(s);
        }
      }

      // 1度読み込んだら監視解除
      observer.unobserve(el);
    });
  };

  const io = new IntersectionObserver(onIntersect, {
    root: null,           // ビューポート
    rootMargin: '200px',  // 200px 手前で“先読み”開始
    threshold: 0          // 交差を検知したら実行
  });

  lazyTargets.forEach(t => io.observe(t));
})();


3) フォント最適化(サブセット化/preload/display)

説明
Webフォントは意外と重い上に、ブラウザが表示するときに「待たせる」原因にもなります。使うフォントを絞りこみ、先読みや表示設定を組み合わせて“待たせない”設定にします。

ポイント

  • 使用文字だけのサブセット化フォントを使う(日本語フォントは使用ウェイトも最小限に)
  • 初回に必要なファイルだけ<link rel="preload">で先読み
  • font-display: swap で何も見えない空白時間を回避

実装例

<link rel="preload" as="font" href="/fonts/NotoSansJP-subset.woff2" type="font/woff2" crossorigin>

<style>
@font-face {
  font-family: 'NotoSansJP';
  src: url('/fonts/NotoSansJP-subset.woff2') format('woff2');
  font-weight: 400; font-style: normal;
  font-display: swap; /* FOIT回避 */
}
body { font-family: system-ui, "NotoSansJP", sans-serif; }
</style>

4) JavaScriptの軽量化(defer/async・分割・未使用削除)

説明
JavaScriptの実行はHTMLやCSSの表示に加えてパソコンのパワーを使います。ファーストビューの表示に不要なJavaScriptを後回しにし、実行量を減らします。

ポイント

  • 初期描画に関係ないスクリプトは defer / async属性を追加
  • ページ別にファイルを分けて遅延よみこみ
  • サイト公開前にはビルドツール(Webpackなど)でツリーシェイキングで未使用コード除去
  • サードパーティタグは棚卸しして最小限に(マーケティングタグは実はとても重いです)

実装例

<script src="/assets/app.js" defer></script> <!-- HTML解析後に実行 -->
<script src="/assets/analytics.js" async></script> <!-- 依存なしは並行読み込み -->

<!--ページ別に遅延import(例:ES Modules) -->
<script type="module">
  if (location.pathname.startsWith('/dashboard')) {
    import('/assets/dashboard.js');
  }
</script>

5) CSSの軽量化(クリティカルCSS/未使用削除/設計の見直し)

説明
CSSはページを表示するための“基礎”ともいえます。ファーストビューに必要な最小限だけ先に適用し、残りは後から読み込みます。使っていない宣言も整理してサイズを削減しましょう。

ポイント

  • クリティカルCSSをインライン、残りは遅延ロードで読み込む
  • Purge/Content-awareツール(ビルドツールのプラグイン)で未使用クラス削除
  • BEM/Utilityなど一貫した設計で、そもそも重複したり、冗長にならないように制作をする

実装例

<!-- 1. クリティカルCSSのみインライン -->
<style>
/* above-the-fold の最小限 */
.header{display:flex;align-items:center;min-height:56px}
.hero{padding:8vw 0 4vw}
</style>

<!-- 2. 残りは遅延読み込み -->
<link rel="preload" as="style" href="/assets/app.css">
<link rel="stylesheet" href="/assets/app.css" media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/assets/app.css"></noscript>

6) 配信最適化(圧縮・HTTP/2/3・CDN・キャッシュ)

説明
同じファイルでも、どうやって「配るか」で速度は大きく変わります。サーバ側の圧縮と多重化、CDNキャッシュで往復回数とサイズを減らします。

ポイント

  • テキスト系に Brotli(fallbackでGzip)圧縮を利用(※サーバが対応している場合)
  • HTTP/2/HTTP/3で多重化(同時リクエストに強いです)
  • CDNサービスの活用で地域最寄りから配信、長期キャッシュ+ハッシュ名
  • 重要ドメインへのpreconnectでハンドシェイク短縮

実装例(.htaccess)

# Cache-Control(静的アセット)
Cache-Control: public, max-age=31536000, immutable

# HTMLは短め
Cache-Control: no-cache

# Nginx例:Brotli(サーバ設定)
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/javascript application/json image/svg+xml;

# HTTPヘッダでのヒント
Link: &lt;https://cdn.example.com>; rel=preconnect; crossorigin

補足:一気に全部やらなくてOKです。まずは画像/遅延読み込み/フォントから始め、次にJS/CSS/配信へ広げると効果も得やすく、また今のコードを大きく変えずに対応できます。

実際の事例:UIコンポーネントの賢い活用

弊社のとある案件では、上記の、CSSやJavaScriptの最適化をするために、効率的にUIライブラリを活用することに注力しました。たとえばBootstrapやVuetify、Material-UIといったUIコンポーネントライブラリを取り入れ、必要最低限のカスタマイズを加えることで、開発時間を削減しつつ、サイトの軽量化を実現しました。これにより、プロジェクト全体で最大30%の読み込み時間の削減に成功しました。

まとめ

Webサイトの軽量化は、ユーザー体験の向上とSEOの改善に直結します。次回のプロジェクトでは、今回ご紹介した画像の最適化やCSS設計の工夫をぜひ試してみてください。必要以上にリソースを使わない「サステナブルなサイト作り」が、今後ますます求められるでしょう。

株式会社ファストコーディングでは、サイトの軽量化や改善サポートを行っています。詳細については、こちらのフォームからお問い合わせください。次の一歩を踏み出し、顧客満足度の高いサイト制作を目指しましょう!