INP(Interaction to Next Paint)の数値を劇的改善!11個の施策をわかりやすく解説!

INP(Interaction to Next Paint)の数値を劇的改善!11個の施策をわかりやすく解説!

INP(Interaction to Next Paint)はユーザーがWebページ上でユーザーの操作(クリック・タップ・キー押下)を行ってから、その操作に反応する画面の「次の描画」が行われるまでの応答時間を測定する指標です。

このページではINPに対する11個の改善施策をわかりやすく解説します。

INPの仕組みや内容を把握したい方は「INPとは」の記事をご覧ください。

INPの数値を低下させる主な要因

INPについては、入力遅延(Input Delay)、処理時間(Processing Time)、表示遅延(Presentation Delay)の3つフェーズがあります。

それぞれのフェーズごとに要因をまとめました。

分類

要因

説明

影響度

Input Delay(入力遅延)

ロングタスク実行中の操作

50ms以上のタスク実行中にユーザーが操作を行う

極大

メインスレッドのブロック

JavaScriptの同期処理でメインスレッドが占有される

極大

サードパーティスクリプト

外部ライブラリ・広告・アナリティクスの重い処理

大

Processing Time(処理時間)

重いDOM操作

大量要素の一括生成・削除・変更

極大

複雑な計算処理

ループ処理・数値計算・データ変換

大

同期的なAPIリクエスト

fetch・XMLHttpRequestの同期実行

大

大量のスタイル変更

CSSプロパティの連続的な変更

中

メモリ不足

ガベージコレクションの頻発

中

Presentation Delay(表示遅延)

巨大なDOMサイズ

数千個以上のDOM要素

極大

強制同期レイアウト

スタイル変更後の即座の寸法取得

極大

複雑なCSSセレクター

深いネスト・複雑な疑似クラス

大

大量の画像・メディア

未最適化の大容量ファイル

大

レイアウトスラッシング

読み書きの繰り返しによる連続レイアウト

大

これらを踏まえて、下記の改善策をご覧頂けたらと思います。

INPを改善させる11個の方法

ここからは入力遅延(Input Delay)、処理時間(Processing Time)、表示遅延(Presentation Delay)の3つフェーズ別にに改善方法を解説していきます。

入力遅延(Input Delay)部分の改善

入力遅延(Input Delay)については、メインスレッドが他の処理で忙しく、ユーザー操作への対応が後回しになるケースで遅延が起きる場合があります。

入力遅延で改善できる方法は、主にタスクの分割が重要となります。

改善の方法については、主に「setTimeout()」や「requestIdleCallback」、「scheduler.postTask()」などと言った方法があります。

1.setTimeoutによるタスク分割

setTimeout()は、INPの入力遅延改善において最も基本的で広く使われているタスク分割手法です。

この手法は長時間実行されるJavaScriptタスクを小さな単位に分割し、メインスレッドに制御を譲ることでユーザーの操作に対する応答性を向上させます。

ユーザーがクリックやキー入力を行った際、重い処理が実行されているとメインスレッドがブロックされ、操作への応答が遅れてしまいます。

setTimeout()を setTimeout(callback, 0) の形で使用することで、現在実行中の長いタスクを一時的に中断し、ブラウザのメインスレッドに制御を譲ることができます。

基本的なタスク分割の方法

// 改善前:長いタスク(ロングタスク化)
function processLargeArray(items) {
    for (let i = 0; i < items.length; i++) {
        heavyOperation(items[i]); // 各処理に5ms要する場合
        // 1000個の要素で5000ms = 5秒のロングタスク
    }
    console.log('処理完了');
}

// 改善後:setTimeoutによる分割
function processLargeArrayOptimized(items) {
    let currentIndex = 0;
    const batchSize = 10; // 一度に処理する件数

    function processBatch() {
        const endIndex = Math.min(currentIndex + batchSize, items.length);
        
        // 一部の要素を処理
        for (let i = currentIndex; i < endIndex; i++) {
            heavyOperation(items[i]);
        }
        
        currentIndex = endIndex;
        
        // まだ処理すべき要素があるかチェック
        if (currentIndex < items.length) {
            // メインスレッドに譲り、次のバッチを処理
            setTimeout(processBatch, 0);
        } else {
            console.log('処理完了');
        }
    }
    
    processBatch(); // 最初のバッチを開始
}

