2026年8月21日

2026年8月21日

WordPressのCore Web Vitalsを改善する方法【LCP・CLS・INP】

はじめに

Google検索ランキングの要因となるCore Web Vitals(CWV)はLCP・CLS・INPの3指標です。WordPressで低スコアが出やすい原因と改善方法を具体的なコードで解説します。

症状・原因

Core Web Vitalsが低下する主な原因:

  • LCP(最大コンテンツ描画):アイキャッチ画像のlazy load・フォントブロッキング
  • CLS(累積レイアウトシフト):画像サイズ未指定・遅延読み込み広告
  • INP(操作から次の描画まで):重いJavaScript・非最適化イベントハンドラー

解決手順

ステップ1:LCP(最大コンテンツ描画)を改善する

// functions.php

// ファーストビューのアイキャッチ画像にlazy loadを無効化(LCP改善)
add_filter('wp_get_attachment_image_attributes', 'optimize_hero_image_attrs', 10, 3);
function optimize_hero_image_attrs(array $attrs, WP_Post $attachment, $size): array {
    // アーカイブページや投稿ページのアイキャッチ(最初の1枚)
    static $hero_loaded = false;
    
    if (!$hero_loaded && is_singular() && in_the_loop()) {
        // LCPとなるヒーロー画像はeager + fetchpriorityをhighに
        $attrs['loading']       = 'eager';
        $attrs['fetchpriority'] = 'high';
        $attrs['decoding']      = 'sync';
        $hero_loaded = true;
    }
    
    return $attrs;
}

// LCP画像を<head>でpreloadする
add_action('wp_head', 'preload_hero_image', 1);
function preload_hero_image(): void {
    if (!is_singular()) return;
    
    $post_id = get_the_ID();
    if (!has_post_thumbnail($post_id)) return;
    
    $thumb_id  = get_post_thumbnail_id($post_id);
    $image_src = wp_get_attachment_image_srcset($thumb_id, 'large');
    $thumb_url = get_the_post_thumbnail_url($post_id, 'large');
    
    if ($thumb_url) {
        $srcset = $image_src ? ' imagesrcset="' . esc_attr($image_src) . '"' : '';
        echo '<link rel="preload" as="image" href="' . esc_url($thumb_url) . '"'
             . $srcset . ' fetchpriority="high">' . PHP_EOL;
    }
}

// Google Fontsをpreloadして読み込みを最適化
add_action('wp_head', 'preconnect_google_fonts', 1);
function preconnect_google_fonts(): void {
    // フォントの取得元にpreconnectする
    echo '<link rel="preconnect" href="https://fonts.googleapis.com">' . PHP_EOL;
    echo '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>' . PHP_EOL;
}

ステップ2:CLS(累積レイアウトシフト)を防ぐ

// 画像タグに必ずwidth/heightを追加してCLSを防ぐ
add_filter('the_content', 'add_missing_image_dimensions');
function add_missing_image_dimensions(string $content): string {
    // width/heightがない<img>タグを検索
    return preg_replace_callback(
        '/<img([^>]*?)(?<!width=")(?<!height=")>/i',
        function($matches) {
            $img_tag = $matches[0];
            
            // すでにwidth/heightがある場合はスキップ
            if (preg_match('/width\s*=/i', $img_tag) || preg_match('/height\s*=/i', $img_tag)) {
                return $img_tag;
            }
            
            // srcからファイル情報を取得
            if (preg_match('/src=["\']([^"\']+)["\']/', $img_tag, $src_match)) {
                $src  = $src_match[1];
                $size = @getimagesize($src);
                if ($size) {
                    $img_tag = str_replace('>', " width=\"{$size[0]}\" height=\"{$size[1]}\">", $img_tag);
                }
            }
            
            return $img_tag;
        },
        $content
    );
}

