The Ultimate Core Web Vitals Checklist (2026)

Every optimization you should check when improving LCP, INP, and CLS performance

Arjen Karel
Updated · 21 min read
Share

Core Web Vitals checklist in short

This Core Web Vitals checklist covers images, fonts, scripts, styles, resource hints, server response times and interactivity: check it before a new site goes live and after any big change to the site. Work down it section by section, starting with the metric that fails at p75 on mobile.

The Ultimate Core Web Vitals Checklist

I keep this checklist up to date as I learn new things. Something missing? Let me know.

Core Web Vitals Optimization Checklist

Each section links to the detailed guide behind it, so you can read why an item matters before you change anything.

Optimize Images

A large image in the visible viewport usually becomes the Largest Contentful Paint element. That makes image work one of the best things you can do for LCP. For the full strategy, read how to optimize the LCP image.

  • Resize images to the largest size they are shown at on screen. Every byte spent on pixels nobody sees is wasted, and correctly sized images are often 50% smaller or more with no visible loss in quality. Combine this with responsive images for smaller screens.
  • Lazy load the images below the fold. The browser leaves them alone until the visitor scrolls near them, so the early bandwidth goes to what's on screen, which helps First Contentful Paint (FCP) and the overall load. Never lazy load the LCP image: the browser then waits for layout before it even starts the download.
  • Give the LCP image fetchpriority="high", and preload it only when the HTML hides it. An <img> in the HTML is found by the preload scanner anyway; the attribute moves it to the front of the queue. Preloading with <link rel="preload" as="image" fetchpriority="high"> is for an LCP image set in CSS or loaded by JavaScript, which the browser can't see in the HTML at all.
  • Set a width and height on every image. Modern browsers use the two attributes to work out the aspect ratio before the image has loaded, so the right amount of space is reserved and nothing moves when the file arrives. That's one source of layout shifts gone.
  • Use WebP or AVIF instead of JPEG or PNG. At similar quality a WebP file is typically 25 to 34% smaller than a JPEG, and AVIF can save up to 50%. A <picture> element with fallbacks keeps every browser covered.
  • Use native lazy loading and turn off any JavaScript lazy loading. The loading="lazy" attribute does the same job without a script to parse and run first.
  • Give images a srcset, with the sizes attribute for precise control. The browser then picks the optimal image for the device and a phone no longer downloads the file meant for a large desktop screen.
  • Add decoding="async". The browser decodes the image in parallel and keeps painting the rest of the page while it does.
  • Strip image metadata. EXIF data and the like only add bytes, and removing it doesn't touch the image quality. ImageOptim and Squoosh do this by hand, and Sharp can do it for you as part of your build.
  • Don't use a CSS background image for the LCP element. The browser finds a background image in CSS much later than an <img> in the HTML. If you have no choice, preload it with a <link rel="preload"> tag so it's discovered early. Learn more about LCP resource load delay.

Optimize Web Fonts

Web fonts can delay the First Contentful Paint, cause layout shifts and compete with other files for the little bandwidth there is early in the page load. Where to host the files is in the guide on self-hosting Google Fonts.

  • Set font-display: swap in your @font-face rules for a faster first paint. The browser shows a fallback font straight away and loads the web font in the background. When it's ready, it swaps it in. Read more about ensuring text remains visible during webfont load.
  • Combine font-display: optional with a preload to get rid of font layout shifts. With optional the browser waits about 100ms for the web font; if it isn't there by then, the fallback stays for that page view and nothing swaps. The preload makes it far more likely the font arrives inside that window.
  • Make the fallback font match the web font's dimensions with font-face descriptors. Set size-adjust, ascent-override, descent-override and line-gap-override on the fallback and the text barely moves when the web font swaps in, which keeps CLS minimal.
  • Subset fonts to the characters your content needs. A full Latin font of 100KB+ often drops under 20KB after proper subsetting. Font Squirrel makes subsets online; pyftsubset and glyphhanger do it on the command line.
  • Limit the number of font weights and styles. Each extra weight adds 15 to 50KB to download. Stick to at most 2 critical fonts (usually preloaded) and 2 that load late, after the first render.