改善前は、1000個の配列要素を一つのforループ内で連続処理しており、各要素の処理に5msかかる場合、合計5000ms(5秒)のロングタスクとなってしまいます。

この間、メインスレッドが完全にブロックされ、ユーザーの操作に一切応答できません。

改善後のコードは、バッチ処理とsetTimeoutを組み合わせた分割処理を実装しています。

10個ずつの要素を処理した後、setTimeout(processBatch, 0) でメインスレッドに制御を譲り、次のバッチ処理を別のタスクとして実行します。

これにより、5秒間のロングタスクが50ms程度の小さなタスクに分割され、その合間にユーザー操作や画面描画が適切に処理されるようになります。結果として、INPの大幅な改善が実現されます。

2. requestIdleCallback()による空き時間の活用

requestIdleCallback()は、ブラウザがアイドル状態(空き時間)になった時に実行される関数をキューに登録するためのAPIです。

このAPIを使うことで、アニメーションやユーザー操作などの重要なタスクに影響を与えることなく、優先度の低いバックグラウンド処理をメインスレッドで実行できます。

基本的には下記のようなソースで記述します。

// 基本的な使用例
function myNonEssentialWork(deadline) {
    while (deadline.timeRemaining() > 0) {
        doWorkIfNeeded();
    }
}

requestIdleCallback(myNonEssentialWork);

大量のタスクを効率的に処理する方法や、重要度に応じてタイムアウトの設定も可能です。

3. scheduler.postTask()で優先度を指定する

scheduler.postTask()は、Prioritized Task Scheduling API(優先度付きタスクスケジューリングAPI)の一部で、タスクの優先度を指定してブラウザのスケジューラーに送信できる実験的なAPIです。

これにより、重要なタスクが低優先度のタスクよりも先に実行されることを保証し、ユーザー体験の向上が可能になります。

基本的には下記のようなソースとなります。

// 最高優先度のタスク(ユーザー操作に関連)
scheduler.postTask(() => {
    updateUserInterface();
}, { priority: 'user-blocking' });

// 中程度優先度のタスク(ユーザーに見える内容)
scheduler.postTask(() => {
    loadMainContent();
}, { priority: 'user-visible' });

// 低優先度のタスク(バックグラウンド処理)
scheduler.postTask(() => {
    sendAnalyticsData();
}, { priority: 'background' });

優先度レベルについては、下記の表を参考にしてみてください。

scheduler.postTask()の優先度レベル一覧

優先度

優先度レベル

説明

具体的な使用例

user-blocking

最高優先度

ユーザーの操作を妨げる可能性があるタスク
即座に実行が必要な処理

・クリック・タップの応答
・入力フィールドのフィードバック
・スクロール応答
・緊急アラート表示

user-visible

中程度優先度
(デフォルト)

ユーザーに見える結果を生成するタスク
UI更新や画面描画に関する処理

・画面コンテンツの描画
・データリストの表示更新
・アニメーション
・画像の読み込み

background

最低優先度

バックグラウンドで実行される重要度の低いタスク
遅延しても問題ない処理

・アナリティクスデータ送信
・ログの記録
・データの前処理・キャッシュ
・プリフェッチ

処理時間(Processing Time)部分の改善

INP(Interaction to Next Paint)の処理時間改善において重要なポイントを詳しく解説いたします。

主に処理時間ではJavaScriptの最適化やデバウンス・スロットリング、重要度に応じたUI更新の分離などが有効です。

下記で詳しく解説していきます。

4.JavaScriptの計算や実行を最適化させる

ここでいうJavaScriptの圧縮・最小化というのは、JavaScriptの計算量を減らすことを指します。

大きなポイントとして下記があります。

長時間タスクの分割

JavaScriptの処理時間が長い場合、そのタスクを分割することで入力遅延を軽減できます。

タスクの分割は、長時間実行される処理をメインスレッドブロッキング回避のため小さなチャンクに分ける手法です。

ソース例

Copy// 改善前:長時間の処理でブラウザをブロック
function heavyTask() {
    for (let i = 0; i < 1000000; i++) {
        // 重い処理
    }
}

