Improve Core Web Vitals LCP CLS and FID in WordPress

Google’s Core Web Vitals became an official search ranking factor in June 2021, which means sites with poor loading performance, interactivity, or visual stability can rank lower than competitors with similar content but better technical performance. The three Core Web Vitals metrics are: Largest Contentful Paint (LCP) — how long the largest visible element takes to load (target: under 2.5 seconds); First Input Delay (FID) — how long before the browser can respond to the first user interaction (target: under 100ms); and Cumulative Layout Shift (CLS) — how much the page layout shifts unexpectedly during loading (target: under 0.1). Measuring these values is free using Google Search Console (field data for real users), PageSpeed Insights, or Lighthouse in Chrome DevTools. The most common causes of poor Core Web Vitals in WordPress are: render-blocking scripts and stylesheets (fix with defer and preloading), unoptimised images without explicit width/height attributes (fix CLS), large hero images loaded eagerly without WebP (fix LCP), and excessive JavaScript execution time from too many plugins (fix FID). Combine the WebP image guide and lazy loading with the server-side improvements below for the biggest gains. The code examples show how to add defer to scripts, preload the LCP image, and add explicit image dimensions via WordPress.

Problem: Your WordPress site scores poorly on Core Web Vitals (LCP, FID, CLS) and you want to fix the most common issues without a caching plugin.

Solution: Add the following code to your functions.php file:

/**
 * Add defer attribute to non-critical scripts to avoid render-blocking.
 */
add_filter( 'script_loader_tag', 'helloadmin_defer_scripts', 10, 3 );
function helloadmin_defer_scripts( $tag, $handle, $src ) {
    $defer_handles = [ 'comment-reply', 'jquery-migrate', 'wp-embed' ];
    if ( in_array( $handle, $defer_handles, true ) ) {
        return str_replace( '<script ', '<script defer ', $tag );
    }
    return $tag;
}

/**
 * Preload the LCP hero image to improve Largest Contentful Paint.
 * Change the image URL to match your actual hero image.
 */
add_action( 'wp_head', 'helloadmin_preload_hero_image', 1 );
function helloadmin_preload_hero_image() {
    if ( is_front_page() ) {
        $hero_image = get_template_directory_uri() . '/images/hero.webp';
        echo '<link rel="preload" as="image" href="' . esc_url( $hero_image ) . '" fetchpriority="high">' . "
";
    }
}

/**
 * Add explicit width and height to <img> tags in post content to prevent CLS.
 */
add_filter( 'the_content', 'helloadmin_add_img_dimensions' );
function helloadmin_add_img_dimensions( $content ) {
    return preg_replace_callback(
        '/<img([^>]+)class="([^"]*wp-image-(\d+)[^"]*)"([^>]*)>/i',
        function ( $m ) {
            if ( strpos( $m[0], 'width=' ) !== false ) {
                return $m[0]; // already has dimensions
            }
            $attachment_id = (int) $m[3];
            $meta = wp_get_attachment_metadata( $attachment_id );
            if ( ! $meta || empty( $meta['width'] ) ) {
                return $m[0];
            }
            return '<img' . $m[1] . 'class="' . esc_attr( $m[2] ) . '"'
                 . $m[4] . ' width="' . absint( $meta['width'] )
                 . '" height="' . absint( $meta['height'] ) . '">';
        },
        $content
    );
}

/**
 * Remove render-blocking Google Fonts by preconnecting to the origin.
 */
add_action( 'wp_head', 'helloadmin_preconnect_fonts', 1 );
function helloadmin_preconnect_fonts() {
    echo '<link rel="preconnect" href="https://fonts.googleapis.com">' . "
";
    echo '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>' . "
";
}

NOTE: Do not blindly defer all scripts — some scripts (analytics, consent management, A/B testing) must load synchronously before the page renders to work correctly. Test each script handle individually. Also, fetchpriority="high" on the preloaded image is a hint to the browser to load it before other resources — only apply it to the single most important image on the page; using it on multiple images defeats the purpose.