Optimize Scripts

Scripts can cause Interaction to Next Paint problems, trigger Cumulative Layout Shifts or delay the Largest Contentful Paint. Even a small, well-behaved script early in the page competes for resources and pushes back the paint metrics (LCP and FCP). Every option you have is in 14 methods to defer JavaScript.

  • Remove the JavaScript you don't need. The Coverage tab in Chrome DevTools shows you the unused code, and every bit of dead code you cut saves download time and main thread time.
  • Prioritize scripts by what they do. A script that makes large changes to the visible viewport should be render blocking. Important scripts get deferred or loaded async, and the nice-to-have ones wait until the browser is idle. The resource prioritization guide has the detailed strategy.
  • Split your code and lazy load the parts. Break large JavaScript bundles into smaller chunks that load only when they're needed, so the first load is lighter. Any modern bundler (webpack, Rollup, esbuild) splits automatically on dynamic imports.
  • Minify and recompile your JavaScript files, every time, with a tool like SWC, Terser or esbuild. That typically takes 30 to 50% off the file size.
  • Limit third-party scripts. Each one adds DNS lookups, connection overhead and main thread time, so ask whether you need it and whether there's a lighter alternative. Check them every so often in the Network panel of Chrome DevTools.
  • Load third-party scripts with async or defer. You can't predict what a third party will do next, so never let one block rendering.
  • Watch what third-party scripts cost your real visitors. The Long Animation Frames (LoAF) API or CoreDash show their impact on INP and LCP in the field. Set a performance budget for third-party JavaScript and review it regularly.

Optimize Styles

Styles block rendering by default, so every stylesheet in the head delays the First Contentful Paint and adds to the LCP element render delay. Keep the stylesheets small and separate, and skip the tricks that split your CSS in two. For tips on cleaning up unused styles, see how to remove unused CSS.

  • Minify your CSS. Use cssnano or PostCSS, or the built-in compression of your CSS preprocessor, to strip the whitespace and comments. It's free, so do it, but it saves a few kilobytes and none of your visitors will notice.
  • Remove the CSS your pages don't use. Less CSS means less to download and parse. PurgeCSS or the Coverage tab in Chrome DevTools help you find unused CSS. Do it in your own build, though, and leave the Remove Unused CSS feature of plugins like WP Rocket, Perfmatters or LiteSpeed switched off: it causes as much trouble as critical CSS.
  • Don't inline your CSS, and skip critical CSS. Inline CSS makes the HTML bigger, and the HTML is the file everything else waits for. The browser usually can't cache that HTML either, so every page view downloads the same CSS again, while a separate stylesheet comes from the cache from the second page on. Critical CSS does save a round trip on the first view, but the rest of the CSS then arrives after the first paint: a restyle, a repaint and often a layout shift.
  • Keep each render-blocking stylesheet under about 15KB compressed. That's roughly what fits in the first round trip of a new connection, so treat it as a budget to aim for. A Slow 4G waterfall shows both ways to get it wrong: a 60KB stylesheet takes too long, and a row of 0.3KB files each pays the start-up time of its own request. Combine the tiny ones and split or trim the big one.
  • Don't load CSS async as a habit. The media="print" trick only works for CSS you know for a fact is never used in the first screen, and you rarely know that for sure. Splitting first-screen CSS from the rest is critical CSS under another name.
  • Remove every @import from your CSS. Each one starts a chain of stylesheets the browser can only fetch one after the other, and the preload scanner can't see the files at the end of it. Link them from the HTML or combine them in your build.

Optimize Resource Hints