// 改善後:処理を分割
function optimizedTask() {
    let i = 0;
    function chunk() {
        for (let j = 0; j < 1000 && i < 1000000; j++, i++) {
            // 重い処理
        }
        if (i < 1000000) {
            setTimeout(chunk, 0);
        }
    }
    chunk();
}

入力遅延(Input Delay)で説明したタスク分割と原理は同じで、setTimeoutやrequestIdleCallbackで次のフレームに処理を移譲します。

これによりUIの応答性を維持しながら重い処理が実行可能になります。

これでユーザーインタラクションが遮断されず、INPの改善に直結します。

Web Workerによる処理の分離

Web Workersは、メインUIスレッドとは別のバックグラウンドスレッドで重い処理を実行する技術です。

CPUを集約する計算処理、大量データの変換、複雑なアルゴリズム実行をメインスレッドから分離し、UIの応答性を完全に保持できます。

ソース例

// メインスレッドから重い処理を分離
const worker = new Worker('heavy-task-worker.js');
worker.postMessage({data: largeDataSet});
worker.onmessage = function(event) {
  // 処理結果を受け取り、UIを更新
  updateUI(event.data);
};

new Worker('script.js')でワーカーを作成し、postMessage()でデータ送信、onmessageで結果受信をする形で対応できます。

得に画像処理、データソート、暗号化処理、リアルタイム分析などに有効的な方法で、処理完了時のみメインスレッドに結果を返すため、INP改善に極めて効果的です。

不要なJavaScriptの削除

JavaScript削除による改善で、無駄な計算処理がなくなりメインスレッドが軽くなります。

特に未使用コードの除去、動的ロード、軽量な代替ライブラリなどの改善により、メインスレッドの負荷を大幅に軽減し処理速度も改善されます。

JavaScriptの最適化について詳しくは「JavaScriptの速度改善方法」もご覧ください。

5.デバウンス・スロットリングの実装

デバウンス・スロットリングは、頻繁に発生するイベントを制御してパフォーマンスを向上させる手法です。

特に早すぎる連続するアクション(検索の文字入力やクリックなど)に対して、何秒後に反映させるかの時間を指定してあげることで無駄な処理が発生することを防ぐ方法です。

動作例として、検索ボックスで「りんご」と入力する時、「り」「りん」「りんご」と1文字ずつ検索されるのではなく、入力が終わってから1回だけ検索される仕組みとなります。

基本ソース例

