Most small business WordPress sites do not fail Core Web Vitals because of one big mistake. They fail because of a dozen small ones that pile up: a slider on the homepage, three font families, a page builder loading everything everywhere, and a chat widget that blocks the main thread. This is the checklist we work through when a site comes to us with poor field data. It assumes a typical setup: shared or entry level managed hosting, a commercial theme or page builder, and somewhere between 15 and 40 plugins.
Step 0: measure the right thing
Before touching anything, check field data, not just a Lighthouse score. PageSpeed Insights shows Chrome UX Report (CrUX) data at the top when the site has enough traffic. That is what Google uses. Lab scores are useful for debugging but they run on a simulated slow device and can swing wildly between runs.
The three metrics and their "good" thresholds, measured at the 75th percentile of real visits:
- LCP (Largest Contentful Paint): 2.5 seconds or less
- INP (Interaction to Next Paint): 200 milliseconds or less
- CLS (Cumulative Layout Shift): 0.1 or less
If the site has no CrUX data, add the web-vitals library and send the numbers to your analytics so you have something real to compare against:
<script type="module">
import {onLCP, onINP, onCLS} from 'https://unpkg.com/web-vitals@4?module';
function send(metric) {
navigator.sendBeacon('/vitals', JSON.stringify({
name: metric.name, value: metric.value, id: metric.id, page: location.pathname
}));
}
onLCP(send); onINP(send); onCLS(send);
</script>
LCP checklist
1. Find the LCP element. In Chrome DevTools, open the Performance panel, record a reload and look for the LCP marker. On most small business sites it is the hero image or the main heading.
2. Do not lazy load the hero image. WordPress adds loading="lazy" to images automatically and since 6.3 tries to skip the first content image, but page builders and sliders often bypass that logic. Check the rendered HTML. If your hero has loading="lazy", fix it at the source or filter it:
add_filter( 'wp_get_loading_optimization_attributes', function ( $attrs, $tag_name, $attr, $context ) {
if ( 'img' === $tag_name && is_front_page() && empty( $GLOBALS['dc_hero_done'] ) ) {
$GLOBALS['dc_hero_done'] = true;
unset( $attrs['loading'] );
$attrs['fetchpriority'] = 'high';
}
return $attrs;
}, 10, 4 );
3. Prefer an <img> over a CSS background. A background image set in a stylesheet is discovered late, after the CSS has downloaded and parsed. If the theme insists on a background, preload it in the head with <link rel="preload" as="image" fetchpriority="high">.
4. Serve modern formats at sensible sizes. WordPress has supported WebP uploads since 5.8 and AVIF since 6.5, provided the server's image library supports them. Make sure srcset and sizes are present so a phone is not downloading a 2400 pixel image.
5. Fix server response time. If TTFB is above about 800ms, no front end trick will rescue LCP. Turn on full page caching, check that logged out visitors are actually getting cached pages (look for a cache header), and add a persistent object cache such as Redis if the host offers it. WooCommerce sites need care here because cart and checkout pages must stay uncached.
6. Cut render blocking CSS. Many page builders ship one large stylesheet for every widget. Use the Coverage tab in DevTools to see how much is unused on the homepage. Where the builder offers "improved asset loading" or similar, turn it on.
INP checklist
INP replaced First Input Delay in March 2024 and it is the metric most small business sites now fail. It measures how long the page takes to respond visually after a tap, click or key press, across the whole visit.
7. Audit third party scripts. Chat widgets, cookie banners, review carousels, heatmap tools and multiple tracking pixels are the usual suspects. Each one adds JavaScript that competes for the main thread. Ask whether each script earns its place.
8. Defer what you can. Since WordPress 6.3 you can register scripts with a loading strategy instead of hacking script_loader_tag:
add_action( 'wp_enqueue_scripts', function () {
wp_register_script(
'site-ui',
get_stylesheet_directory_uri() . '/js/site-ui.js',
array(),
'1.4.0',
array( 'strategy' => 'defer', 'in_footer' => true )
);
wp_enqueue_script( 'site-ui' );
} );
9. Load plugin assets only where they are used. Contact Form 7 is a classic example: by default its script and stylesheet load on every page. Dequeue them everywhere except the pages with forms:
add_action( 'wp_enqueue_scripts', function () {
if ( ! is_page( array( 'contact', 'request-a-quote' ) ) ) {
wp_dequeue_script( 'contact-form-7' );
wp_dequeue_style( 'contact-form-7' );
}
}, 20 );
Be careful with this on sites that use a global popup form. Test every form after the change.
10. Delay non essential tags until interaction. A chat widget does not need to load before the visitor has scrolled or moved the mouse. A small loader that injects the script on the first pointerdown, keydown or scroll event removes it from the critical window entirely.
CLS checklist
11. Give every image and embed dimensions. WordPress outputs width and height on images from the media library, but images added through custom HTML blocks, sliders and iframes often lack them. Use CSS aspect-ratio for embeds such as YouTube or maps.
12. Stabilise web fonts. Self host fonts where possible, preload the one or two files used above the fold, and use font-display: swap along with a fallback font that has similar metrics. The size-adjust descriptor in @font-face can reduce the jump when the web font arrives.
13. Reserve space for banners. Cookie banners and announcement bars that push content down are a common cause of layout shift. Overlay them at the bottom of the viewport or reserve their height in the initial layout.
Keeping it fixed
Performance regresses quietly. A new plugin, a marketing tag added through Tag Manager, or a theme update can undo weeks of work. A few habits help:
- Keep a short list of approved plugins and review it every quarter.
- Re check field data in PageSpeed Insights or Search Console's Core Web Vitals report a month after any significant change, because CrUX uses a rolling 28 day window.
- Run Query Monitor on staging before deploying, and look at slow database queries as well as front end assets.
If you want a second pair of eyes on a WordPress build, our WordPress development team in Birmingham works through exactly this list on client sites. But most of it you can do yourself in an afternoon with DevTools open and a staging copy of the site.
Rocky V, Head of Operations at DigitalCave
Top comments (0)