Resource hints help the browser download critical resources first. A preloaded file is usually queued, and available, much earlier than it would be without the hint. Used well, hints cut the LCP resource load delay; for the advanced version, read about 103 Early Hints.

  • Remove the hints for resources that aren't needed for the first view. Early bandwidth is scarce, and every preload or connection you don't need takes some of it away from the files that do matter.
  • Preconnect to the domains you need early, like a content delivery network or a font provider. The DNS lookup, TCP connection and TLS handshake are then done before the first request to that domain goes out. Use <link rel="preconnect" href="https://example.com"> for critical third-party origins.
  • Consider DNS prefetch where a preconnect is too much. Preconnect sets up the whole connection, while <link rel="dns-prefetch"> only resolves the domain name in advance. That's enough for a domain you'll probably need later, when the cost of a full connection isn't worth it yet.
  • Preload the LCP image when it isn't in the HTML. LCP measures how long the main content takes to show up, and preloading the LCP element moves that resource to the front of the queue. For an <img> that's already in the HTML, fetchpriority="high" does the same without the extra tag.
  • Preload critical fonts with <link rel="preload" as="font" type="font/woff2" crossorigin>. The browser fetches them early, so text shows sooner and font swapping causes fewer layout shifts.
  • Send your resource hints as 103 Early Hints where you can. With a 103 the server sends the hints before the full response is ready, which is the earliest the browser can possibly hear about a resource. If your server can't send a 103, use Link response headers, and if headers aren't an option either, add <link> elements to the <head> of the page as a fallback.
  • Preload fonts before your CSS discovers them. A font referenced in CSS is only found after that CSS has downloaded and been parsed. A preload in the HTML <head> removes that wait, the font loads in parallel, and both FCP and the risk of layout shift go down.

Optimize Icons

Icons are easy to overdo: a full icon set loaded for the five icons the page uses. Large inline SVG icons bloat your HTML, and an icon font often carries thousands of glyphs you never use. Getting icons right helps LCP (less HTML and CSS to load) and CLS (space reserved for every icon).

  • Don't put large SVG icons inline in your HTML. They make the HTML bigger and the page slower, and the browser can't cache them on their own. An external SVG sprite sheet is usually the best balance between performance and flexibility, though separate files or an icon font (with caution) also keep the HTML small.
  • Never load a large icon set like Font Awesome in full. The whole set can be over 100KB, while a subset with 20 icons can stay under 5KB, so subset it into a smaller icon font or use individual SVGs.
  • Reserve a width and height for icons, just like for images. The browser then keeps the space free and the icons don't cause layout shifts as they load. Set the width and height attributes on the SVG elements or give them explicit dimensions in CSS.
  • Don't preload or inline icons the first screen doesn't need, so the essential content loads first. Lazy load the icons that sit below the fold.

Optimize Server Response Times

Server response time, measured as Time to First Byte (TTFB), comes before every paint metric, so a slow server delays everything that follows. For detailed strategies, see the guides on diagnosing TTFB issues and configuring Cloudflare for performance.

  • Pick a fast, reliable hosting provider with solid infrastructure. Compare providers on the TTFB you measure yourself in the real world. The numbers in their marketing are synthetic.
  • Log how long your code and your database queries take, and do it often. Query profiling and application performance monitoring (APM) tools point you straight at the slow endpoints.
  • Cache in the browser and on the server, so data that's requested often doesn't have to be fetched again every time. Full-page caching can take TTFB from seconds to under 100ms. The cache duration optimization guide has the details.
  • Render small personal bits on the client or at the edge. A cart count, the login status or a minor menu change shouldn't bust the cache for the whole page. Keep the full-page cache and fill in those details client-side or with edge rendering.
  • Tune your web server: keep-alive settings, the number of worker processes, memory allocation and timeout values. A misconfigured server wastes resources and answers slower.
  • Use a Content Delivery Network (CDN). It serves your static content from edge nodes (servers) close to the visitor, which shortens the distance the data travels and speeds up loading for a global audience. A CDN is also usually better configured than your own server. See the guide on configuring Cloudflare for a practical setup walkthrough.
  • Do less work per request. Compute the expensive things ahead of time, use efficient algorithms and move anything that isn't essential to a background job. Then walk through your application's request lifecycle once more and drop every step that doesn't need to be there.
  • Use HTTP/3. QUIC sets up the connection in one round trip, where TCP plus TLS needs two or more, and a lost packet no longer holds up the other requests on that connection. You notice that most on mobile networks. The gain is in the connection, so it shows up in TTFB and through it in LCP. A first visit often still goes over HTTP/2, because the browser learns about HTTP/3 from the Alt-Svc header or an HTTPS DNS record. More on connection duration optimization.
  • Add Server-Timing headers. They tell you how long each part of the server work takes for a page, so you can see the bottleneck and work on the part that holds back the Largest Contentful Paint (LCP). You see them in the Network panel of Chrome DevTools, and RUM tools like CoreDash can collect them.
  • Log slow database queries and fix them regularly. Turn on slow query logging (every major database has it) and read the log every week. Better indexes, rewritten queries and a cache layer for the frequent ones can cut TTFB by a lot.
  • Compress text files (HTML, CSS, JavaScript) with GZIP or, better, Brotli. The server compresses them on the fly before sending, and they end up about 70% smaller. Brotli typically compresses 15 to 20% better than GZIP.