function debounce(func, delay) {
  let timeoutId;
  
  return function(...args) {
    // 前のタイマーをクリア
    clearTimeout(timeoutId);
    
    // 新しいタイマーを設定
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 使用例:検索入力フィールド
const searchInput = document.getElementById('search');
const debouncedSearch = debounce(performSearch, 300);

searchInput.addEventListener('input', debouncedSearch);

function performSearch(event) {
  const query = event.target.value;
  // API呼び出しや検索処理
  console.log('検索実行:', query);
}

ここでは特に、何秒後に反映させるかといった指示が大事なポイントとなります。

ユーザーの行動や動作を考えて最適な時間を設定するといいでしょう。

6.重要度に応じたUI更新の分離

重要度に応じたUI更新の分離とは、ユーザーインタラクションに対する応答を優先度別に階層化し、緊急度の高い更新を最初に実行する手法です。

すべてのUI更新を同時に行うのではなく、「即座に反応すべき部分」「少し遅れても良い部分」「バックグラウンドで処理する部分」に分類します。

こうすることで、体感的な応答性が大幅に向上しINP改善にも繋がります。

参考例ですが、下記のようなソースで対応します。

// 優先度の定義
const UI_PRIORITY = {
  IMMEDIATE: 'immediate',    // 16ms以内(1フレーム)
  HIGH: 'high',             // 100ms以内
  LOW: 'low',               // 500ms以内
  BACKGROUND: 'background'   // アイドル時間
};

class PriorityBasedUIUpdater {
  constructor() {
    this.updateQueue = new Map();
    this.setupPriorityQueues();
  }
  
  setupPriorityQueues() {
    // 各優先度のキューを初期化
    Object.values(UI_PRIORITY).forEach(priority => {
      this.updateQueue.set(priority, []);
    });
  }
  
  scheduleUpdate(updateFunction, priority = UI_PRIORITY.HIGH, context = {}) {
    this.updateQueue.get(priority).push({
      update: updateFunction,
      context: context,
      timestamp: performance.now()
    });
    
    this.processUpdates(priority);
  }
  
  processUpdates(priority) {
    switch (priority) {
      case UI_PRIORITY.IMMEDIATE:
        this.processImmediateUpdates();
        break;
      case UI_PRIORITY.HIGH:
        this.processHighPriorityUpdates();
        break;
      case UI_PRIORITY.LOW:
        this.processLowPriorityUpdates();
        break;
      case UI_PRIORITY.BACKGROUND:
        this.processBackgroundUpdates();
        break;
    }
  }
}

UI_PRIORITYオブジェクトで4つの優先度レベルを定義し、PriorityBasedUIUpdaterクラスでキュー管理を行います。

このようにUI更新の優先度別に調整を行うことでユーザー体験も改善されます。

表示遅延(Presentation Delay)部分の改善

INP(Interaction to Next Paint)の表示遅延の改善において重要なポイントを詳しく解説いたします。

表示遅延(Presentation Delay)の改善では、特にDOMサイズの最適化、サーバー応答時間の最適化などの改善を進めていきます。

下記で改善方法を説明していきます。

7.DOMサイズの最適化

ブラウザは画面描画時にDOM全体のレイアウト計算と描画処理を行うため、DOM要素数が多いほど処理時間が長くなります。

例えば数千個の要素を持つページでは、ユーザー操作後の再描画に数十ミリ秒以上要することがあります。

改善策として、仮想スクロールによる表示範囲の限定、不要な要素の遅延読み込み、ネストの深い構造の平坦化、CSS content-visibilityによる画面外要素のレンダリングスキップなどが効果的です。

仮想スクロールによる表示範囲の限定

仮想スクロールは、大量のリストデータを効率的に表示するための手法で、画面に見える範囲の要素のみをDOMに存在させます。

例えば10,000件のデータがあっても、実際にDOMに生成されるのは画面表示可能な20-30個程度の要素のみとなるような形です。

これにより、DOM要素数を95%以上削減でき、メモリ使用量とレンダリング時間を大幅に短縮できます。

実装例

class VirtualScrollList {
    constructor(container, items, itemHeight = 50) {
        this.container = container;
        this.items = items;
        this.itemHeight = itemHeight;
        this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2;
        this.startIndex = 0;
        
        this.render();
        this.setupScrollListener();
    }
    
    render() {
        const fragment = document.createDocumentFragment();
        const endIndex = Math.min(this.startIndex + this.visibleCount, this.items.length);
        
        // 表示範囲の要素のみ作成
        for (let i = this.startIndex; i < endIndex; i++) {
            const item = document.createElement('div');
            item.className = 'list-item';
            item.style.height = `${this.itemHeight}px`;
            item.textContent = this.items[i].text;
            fragment.appendChild(item);
        }
        
        this.container.innerHTML = '';
        this.container.appendChild(fragment);
    }
    
    setupScrollListener() {
        this.container.addEventListener('scroll', () => {
            const newStartIndex = Math.floor(this.container.scrollTop / this.itemHeight);
            if (newStartIndex !== this.startIndex) {
                this.startIndex = newStartIndex;
                this.render();
            }
        });
    }
}

// 使用例: 10,000件のデータを20件程度のDOM要素で表示
const virtualList = new VirtualScrollList(
    document.getElementById('list-container'),
    largeDataArray, // 10,000件のデータ
    60 // 各アイテムの高さ
);

不要な要素の遅延読み込み

不要な要素の遅延読み込みは、初期表示時に必要でないコンテンツを後から動的に読み込む最適化手法です。

例えばコメント欄、関連記事、ユーザープロフィールなどの二次的要素を最初はプレースホルダーとして表示し、ユーザーの操作やスクロールに応じて実際のコンテンツに置換します。

Intersection Observer APIを活用して、要素が画面に入るタイミングで読み込みを開始することで、初期DOM要素数を60-80%削減できます。

これにより初期レンダリング時間が短縮され、ページの応答性が大幅に向上します。

実装例

class LazyContentLoader {
    constructor() {
        this.observer = new IntersectionObserver(this.handleIntersection.bind(this));
        this.setupInitialContent();
    }
    
    setupInitialContent() {
        // 初期DOM構造を最小限に
        const placeholders = document.querySelectorAll('[data-lazy-content]');
        placeholders.forEach(placeholder => {
            this.observer.observe(placeholder);
        });
    }
    
    handleIntersection(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                this.loadContent(entry.target);
                this.observer.unobserve(entry.target);
            }
        });
    }
    
    async loadContent(element) {
        const contentType = element.dataset.lazyContent;
        
        // プレースホルダーを実際のコンテンツに置換
        setTimeout(() => {
            switch(contentType) {
                case 'comments':
                    this.loadComments(element);
                    break;
                case 'related-articles':
                    this.loadRelatedArticles(element);
                    break;
                case 'user-profile':
                    this.loadUserProfile(element);
                    break;
            }
        }, 0);
    }
    
    loadComments(container) {
        const commentsHTML = `
            <div class="comments-section">
                <h3>コメント</h3>
                <div class="comment-list">
                    <!-- コメントコンテンツ -->
                </div>
            </div>
        `;
        container.innerHTML = commentsHTML;
    }
}

