2026年5月20日

2026年5月20日

WordPressのTotal Blocking Time(TBT)を改善する方法

はじめに

Total Blocking Time(TBT)はメインスレッドを50ms以上ブロックする「長いタスク」の合計時間を測定する指標です。TBTが高いとページの操作応答性が悪化し、INP(Interaction to Next Paint)やTTI(Time to Interactive)も悪化します。PageSpeed InsightsのLab計測に影響し、200ms以下が目標値です。

症状・原因

  • PageSpeed InsightsでTBTが赤色(600ms以上)になっている
  • ページ読み込み後しばらくボタンをクリックしても反応しない
  • JavaScriptの実行時間が長くメインスレッドが占有されている
  • サードパーティスクリプト(広告・チャット・分析)がブロッキングしている

解決手順

ステップ1:TBTの原因を特定する

# Chrome DevTools: Performance タブ
# 1. Record ボタンで録画開始
# 2. ページリロード → 5秒後に停止
# 3. Main スレッドで赤いタスク(50ms以上)を確認

# Long Tasks API でプログラム的に検出
// 長いタスクを監視(ブラウザコンソールで実行)
const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        console.warn('Long Task:', {
            duration: Math.round(entry.duration),
            startTime: Math.round(entry.startTime),
            attribution: entry.attribution
        });
    }
});
observer.observe({ entryTypes: ['longtask'] });
# WP-CLIで読み込まれているスクリプトを確認
wp eval "global \$wp_scripts; print_r(array_keys(\$wp_scripts->registered));" 2>/dev/null | head -50

# プラグインごとのスクリプト読み込みを確認
wp eval "
global \$wp_scripts;
foreach(\$wp_scripts->queue as \$handle) {
    \$src = \$wp_scripts->registered[\$handle]->src ?? '';
    echo \$handle . ': ' . \$src . PHP_EOL;
}
"

ステップ2:JavaScriptを遅延読み込みする

// functions.php: スクリプトに defer/async を追加

add_filter('script_loader_tag', function(string $tag, string $handle, string $src): string {
    // 管理画面は除外
    if (is_admin()) {
        return $tag;
    }

    // defer を追加するスクリプトのリスト
    $defer_scripts = [
        'contact-form-7',
        'jquery-form',
        'wp-embed',
        'comment-reply',
    ];

    // async を追加するスクリプトのリスト(順序依存なし)
    $async_scripts = [
        'google-analytics',
        'gtag',
    ];

    if (in_array($handle, $defer_scripts, true)) {
        return str_replace(' src=', ' defer src=', $tag);
    }

    if (in_array($handle, $async_scripts, true)) {
        return str_replace(' src=', ' async src=', $tag);
    }

    return $tag;
}, 10, 3);
// フロントページ以外では jQuery を遅延読み込み
add_action('wp_enqueue_scripts', function(): void {
    if (!is_front_page() && !is_home()) {
        wp_script_add_data('jquery-core', 'defer', true);
        wp_script_add_data('jquery', 'defer', true);
    }
});

ステップ3:不要なスクリプトを条件付きで読み込む

// functions.php: 不要なページでスクリプトを無効化

add_action('wp_enqueue_scripts', function(): void {
    // 投稿ページ以外ではコメント関連スクリプトを除去
    if (!is_singular() || !comments_open()) {
        wp_dequeue_script('comment-reply');
    }

    // ショップページ以外ではWooCommerceスクリプトを除去
    if (!is_woocommerce() && !is_cart() && !is_checkout()) {
        wp_dequeue_script('woocommerce');
        wp_dequeue_script('wc-cart-fragments');  // 特にこれが重い
        wp_dequeue_style('woocommerce-general');
    }

    // Contact Form 7: フォームのあるページだけで読み込む
    if (!has_shortcode(get_post()->post_content ?? '', 'contact-form-7')) {
        wp_dequeue_script('contact-form-7');
        wp_dequeue_style('contact-form-7');
    }
}, 99);

ステップ4:サードパーティスクリプトを遅延させる

// テーマの footer や functions.php 経由で出力
// インタラクション発生まで GTM・チャットウィジェットを遅延

(function() {
    let loaded = false;

    function loadHeavyScripts() {
        if (loaded) return;
        loaded = true;

        // Google Tag Manager を遅延読み込み
        (function(w,d,s,l,i){
            w[l]=w[l]||[];
            w[l].push({'gtm.start': new Date().getTime(), event:'gtm.js'});
            const f=d.getElementsByTagName(s)[0],
                  j=d.createElement(s);
            j.async=true;
            j.src='https://www.googletagmanager.com/gtm.js?id='+i;
            f.parentNode.insertBefore(j,f);
        })(window,document,'script','dataLayer','GTM-XXXXX');

        // チャットウィジェット
        window.intercomSettings = { app_id: 'YOUR_APP_ID' };
        const s = document.createElement('script');
        s.src = 'https://widget.intercom.io/widget/YOUR_APP_ID';
        document.head.appendChild(s);
    }

    // ユーザー操作で読み込む
    ['scroll', 'mousemove', 'touchstart', 'keydown'].forEach(function(e) {
        window.addEventListener(e, loadHeavyScripts, { once: true, passive: true });
    });

    // 5秒後には必ず読み込む
    setTimeout(loadHeavyScripts, 5000);
})();

ステップ5:長い処理をチャンク分割する

// 長いJavaScript処理をyieldで分割(INP改善)
// テーマのmain.jsやカスタムスクリプトに適用

async function processLargeArray(items) {
    const CHUNK_SIZE = 100;

    for (let i = 0; i < items.length; i += CHUNK_SIZE) {
        const chunk = items.slice(i, i + CHUNK_SIZE);

        // チャンクを処理
        chunk.forEach(item => processItem(item));

        // メインスレッドを解放(ブラウザが他の処理できるよう)
        await new Promise(resolve => setTimeout(resolve, 0));
    }
}

// scheduler.yield() が使える場合(Chrome 115+)
async function processWithYield(items) {
    for (const item of items) {
        processItem(item);

        // 定期的にyield
        if ('scheduler' in window && 'yield' in scheduler) {
            await scheduler.yield();
        }
    }
}

注意事項

  • defer はスクリプトの実行順序を保証しますが、async は保証しません。jQuery依存のスクリプトに async を付けると動作しなくなります
  • WooCommerceの wc-cart-fragments スクリプトは毎ページでAJAXリクエストを発生させ重いですが、カート機能に必要です。カートを使わないページのみ除去してください
  • サードパーティスクリプトの遅延は、GTMイベントのタイミングに影響する場合があります。コンバージョン計測を事前に確認してください

まとめ

TBT改善は①Chrome DevToolsのPerformanceタブで長いタスクを特定、②script_loader_tagフィルターで不要スクリプトにdefer/asyncを付与、③投稿ページ以外でWooCommerce・Contact Form 7スクリプトを除去、④サードパーティスクリプト(GTM・チャット)をユーザー操作まで遅延、⑤長いJS処理をchunkに分割してメインスレッドをyieldする、の順で対応します。

お気軽にご相談ください

お見積りへ お問い合わせへ