// アイキャッチ画像に必ずaspect-ratioを設定するCSS
add_action('wp_head', 'output_aspect_ratio_css');
function output_aspect_ratio_css(): void {
    echo '<style>
    /* CLS防止: 画像の縦横比を事前に確保 */
    .wp-post-image,
    .attachment-thumbnail,
    .size-large {
        aspect-ratio: attr(width) / attr(height);
    }
    /* アイキャッチ画像のCLS防止 */
    .post-thumbnail img {
        width: 100%;
        height: auto;
        display: block;
    }
    /* 広告スペースの高さを予約 */
    .ad-slot {
        min-height: 250px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    </style>' . PHP_EOL;
}

ステップ3:INP(操作から次の描画まで)を改善する

// JavaScriptのINP最適化(wp-content/themes/mytheme/js/inp-optimize.js)

// 重いイベントハンドラーをschedulerで分割
document.addEventListener('click', function handleClick(e) {
    const btn = e.target.closest('[data-heavy-task]');
    if (!btn) return;
    
    // クリックのビジュアルフィードバックをまず処理(INP改善)
    btn.classList.add('is-loading');
    btn.setAttribute('disabled', 'true');
    
    // 重い処理をyieldで分割(Scheduler API)
    if ('scheduler' in window && 'yield' in scheduler) {
        (async () => {
            await scheduler.yield(); // ブラウザに描画を譲る
            await performHeavyTask(btn);
            btn.classList.remove('is-loading');
            btn.removeAttribute('disabled');
        })();
    } else {
        // フォールバック: setTimeout でマクロタスクに分割
        setTimeout(() => {
            performHeavyTask(btn);
            btn.classList.remove('is-loading');
            btn.removeAttribute('disabled');
        }, 0);
    }
});

// 入力フォームのイベントをデバウンス(INP改善)
function debounce(fn, delay) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

const searchInput = document.getElementById('search-input');
if (searchInput) {
    searchInput.addEventListener('input', debounce(function(e) {
        // 検索処理(300ms後に実行)
        fetchSearchResults(e.target.value);
    }, 300));
}
// スクリプトをdeferで非同期読み込み(INP改善)
add_filter('script_loader_tag', 'add_defer_to_scripts', 10, 3);
function add_defer_to_scripts(string $tag, string $handle, string $src): string {
    $defer_scripts = ['my-heavy-script', 'comments-js', 'social-share'];
    
    if (in_array($handle, $defer_scripts)) {
        return str_replace(' src=', ' defer src=', $tag);
    }
    
    return $tag;
}

ステップ4:PageSpeed Insightsで測定・確認する

# PageSpeed Insights API でスコアを取得
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com&strategy=mobile" | \
  jq '.lighthouseResult.categories.performance.score'

# Core Web Vitalsの各指標を確認
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=https://example.com&strategy=mobile" | \
  jq '.lighthouseResult.audits | {
    LCP: ."largest-contentful-paint".displayValue,
    CLS: ."cumulative-layout-shift".displayValue,
    TBT: ."total-blocking-time".displayValue
  }'

# WP-CLIでリソースヒントを確認
wp eval "echo wp_resource_hints_rels(['preload', 'dns-prefetch', 'preconnect']);"

ステップ5:定期的な自動計測を設定する

# Lighthouse CIをGitHub Actionsで自動実行
# .github/workflows/lighthouse.yml
# - npm install -g @lhci/cli
# - lhci collect --url=https://example.com
# - lhci assert --preset=lighthouse:recommended
# - lhci upload --target=temporary-public-storage

注意事項

  • fetchpriority="high"はファーストビューの最も重要な画像1枚のみに設定してください
  • CLSはブラウザのDevToolsの「レイアウトシフトリージョン」機能で視覚的に確認できます
  • INPは2024年3月にFID(First Input Delay)の代替として採用された新指標です

まとめ

Core Web Vitals改善は、①LCP画像のpreload + fetchpriority=high設定、②画像のwidth/height属性明示によるCLS防止、③JavaScript分割とdeferによるINP改善、④PageSpeed Insights APIで自動測定、⑤Lighthouse CIでCI/CDに組み込む流れで実施します。

お気軽にご相談ください

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