Core Web Vitals: How to Improve Speed and UX
Core Web Vitals are the rare performance metrics that force teams to talk about the user experience, not just the engineering backlog. They translate “the site feels slow” into three measurable signals, and they give you enough structure to improve both speed and comfort. But the hard part is that each metric rewards different kinds of work, and it is easy to optimize one number while accidentally hurting another.
I have seen this play out in real projects: a team reduces JavaScript bundles and celebrates, only to discover the page now renders sooner but shifts layout later because images and ads load without reserved space. Another team “fixes” LCP by swapping components, then increases CLS because new interactions inject content after initial paint. The goal is not a single optimization pass, it is a coordinated set of changes that make the page stable, fast to first meaningfully rendered content, and responsive when the user starts clicking.
Below is a practical, field-tested approach to improving Core Web Vitals with judgment, not just check-the-box tweaks.
The three signals, what they really measure
Core Web Vitals typically refer to:
- LCP (Largest Contentful Paint): how quickly the largest visible content element appears.
- INP (Interaction to Next Paint): how responsive the page feels when users interact.
- CLS (Cumulative Layout Shift): how stable the layout is while it loads.
Even if your reports show different numbers than expected, the direction usually holds. When LCP is slow, users tend to bounce before they see what they came for. When INP is poor, clicks feel sticky, menus lag, forms hesitate, and frustration rises quickly. When CLS is high, the page looks unstable, content jumps, and users mis-click or abandon tasks.
LCP: “fast enough to start trusting the page”
LCP is often the hero area: a headline, a product image, a banner background, or a video poster. The key nuance is that LCP is about when the element becomes visible, not when the server responds. You can have a fast server and still miss the LCP target if the browser is blocked from rendering by heavy scripts, delayed CSS, or late image loading.
In practice, LCP is frequently harmed by:
- Large above-the-fold images without proper sizing and compression.
- Render blocking resources, especially CSS that is not optimized or is fetched late.
- JavaScript work that delays first paint or the rendering of the LCP element.
- Slow third-party scripts that compete for bandwidth and main thread time.
But LCP is not only “optimize images.” I have debugged pages where the LCP element was technically an image, yet the real culprit was a CSS animation or a layout thrash loop that kept the browser busy during initial render. If the largest element cannot be painted because the main thread is jammed, your LCP suffers.
INP: responsiveness under real user behavior
INP measures the latency of interactions, focusing on the worst interactions over the observed period. That means you can have a page that feels snappy for a second, then falls apart during a carousel interaction, a search filter, or a slow button handler.
INP is often dominated by main thread contention. If your page spends too much time running JavaScript, parsing, rendering, or doing expensive work on click, INP will show it.
Common INP triggers I have seen:
- Event handlers that trigger large re-renders.
- Heavy client-side filtering on big datasets.
- Long tasks from analytics or tag managers firing during interactions.
- Smooth scrolling and animation libraries competing with user input.
INP is also sensitive to how your page handles user flows. For example, a “quick add to cart” button that triggers multiple network requests and rerenders the cart widget can be slow even if the initial page load is fine. If users click early, you need to ensure the page remains responsive right then, not after some background initialization finishes.
CLS: stability is a UX feature
CLS captures how much the layout shifts during the lifecycle. It is not just about images resizing. It includes any visible element that changes size or position after the initial layout.
The most reliable way to reduce CLS is to make space for what is coming.
The typical culprits:
- Images without width and height attributes.
- Ads inserted after load without reserved space.
- Fonts that swap and change text metrics (or that reflow lines).
- Late-loaded components above or near the fold.
The important part is causality. CLS is not just “something moved.” It is about unexpected movement that users perceive. You can often eliminate CLS by ensuring that the layout is stable from first render to final render, even if content streams in later.
Start with evidence, not vibes
If you already have CrUX or field data, use it. If you do not, begin with lab data but plan to confirm with field metrics.
A workflow I trust:
- Check both field and lab measures when possible.
- Identify the element tied to LCP.
- Look for the largest CLS shifts and their sources.
- For INP, examine long tasks and interaction traces, not just average responsiveness.
You should also test on realistic connections and devices. A page can pass on desktop on a fast Wi-Fi connection and fail hard on mid-tier phones on mobile networks. The browser’s constraints matter: CPU throttling changes the behavior of JavaScript, and bandwidth limits change the timing of late resources.
If your tool stack includes Lighthouse, WebPageTest, or browser performance traces, treat them as instruments. They help you identify what changed, but do not assume the first diagnosis is always correct. One reason teams get stuck is that the “suggested fix” targets the wrong bottleneck for their users.
LCP improvements that do not break UX
When LCP is slow, the instinct is to “make the page faster.” In practice, you need to make the largest above-the-fold element easier to render early.
Confirm what the LCP element is
If your LCP element is a hero image, prioritize that pipeline. If it is a block of text, prioritize font loading and CSS for that text.
I once worked on a marketing page where the LCP element looked like the hero heading. Yet the real delay came from the stylesheet that styled the heading. The browser had the text, but it could not paint it with the correct styling quickly because critical CSS was delayed. After extracting and inlining the critical CSS, LCP improved significantly without any image tweaks.
The lesson is simple: LCP is about the element the browser reports, not the one you assume.
Optimize the LCP resource like a product
For images, aim for the right format, the right size, and predictable decoding.
Practical steps that usually help:
- Serve modern formats when supported, and keep fallbacks correct.
- Make sure the image is not significantly larger than the displayed size.
- Preload the LCP image when it is truly above the fold and not conditionally displayed.
- Avoid lazy-loading the LCP element itself. Lazy loading can delay the element if it is still considered “not in viewport” at load time.
For hero videos, posters matter. A video can have a strong poster but if the poster is loaded late, you still lose LCP. In those cases, treat the poster image like the LCP candidate.
Reduce render blocking, then verify
If you delay CSS, your LCP can be delayed even when JavaScript is light. Inline the minimal critical CSS needed for the first meaningful render, and defer the rest.
Also watch for font loading behavior. Fonts can affect text rendering and can cause a delay if the browser is waiting for font files to be ready before painting. If your design allows it, use font-display strategies that prevent invisible text while maintaining acceptable visual quality.
When you change font loading, keep CLS in mind. Font swaps can trigger reflow. The goal is not to eliminate all font-related behavior, but to avoid layout instability.
Trim the work that competes with paint
A big above-the-fold element needs a clear runway. If you have heavy JavaScript executing early, your paint can be delayed. That is why LCP fixes often overlap with INP fixes, but timing differs.
A page can download quickly but still delay painting because the main thread is busy. If you can defer non-critical scripts, split bundles, and remove early boot work, LCP often improves without sacrificing features.
CLS: how to stop the page from “moving under the user”
CLS is usually the easiest to fix once you know the source, but it requires discipline. Teams often address the obvious cases like images without dimensions, then miss the more subtle ones like late component insertion.
Reserve space for media and dynamic content
If you only do one CLS improvement, make sure every image, video poster, and embedded widget has a stable layout from first render.
For images, always include explicit width and height attributes, or use CSS aspect-ratio where appropriate. That allows the browser to allocate the right search engine optimization space before the image is decoded.
For carousels, accordions, and expanding sections, decide the initial layout. If content will expand, reserve the container space when possible, or ensure expansion happens after a deliberate user action, not during load.
Ads are local seo strategies a common CLS source. If you cannot reserve space due to ad network constraints, you need an alternative: placeholder blocks with fixed height, or a strategy that hides the ad container until dimensions are known, while still preserving overall layout.
Handle font metrics carefully
Fonts are sneaky. When fonts load and the browser swaps from fallback to the final font, line breaks and text wrapping can change, which causes layout shifts.
You can reduce this with:
- Using web fonts with metrics that are close to fallback.
- Setting appropriate font-display behavior.
- Keeping typography sizes stable across states.
You will sometimes accept a slight change in typography to prevent the jarring jump that users notice. The UX trade-off matters more than the metric alone.
Watch for component reordering and late DOM injection
CLS can also come from state changes that insert content above the fold after load.
Examples:
- A cookie banner that pushes the whole page down after an animation.
- A notification bar that appears at the top after initial render.
- A “recommended products” module that mounts above the fold.
If the design requires these elements, render them in the correct place from the start with reserved space, even if the content is filled later.
A note on judgment: hiding things until they are fully ready can reduce CLS, but it can harm LCP or perceived responsiveness if the user sees blank areas. Stabilize the layout while still showing something meaningful.
INP: make interactions fast, predictable, and tolerant of load
INP problems often show up when you click something, even though the page looks fine. That is because the interaction triggers work that the initial load path does not.
Identify the interactions that hurt INP
Do not treat INP as a generic “performance score.” You need to know which interactions are being measured as slow. In performance traces, look at the interaction target and what happens next.
Is it a click handler that triggers a re-render of a large portion of the page? Is it waiting on a network request? Is it blocked by a long-running script that started earlier? The fix depends on the cause.
I have seen slow INP caused by a seemingly unrelated issue: an analytics script registering heavy event listeners and running work on every click. The UI looked responsive until you opened the trace, and then the main thread profile showed the overhead plainly.
Reduce expensive main-thread work during interaction
A good INP strategy is to reduce what happens on click.
Typical approaches:
- Debounce or throttle work triggered by rapid interactions.
- Avoid re-rendering large trees. If a component updates, constrain the scope.
- Use memoization carefully, because it can help but it can also add complexity.
- Move heavy computation to a Web Worker when appropriate.
- Defer non-essential work until after the interaction, not before it.
The goal is not to eliminate JavaScript, it is to keep the main thread free enough that the browser can paint the “next frame” promptly.
Break up long tasks and control third-party behavior
Third-party scripts are frequent INP offenders. They may load early, execute during interaction, or create long tasks through DOM operations.
You do not always control those scripts, but you can influence their timing:
- Load them after user consent when required.
- Defer non-critical tags.
- Reduce the number of tags that fire on each interaction.
- Prefer lightweight alternatives for tools that are used for convenience but not core value.
If you have a tag manager, check what fires during the first interaction. It is easy to add scripts over time, and it is easy to forget which ones affect responsiveness.
Be careful with optimistic UI
Optimistic interfaces can improve perceived speed by updating the UI immediately, while the network request happens in the background. That can help INP, because the browser has something to paint right away.
But optimistic UI can backfire if you then correct it with a big re-render when the response arrives. That correction can add CLS and can also hurt INP because you create more main-thread work. If you choose optimistic updates, keep the update small and stable.
Build a measurement strategy that matches your release cycle
Core Web Vitals are not one-and-done. You need a loop that fits how you ship.
A strategy that works well in teams:
- Track performance budgets for key pages, not just the homepage.
- Run repeatable lab tests in CI when possible, and spot-check field metrics after releases.
- Monitor regressions specifically for LCP, INP, and CLS, because improvements in one area can cause regressions in another.
For example, if you reduce JavaScript to speed LCP, but you also change rendering logic such that images now load without reserved space, CLS can spike. Your measurement loop needs to catch those shifts.
Use a targeted triage mindset
When a metric is off, do not jump straight to the biggest architectural change. Narrow down first.
Here is a simple triage framework I use during incident style debugging:
- Determine whether the issue is mostly LCP, mostly INP, or mostly CLS based on your reports.
- Identify the affected template or page types, not just the overall site.
- Find the common thread in traces, such as a specific component or third-party script.
- Fix one dominant cause, then re-measure.
- Repeat until the performance profile matches user expectations.
It sounds obvious, but it prevents the common trap of applying a broad set of tweaks at once, then losing the ability to tell what actually worked.
Practical optimization areas that often pay off
Core Web Vitals improvements usually come from a handful of recurring themes: loading critical assets sooner, reducing main-thread work, and keeping layout stable.
Asset delivery and bundling
A lot of speed problems start before code even runs: large bundles, duplicated libraries, and CSS delivered later than necessary.
Look for:
- Duplicate dependencies in vendor and app bundles.
- Overly broad imports that pull in unused code.
- Lack of code splitting for routes and heavy components.
- CSS that is not scoped correctly, causing bigger render costs.
The best improvements are usually structural. They last longer than a one-off compression tweak.
Rendering and component strategy
React, Vue, Svelte, and friends are great tools, but the rendering model matters. If you re-render huge portions of the page on every state update, INP suffers.
During optimization, focus on:
- Reducing the number of components that rerender in response to interaction.
- Ensuring expensive components are lazy-loaded when they are truly off the critical path.
- Avoiding layout reads and writes loops, like repeatedly reading element positions and then changing styles in a tight sequence.
These are not abstract concerns. They show up in traces as layout thrashing, long tasks, and delayed paints.
Media handling
Media is often the easiest place to get quick wins, especially for LCP and CLS.
The “right” approach depends on the design, but usually includes:
- Correct image dimensions to prevent layout shifts.
- A responsive image strategy that serves appropriate sizes.
- Preloading only the LCP media, not everything.
- Avoiding lazy loading for above-the-fold content.
For galleries, consider whether you truly need to load the full set immediately. If users only see one image first, load the first image eagerly and the rest progressively.
Trade-offs you have to make (and how to avoid self-inflicted harm)
Speed and UX are connected, but they are not the same thing. Some optimizations improve metrics while making the page feel worse. Here are a few trade-offs I have learned to respect.
Aggressive deferral can hurt perceived quality
Deferring scripts helps LCP and INP, but if the page becomes interactive late, users might see a page that looks loaded but does not respond.
This is especially common with pages that require authentication, personalization, or search filters. You might reduce JavaScript on the critical path, but then you delay the code that handles core interactions.
A better approach is to separate “initial paint” code from “full feature” code. Ship enough interaction logic to handle the first meaningful user action quickly.
Preventing CLS can reduce LCP if you go too far
Reserving space is good, but if you add placeholders that take up too much space or delay content, the LCP element can appear later.
For instance, if you hide the hero image until fonts and layout are stable, you might reduce CLS but worsen LCP. Users notice missing content more than small shifts in some designs.
Aim for stability, but keep the LCP element visible and painted as early as possible.
Libraries can be optimized, but workflows also matter
Replacing a library might reduce JavaScript size. However, real gains come from eliminating expensive work during interaction, not only shrinking download size.
Always verify with INP traces after bundle changes. Smaller bundles can still be slow if the runtime workload is the bottleneck.
A focused checklist for the next time your Core Web Vitals slip
If you want a quick, practical pass that tends to uncover the biggest causes, use this as a starting point:
- Confirm the reported LCP element and make sure it is not delayed by CSS or main-thread work.
- Ensure all above-the-fold images and posters have correct sizing attributes or aspect ratio.
- Check CLS sources in traces, especially fonts, ads, and late DOM insertion near the top of the page.
- Identify slow interactions tied to INP and reduce main-thread workload during those specific events.
- Audit third-party scripts for timing and interaction impact, then defer what is not essential.
Treat it as a triage guide, not a one-time checklist. The goal is repeatable improvement.
How to know you are improving the right thing
Core Web Vitals are numeric proxies. They correlate with user experience, but you should still validate with practical UX signals.
After changes, watch for:
- Reduced bounce rates on key landing pages.
- Better conversion on flows that involve interactions highlighted by INP.
- Fewer usability complaints about “the page jumps” or “buttons feel laggy.”
Also, use your own user feedback. If support tickets mention mis-clicks, layout jumps, or “nothing happens when I press,” that often maps directly to CLS and INP problems. Metrics tell you where to look. Real feedback tells you what to fix first.
Keep the improvements alive after the initial win
Once you fix one wave of performance issues, it is tempting to move on. That is how regressions happen. New components get added, new scripts get deployed, images get swapped for bigger ones, and critical CSS paths drift.
To keep Core Web Vitals healthy, treat them like part of quality, not a quarterly project:
- Require performance checks for new templates.
- Set ownership for the pages that carry the most traffic.
- Monitor field metrics after releases, not only during development.
- Keep a performance notes document that lists what you optimized and why, so the next person does not undo it unknowingly.
Core Web Vitals reward consistency. They measure what users experience repeatedly. Your job is to make that experience stable over time, not just perfect for a single test run.
When you approach LCP, INP, and CLS as a connected system, the work becomes clearer. You are not chasing three unrelated numbers. You are building pages that show meaningful content promptly, respond quickly when users act, and stay visually stable as everything loads. That is speed with a purpose, and it is the kind of optimization users can feel immediately.