// HTML構造
/*
<div class="main-content">
    <article>メイン記事</article>
    
    <!-- 遅延読み込み対象 -->
    <div data-lazy-content="comments" class="lazy-placeholder">
        コメント読み込み中...
    </div>
    
    <div data-lazy-content="related-articles" class="lazy-placeholder">
        関連記事読み込み中...
    </div>
</div>
*/

const lazyLoader = new LazyContentLoader();

ネストの深い構造の平坦化

ネストの深い構造の平坦化は、複雑に入れ子になったHTML構造を浅い階層に変更する最適化手法です。

例えば7-8階層の深いネスト構造を2-3階層に削減することで、ブラウザのレイアウト計算負荷を大幅に軽減できます。

CSS GridやFlexboxを活用して、従来divの多重入れ子で実現していたレイアウトをシンプルな構造で表現します。

これによりDOM階層が削減でき、スタイル計算時間とレンダリング時間が短縮されます。

実装例

<!-- 改善前:深いネスト構造(7階層) -->
<div class="container">
    <div class="wrapper">
        <div class="content-area">
            <div class="section">
                <div class="subsection">
                    <div class="item-group">
                        <div class="item">
                            <span class="text">コンテンツ</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- 改善後:平坦な構造(3階層) -->
<div class="flat-container">
    <div class="flat-item" data-section="main">
        <span class="flat-text">コンテンツ</span>
    </div>
</div>

上記のようにDOMの階層が深いHTMLの記述がある場合は、下記のようなCSSの記述で階層を浅くする工夫をすると良いでしょう。

/* 平坦化されたCSS */
.flat-container {
    display: grid;
    gap: 16px;
}

.flat-item {
    padding: 12px;
    border: 1px solid #ddd;
}

.flat-item[data-section="main"] {
    grid-column: span 2;
}

CSS content-visibilityによる画面外要素のレンダリングスキップ

CSS content-visibilityは、画面外の要素に対するブラウザの描画処理を自動的にスキップさせるプロパティです。

content-visibility: autoを指定した要素は、ビューポート外にある間はレイアウト計算や描画がスキップされ、画面に入るタイミングで初めてレンダリングされます。

contain-intrinsic-sizeと組み合わせることでレイアウトシフトも防止できます。

大きなコンテンツセクションやリストアイテムに適用することで、レンダリング処理を削減し、初期表示とスクロール時の両方でパフォーマンスが向上します。

実装例

/* 画面外の要素のレンダリングをスキップ */
.off-screen-section {
    content-visibility: auto;
    contain-intrinsic-size: 300px; /* レイアウトシフト防止 */
}

/* 大きなリストのアイテム */
.large-list-item {
    content-visibility: auto;
    contain-intrinsic-size: 200px 100px;
}

8.強制同期レイアウトの回避

強制同期レイアウトは、JavaScriptでスタイルを変更した直後に要素の寸法や位置を読み取ることで発生する、ブラウザの同期的なレイアウト計算です。