Optimize Interactivity

Interaction to Next Paint (INP) measures how fast your site responds to user interactions. Bad INP usually comes from long JavaScript tasks that block the main thread. Each of the three INP phases has its own guide: input delay, processing time and presentation delay.

  • Use an idle-until-urgent pattern for expensive scripts. Critical work runs first and the JavaScript that isn't essential waits until the main thread is idle, so rendering and user input never get stuck behind a long-running script. Schedule the non-urgent work with requestIdleCallback, and if the result is needed sooner, run it right away. Learn more about optimizing processing time.
  • Break up long tasks by yielding to the main thread. A long task blocks the main thread and the page stops responding. Split it into smaller chunks and hand control back to the browser between them, and it can handle a click or a key press in the gaps. Use scheduler.yield() where it's supported, or setTimeout(0). See the guide on improving INP by ditching JavaScript scrolling.
  • Paint the feedback before the heavy work. INP runs until the next paint, so change the button state or show a spinner first, then yield (scheduler.yield() or a setTimeout) and do the expensive part after that. A :active style or a CSS transition only shows when the main thread is free to paint it; a handler that keeps the thread busy freezes it along with the rest of the page.
  • Make scroll and touch listeners passive with { passive: true }. That tells the browser the handler will never call preventDefault(), so it can start scrolling right away instead of waiting for JavaScript. You feel it most on mobile. Scrolling itself isn't an interaction INP measures, so this is about smooth scrolling more than about the INP number.

Core Web Vitals Monitoring

Keep monitoring your Core Web Vitals, because that's how you catch a regression early and check that a fix did what you expected. Field data at p75 is what counts; lab tests find out why. Start with the p75 of each metric on mobile, the page groups Search Console flags, and the trend over the last weeks: a sudden drop usually points at a deploy, while a slow slide is more often third-party scripts piling up one by one.

  • Run Lighthouse regularly. It's Google's free, open-source auditing tool for finding performance problems on a page. Lighthouse doesn't measure the Core Web Vitals the way real visitors experience them, but it's a great tool for testing and comparing your site under the same standardized conditions every time. A standard Lighthouse run doesn't measure INP at all, since nobody clicks anything during the test; Total Blocking Time is the closest it gets. Put Lighthouse in your CI/CD pipeline and you catch regressions before they go live.
  • Check the CrUX history regularly. CrUX (the Chrome User Experience Report) is Google's public dataset of real-world performance, and it's the data Google uses to decide whether you pass the Core Web Vitals. Every CrUX number is the p75 of the last 28 days, so a regression creeps in over weeks and a fix takes about four weeks to show in full. PageSpeed Insights shows only the current 28 days; the history is in CrUX Vis and the CrUX History API.
  • Set up RUM tracking. Real User Monitoring measures how long your pages really take for your visitors, in their locations and on their devices. Lighthouse is a lab test and CrUX gives you the p75 of the last 28 days, without telling you which element or script caused it. RUM fills that in. I recommend CoreDash, which I built for detailed Core Web Vitals attribution data.
  • Set performance budgets: concrete targets per metric at p75 on mobile, for example LCP under 2.5 seconds, INP under 200ms and CLS under 0.1. Measure against them regularly and you see right away which areas need attention first.
  • Segment your data. Track your most valuable visitor types and your page types on their own, because the rest of your traffic can hide a problem that only hits them. Split by device and page template first; connection speed and country come after that.

