Core Web Vitals: Improving LCP with Fetch Priority and Preload in WordPress

Largest Contentful Paint (LCP) is the Core Web Vitals metric most directly improved by telling the browser how to prioritise resource loading. Two complementary tools — the fetchpriority="high" attribute and <link rel="preload"> — let you move the LCP image or hero section to the front of the browser’s fetch queue without JavaScript. WordPress 6.3+ applies fetchpriority="high" automatically to the first wp_get_attachment_image() in the main loop, but custom themes often need manual intervention.

Problem: A WordPress page has a large hero image that is the Largest Contentful Paint element — the image loads late because the browser discovers it only after parsing HTML and CSS, causing a high LCP score even with optimised images.

Solution: Add a <link rel="preload" as="image" fetchpriority="high"> tag for the LCP image in wp_head. Set fetchpriority="high" directly on the <img> element. Use loading="eager" on the LCP image and loading="lazy" on all below-fold images. Serve WebP or AVIF formats with a <picture> source fallback.


The code below adds fetchpriority="high" to a custom hero image, injects a preload hint for the LCP image via wp_head, and removes the WordPress auto-generated low-priority hint that conflicts with it.


<?php
// 1. Mark the hero attachment image as high priority
add_filter( 'wp_get_attachment_image_attributes',
    function ( array $attr, WP_Post $attachment, $size ) {
        // Only the designated hero image ID gets the high priority flag
        if ( (int) $attachment->ID === (int) get_theme_mod( 'hero_image_id' ) ) {
            $attr['fetchpriority'] = 'high';
            $attr['loading']       = 'eager';   // override lazy-load default
            unset( $attr['decoding'] );         // remove async decoding for hero
        }
        return $attr;
    }, 10, 3
);

// 2. Inject a <link rel="preload"> for the hero image in <head>
add_action( 'wp_head', function () {
    if ( ! is_front_page() ) {
        return;
    }
    $image_id = (int) get_theme_mod( 'hero_image_id' );
    if ( ! $image_id ) {
        return;
    }
    $src = wp_get_attachment_image_url( $image_id, 'hero-1280' );
    if ( ! $src ) {
        return;
    }
    // Output preload — browser will fetch this before parser reaches <body>
    printf(
        '<link rel="preload" as="image" href="%s" fetchpriority="high">' . "\n",
        esc_url( $src )
    );
}, 1 );   // priority 1 = very early in <head>

// 3. Remove the low-priority preload WordPress auto-generates for backgrounds
add_filter( 'wp_preload_resources', function ( array $resources ) {
    return array_filter( $resources, fn( $r ) =>
        ! isset( $r['as'] ) || $r['as'] !== 'image'
    );
} );


NOTE: Using both fetchpriority="high" on the <img> tag and a <link rel="preload"> for the same image is intentionally redundant — the preload wins if the image is in CSS or a late-rendered component; the attribute wins when the <img> tag is in the initial HTML payload.