通常、ブラウザはレイアウト処理を非同期で最適なタイミングに実行しますが、offsetWidthやgetBoundingClientRect()などのプロパティにアクセスすると、最新の値を返すために即座にレイアウト計算が強制実行されます。

これを回避するには「読み取り→書き込み」の順序を厳守し、複数要素の操作では読み取り処理をまとめて行った後に、書き込み処理を一括実行します。

またrequestAnimationFrame内でDOM操作を行うことで、ブラウザの最適なタイミングでレンダリングできます。

実装例

// 問題のあるパターン:読み書きの混在
function updateElements() {
    for (let i = 0; i < elements.length; i++) {
        // 書き込み→読み取りで強制レイアウト発生
        elements[i].style.width = '100px';
        console.log(elements[i].offsetWidth); // 強制レイアウト!
    }
}

// 改善パターン:読み取りと書き込みの分離
function updateElementsOptimized() {
    // 1. すべて読み取り
    const widths = elements.map(el => el.offsetWidth);
    
    // 2. すべて書き込み
    elements.forEach((el, i) => {
        el.style.width = `${widths[i] + 10}px`;
    });
}

9.CSSの最適化

ブラウザはCSS解析、スタイル計算、レイアウト、描画の順で処理を行うため、各段階での最適化が必要です。

主に複雑なセレクターの最適化や、不要なCSSの削減などが効果的です。

CSSセレクターの最適化

CSSセレクターは、深いネスト(3階層以下に制限)や複雑な疑似クラス(:nth-child等)を避け、シンプルなクラスセレクターを優先使用するようにします。

IDセレクターが最高速で、汎用セレクター(*)やタグセレクターは低速なため避けるべきです。

セレクター優先度による最適化例

/* IDセレクター(最高速) */
#header { color: blue; }

/* クラスセレクター(高速) */
.navigation { display: flex; }
.btn-primary { background: blue; }

/* 属性セレクター(中速) */
[data-type="button"] { cursor: pointer; }

セレクターの深さの最適化例

/* 直接的なクラス名 */
.content-text {
    color: black;
}

/* 明確な状態クラス */
.menu-link-active {
    text-decoration: underline;
}

/* 単純なクラス */
.list-item-alternate {
    background: #f5f5f5;
}

不要なCSSの削除とCSSの分割

ブラウザはページ読み込み時にすべてのCSSルールを解析してスタイルツリーを構築するため、未使用のCSSが多いほど処理時間が増大します。

また重複するルールの統合、ベンダープレフィックスの最適化、古いブラウザ対応コードの削除も効果的です。

さらにCSSファイルの分割により、ページ固有スタイルのみを読み込むことで初期解析負荷を軽減できます。

これらの改善により、CSSファイルサイズ削減し、スタイル計算時間を短縮できます。

削除前のソース例(未使用ルールあり)

/* 削除対象:使用されていないスタイル */
.old-header { 
    background: #333; 
    height: 80px;
}

.legacy-sidebar { 
    width: 200px; 
    float: left;
}

.unused-modal { 
    display: none;
    z-index: 9999;
}