Optimize Critical Rendering Path

The critical rendering path is every step the browser takes to turn HTML and CSS (and JavaScript) into pixels on screen. A shorter path means a faster First Contentful Paint and less LCP element render delay. See also how to avoid excessive DOM size.

  • Minimize the number of critical resources. Every render-blocking resource, CSS or synchronous JavaScript, has to be downloaded and processed before the browser can paint. Defer the scripts that aren't essential and keep your stylesheets few and small.
  • Get the loading order right: stylesheets first, then the LCP image and the fonts the first screen uses, then the other images above the fold, then the deferred scripts. The fetchpriority attribute and resource prioritization hints tell the browser what matters most.
  • Keep the DOM tree shallow. Deep nesting means more style calculation and more layout work. Aim for at most 32 levels and fewer than 1,500 elements in total where you can; a flatter DOM paints faster and helps INP presentation delay.
  • Favor classes and IDs over element tags and attributes in your selectors. Write .important instead of p.important, so the browser has less to check during style matching and recalculates styles faster.
  • Don't nest selectors deeply. Every extra level means more work for the browser. Restructure the HTML to reduce nesting, or put a more specific class closer to the element, and stay at 3 levels at most.
  • Use fewer descendant selectors. A selector like .container .content makes the browser walk up through the ancestors of every .content element to find the container. A class directly on the content element matches faster.
  • Group selectors that share the same styles into one class, or use BEM (Block Element Modifier) naming. It's easier to maintain and the CSS gets smaller.

Optimize Cookie Consent

GDPR and similar regulations require a cookie consent banner, but a carelessly loaded banner can delay LCP, cause CLS and push up INP. For more detail, read about optimizing third-party widgets for Core Web Vitals.

  • Render the consent banner on the server when the page is rendered there anyway. It comes with the first HTML response, which is often faster than a separate JavaScript solution: no extra network request and no script to evaluate.
  • Load the consent script async on cached pages, and consider fetchpriority="high" on it, so it still shows before anyone interacts.
  • Show the banner as an overlay with a fixed position. A banner inserted at the top of the page pushes everything below it down, and that's a layout shift on every first visit. Fixed to the bottom of the screen, it sits on top of the content and nothing moves.
  • Self-host the cookie notification scripts and stylesheets where you can, and cache them. You skip the DNS lookups and connections to the consent management platform, and the loading behavior is yours to control.

Optimize Single Page Applications

Single Page Applications (SPAs) built with React or Vue have their own Core Web Vitals problems: client-side rendering delays both FCP and LCP, and hydration can block INP.

  • Always use server-side rendering or prerendering. An SPA that renders only on the client makes the browser download and run all that JavaScript before anything is visible. With SSR (Next.js, Nuxt, SvelteKit) or static prerendering you send HTML the browser can paint right away.
  • Prefer static prerenders over dynamic ones. A static prerender is generated at build time and served straight from a CDN without any server-side processing, which makes it much faster than one generated per request. Use static generation for every page that doesn't need per-request data.
  • Load third-party scripts after hydration. Hydration already takes a lot of main thread time to make the page interactive, and third-party scripts running at the same moment make input delay worse. Defer every script that isn't essential until hydration has finished.

Avoid Excessive DOM Size

A large DOM (more than 1,500 elements, or deeper than 32 levels) uses more memory, slows down style calculations and causes costly layout reflows. You see it in the INP presentation delay and in the paint metrics. See how to fix excessive DOM size.

  • Remove the DOM elements you don't need. Look for wrapper elements that do nothing for styling or structure, and replace deeply nested <div> structures with semantic HTML. Long lists can be virtualized with a library like react-window or virtual-scroller, which keeps the live DOM small.
  • Keep selectors and DOM queries efficient. Complex CSS selectors and broad JavaScript queries (a querySelectorAll with a wide pattern, for example) get slower and slower as the DOM grows. Use specific class selectors and limit each query to a subtree where you can.
  • Use content-visibility: auto for content that's off screen. The browser skips rendering those elements until they're scrolled into view, which can cut the initial rendering work dramatically on pages with long sections.