.deprecated-button {
    background: linear-gradient(#fff, #ccc);
    border-radius: 3px;
}

/* 保持:実際に使用されているスタイル */
.current-header { 
    background: #2c3e50; 
    height: 60px;
}

.main-content { 
    margin: 20px;
}

削除後のソース例(使用中のスタイルのみ)

/* 実際に使用されているスタイルのみ */
.current-header { 
    background: #2c3e50; 
    height: 60px;
}

.main-content { 
    margin: 20px;
}

/* 削除により:4つのルール → 2つのルール(50%削減) */

重複ルールの統合の例(削除前)

/* 重複するスタイル */
.button-primary {
    padding: 10px 15px;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
}

.button-secondary {
    padding: 10px 15px;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
    background: #6c757d;
}

.button-danger {
    padding: 10px 15px;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
    background: #dc3545;
}

重複ルールの統合の例(削除後)

/* 共通スタイルを基底クラスに */
.button-base {
    padding: 10px 15px;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
}

/* 個別スタイルのみ */
.button-secondary { background: #6c757d; }
.button-danger { background: #dc3545; }

10.サーバー応答時間の最適化

サーバー応答時間の改善は主にTTFBの要素となりますが、主にここでは

表示遅延(Presentation Delay)部分にフォーカスし、主にCDNの活用やHTTP/2やHTTP/3を活用した改善方法を紹介します。

TTFBについて知りたいという方は「TTFBとは」の記事をご覧下さい。

CDNとキャッシュの活用

CDN(Content Delivery Network)は、ユーザーに近いサーバーからコンテンツを配信し、読み込み時間を短縮できる仕組みです。

CDNを導入することで、アクセスした場所から近いCDNサーバーに接続ができるのでサーバーの応答時間も速くなります。

下記はCDNを導入した場合のイメージです。

主にCDNが導入できるサービスは下記があります。

  • Cloudflare(無料プランから始められておすすめ)
  • AWS CloudFront(AWS利用者から人気)
  • さくらのCDN(日本のサービス)

各サービスでアカウントを作成し、ドメインやDNSの設定を行うことで設定ができます。

また、キャッシュ設定も管理画面上で設定ができるので、キャッシュ設定もしておくことをお勧めします。

HTTP/2またはHTTP/3の使用

HTTP/2およびHTTP/3は、従来のHTTP/1.1よりも通信効率が大幅に改善され高速化されます。

HTTP/1.1は1回のリクエストに対し1回のレスポンスで処理するしかありませんが、HTTP/2およびHTTP/3は1つの接続で複数リクエストを複数のレスポンスで処理できるため通信効率が格段に上がります。

TTFBの更に詳しい改善方法については「TTFBの改善方法」で詳しく説明しています。

HTTP/3やHTTP/2について詳しくは「HTTP/3とは?HTTP/2の違いや特徴を比較」の記事でも詳しく説明しています。

11.画像とメディアの最適化

大容量の画像や動画は、レンダリングの過程でブラウザに大きな負荷をかけ、描画時間を延長させます。

特に下記の部分に最適化をかけることでINPの改善に繋がります。

画像フォーマットとサイズの削減

WebPやAVIFなど次世代フォーマットの採用でファイルサイズを削減し、適切な画像サイズの指定(width・height属性)でレイアウトシフトを防止できます。

また、レスポンシブ画像のsrcset属性を活用し、デバイスの画面サイズや解像度に応じた最適な画像を配信することで、処理負荷を大幅に軽減できます。

それ以外にもpicture要素とsource要素を組み合わせることで、ブラウザサポートに応じたフォーマットの自動選択も実現できます。

ポイントをまとめると

  • WebPやAVIFなど次世代フォーマットを採用する
  • レスポンシブ画像のsrcset属性を活用
  • picture要素とsource要素でブラウザに合わせたフォーマットを選択

これらの対策で画像の処理も軽減できるため是非実施してみてください。

次世代フォーマットについて詳しくは「次世代フォーマット画像とは?」をご覧ください。

遅延読み込みの実装

画像の遅延読み込みにより初期描画負荷を軽減し、表示速度を向上させることができます。

導入は簡単でloading="lazy"の属性を追加することで実装が可能です。

実装例

<img src="image.jpg" loading="lazy" alt="説明">

動画ファイルも同様に遅延読み込みが可能で、自動再生による初期負荷を防げます。

INP改善の11個の施策まとめ

Webサイトのパフォーマンス向上において、INP(Interaction to Next Paint)の改善は欠かせない要素です。

今回ご紹介した11個の施策を実践することで、ユーザー操作に反応するまでの応答時間を大幅に向上させることができます。

これらの改善により、検索エンジンからの評価向上はもちろん、ユーザーの満足度向上、コンバージョン率の改善にもつながることでしょう。

ぜひ今日から実践してみてください。

記事を書いた人

井上寛生

井上寛生

LandingHub 執行役員 / 事業責任者 / 技術責任者

大学院では情報工学を専攻し、修了後に株式会社TeNへ新卒入社。当時は社内唯一のエンジニアながら、開発部門をゼロから立ち上げ、採用・育成を一手に担い、全員が未経験からスタートした精鋭エンジニアチームを組成。2021 年にはWEBサイト高速化プラットフォーム「LandingHub」を立ち上げ、プロダクトオーナー兼事業責任者として企画・開発・グロースを牽引。現在は執行役員として、会社の技術戦略と事業成長の双方をリードしている。
コラム一覧に戻る