Optimize API Requests

API requests that block rendering or hold back content hurt LCP and TTFB, and in single page applications data fetched on the client is one of the usual reasons for a slow LCP.

  • Make fewer API requests. Each one adds to the page load, so look at what the first view really needs and cut the rest. Batching (several requests combined into one) and GraphQL save round trips.
  • Make the APIs themselves fast. How an API is designed and built affects the page that waits for it. Add caching on the API side for data that's requested often.
  • Preload critical API requests with <link rel="preload" as="fetch" crossorigin>. Just like an image preload, it starts the request early, and the data is already there when the first render needs it. Leave out crossorigin and the preload doesn't match the fetch() call, so the browser downloads the data twice. The resource prioritization guide has more techniques.

Optimize Chat Widgets

Chat widgets are a common cause of layout shifts and can even get in the way of the LCP if they load early. For a step-by-step approach, read how to implement a chat widget with perfect Core Web Vitals.

  • Load the chat widget after the main content has loaded. No one in the history of the internet has ever needed to chat before the main content of the page has loaded. Start it once the first render is done, with requestIdleCallback or when the visitor scrolls.
  • Hide the chat widget until it's ready. If it causes a layout shift, keep it at opacity: 0 until it has fully rendered, so it can lay itself out in the background without moving visible content. Then fade it in with a CSS transition.
  • Shop around for a lightweight chat widget. Some cause far fewer Core Web Vitals problems than others. Before you commit, compare the JavaScript bundle size, the number of network requests and the INP impact of each provider.

Optimize Service Worker Performance

A service worker can make repeat visits a lot faster by caching assets and even complete pages, which cuts TTFB for returning visitors. Build it badly and it slows navigation down instead. Learn more about cache duration optimization.

  • Cache critical assets in the service worker. A cache-first strategy for static assets (CSS, JavaScript, fonts, images) lets returning visitors load your site almost instantly from the local cache. Precache the most important resources in the install event.
  • Keep the service worker code lean. Complex routing logic, heavy use of event.waitUntil() and a large precache manifest all slow down installation. For resources that change often but don't need to be fresh the very moment they change, use stale-while-revalidate.

Optimize Video Content

A video becomes the LCP element when it's the largest visible content in the viewport, and a big one also takes bandwidth from other critical resources.

  • Compress your videos with a modern codec (H.264, VP9 or AV1) at a sensible quality setting. Encode at the size the video is shown: one that appears 400px wide doesn't need to be 1920px. Two-pass encoding gives the best quality for the file size.
  • Lazy load videos below the fold. Put loading="lazy" on an <iframe>, or hold back the video with the Intersection Observer API. Replace autoplaying background videos with a poster image and load the video only when the visitor scrolls near it.
  • Host videos on a fast CDN. Video files are big and gain a lot from CDN distribution. A dedicated video CDN or hosting service (Cloudflare Stream, Mux, Bunny.net) adds adaptive bitrate streaming, geographic distribution and optimized delivery on top.
  • Always set a poster attribute on <video> elements. The browser can paint the poster right away while the video loads, and the poster can be the LCP element, so optimize it like any other LCP image.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

Arjen Karel

Need a faster site?

I'm Arjen, and I've been fixing slow sites for 17 years. If you're running into issues you can't fix yourself, send me your URL and let's have a chat.

Send me your URL
Arjen Karel
Arjen Karel, Web performance consultant since 2009

Why isn't our fix working?

Sometimes the CDN still serves the old HTML, or the fix went into the desktop template and mobile uses another one. Send me your URL and the problem, and I reply with a few times. I check your field data first, then we spend one hour on your site, and within a day you have my notes in writing. €350 excl. VAT.

Send me your URL

No developer to build the fix? I do the work myself, on a copy of your site.