Choosing Performance Work That Pays Off

Years of collective experience have produced a deep pool of web performance advice. The challenge isn't finding optimizations—it's figuring out which ones deserve your limited time. A theoretically powerful technique is useless if it's impractical to ship, while a broad best practice that most sites already follow offers no competitive edge.

This guide focuses on optimizations that meet three criteria:

  • Demonstrate the largest real-world impact on Core Web Vitals
  • Apply to the majority of websites regardless of stack or industry
  • Remain realistic for teams with ordinary engineering capacity

If you are unsure where to start or want to maximize your return on investment, these techniques provide the clearest path forward.

Prioritizing Interaction Responsiveness (INP)

Interaction to Next Paint remains the least mature Core Web Vital, having replaced the deprecated First Input Delay. Far fewer sites pass the "good" threshold for INP than did for its predecessor, meaning many teams are tackling interaction responsiveness for the first time. The following techniques address the root causes of poor INP in the order of their effectiveness.

Breaking Up Long Tasks

A task represents any discrete unit of browser work—rendering, layout, parsing, compilation, or script execution. When a task extends past 50 milliseconds, it becomes a long task. These long tasks block the main thread, preventing the browser from responding promptly to user input.

While minimizing JavaScript work is always a goal, you can reduce the impact of necessary work by yielding to the main thread frequently. Yielding allows rendering updates and other interactions to interleave with your script execution rather than waiting for your code to finish completely.

You can achieve clean yields with the Scheduler API's scheduler.yield() method. Unlike a simple setTimeout call, scheduler.yield() breaks up long tasks while ensuring your remaining work does not lose its place in the task queue.

Breaking up long tasks gives the browser more chances to slot in urgent, user-blocking work between your code's execution.

Shipping Less JavaScript

JavaScript payloads continue to grow across the web. Large scripts force the main thread to spend significant time parsing and compiling, particularly during the critical startup phase of a page. This creates the exact conditions where long tasks block user interactions.

Several practical approaches reduce your JavaScript footprint:

  • Prefer widely supported Baseline web platform features over custom JavaScript implementations of the same functionality.
  • Use Chrome DevTools' coverage tool to locate dead code in your scripts, then remove those unused portions to shrink the resources that must be parsed during startup.
  • Employ code splitting to place necessary-but-not-initial code into separate bundles that load only when required.
  • Audit tag manager implementations periodically; older, obsolete tags can be removed to reduce their contribution to the overall script weight.

Holding the Line on Rendering Updates

Script execution is not the sole determinant of responsiveness. Rendering is itself a costly operation, and any large rendering task will delay responses to user input just as effectively as a long script.

Rendering optimization depends heavily on what your specific page does, but three habits consistently prevent rendering tasks from becoming long tasks:

  • Structure JavaScript code to separate DOM reads from DOM writes, preventing forced synchronous layouts and repeated layout thrashing.
  • Keep DOM size modest, given the strong correlation between DOM size and layout calculation intensity.
  • Apply CSS containment to isolate off-screen or complex layout areas, letting the renderer skip unnecessary layout and painting work.

LCP: Find the bottleneck before you tune

Largest Contentful Paint (LCP) is the Core Web Vital that trips up the most sites — roughly 40% of origins in the Chrome UX Report miss the recommended threshold. The numbers from Chrome's own field data point to where the real delay lives: on most slow pages, the LCP image spend less than 10% of the total time actually downloading. The rest is waiting — an average of 1,290 ms at p75 just to start loading that resource. That's more than half the total budget for a fast LCP.

The fix starts with discoverability. The 2024 Web Almanac found that 35% of LCP images had URLs that weren't in the initial HTML response in a form the preload scanner can see — no <img src>, no <link rel="preload">. And only 15% of pages used fetchpriority at all.

Make the image discoverable and prioritizable

If the LCP element is an image — which it is on 73% of mobile pages — the goal is to get its URL into the HTML source without depending on JavaScript or CSS to reveal it. Otherwise, load starts only after those dependencies finish. Practical steps:

  • Use a real <img> tag with src or srcset. Non-standard attributes like data-src defer loading until scripting runs — the Web Almanac shows 7% of pages hide their LCP image behind one.
  • Prefer server-side rendering over client-side rendering. SSR puts the image in the HTML; CSR requires a JavaScript round-trip before the browser knows it exists.
  • For images referenced in external CSS or JS, add a <link rel="preload"> tag. Even inline styles evade the preload scanner — the image is technically in the source, but discovery is still blocked on parsing CSS.

Once the resource is discoverable, cut its load duration by raising its priority:

  • Add fetchpriority="high" to the LCP image's <img> or <link rel="preload"> tag so it competes better for bandwidth.
  • Remove loading="lazy" from the same element — lazy loading adds a viewport check before the fetch can start.
  • Move non-critical work out of the way. Defer scripts, lazy-load below-the-fold images and iframes, or load them asynchronously, so the LCP resource isn't queued behind them.

Skip the network: restorations and speculations

There's a hard ceiling on how fast you can push bytes over the wire, and squeezing below it gets expensive fast. The radical alternative is to not wait for the network at all — render the page before the user asks for it.

The back/forward cache handles the return trip. Pages restored from bfcache appear instantly, exactly as the user left them. Two things commonly disqualify pages: no-store directives on the response and leftover unload event listeners. Both are easy to audit for and remove.

For forward navigation, the Speculation Rules API can prerender the next page before the click happens. The trick is being right. Prerendering a wrong guess burns server and client resources, so the less certain you are about the next destination, the more conservative your speculation rules should be. When you're unsure, lean on analytics to pick the pages with the highest probability of being visited next.

A CDN for TTFB, including HTML

Restorations and speculations only help navigations you can predict. A cross-origin link or a fresh visit has no such shortcut — the browser blocks all subresource loading until it receives the first byte of the HTML document. That wait is Time to First Byte, and it's the floor beneath every other LCP optimization.

The two levers on TTFB are proximity and caching, and a CDN pulls both. Edge servers shorten the wire distance, and CDN-level caching means repeat requests never reach your origin. Many teams stop at static assets, but the Web Almanac reports only 33% of HTML documents are served from CDNs — a large opening for improvement.

  • Cache HTML aggressively, even if stale. Ask whether a few minutes of staleness is actually a problem before defaulting to always-fresh responses.
  • Push dynamic logic to the edge. Most modern CDNs support edge functions, and moving templating or personalization off the origin turns what would be a full round trip into an edge cache hit.

Every request served from the edge instead of the origin is a performance win. And even when you have to go all the way back to the origin, CDN infrastructure is typically optimized for that path as well — so it's rarely a loss either way.

Visual Stability: Tackling Cumulative Layout Shift

Cumulative Layout Shift (CLS) measures how much a page's content moves around after it is first painted. While this is a metric that many sites handle well, roughly a quarter of websites still fail to meet the recommended threshold of 0.1. For those sites, addressing the sources of unexpected movement is a direct path to a better user experience.

Reserve space for late-loading content

The most common cause of layout shift is content that loads after the initial render and pushes existing elements out of the way. The definitive fix is to reserve the necessary space in the page's layout before that content arrives.

For images, the solution is straightforward: explicitly define the width and height attributes. Currently, roughly 66% of pages host at least one image without defined dimensions, forcing the browser to treat it as having a height of 0px until it loads and discovers the true size. This causes a jump the moment the image is painted. Simply adding these attributes eliminates that shift.

The aspect-ratio CSS property offers similar control, and it works for more than just images. This Baseline feature is widely available and lets you define a proportional relationship between width and height. By setting an aspect-ratio and a dynamic width, the browser calculates the appropriate height automatically, preventing the layout from adjusting after the media loads.

Of course, some dynamic content—like third-party embeds—won't have a predictable size. Even then, you can mitigate the damage. Setting a reasonable min-height on the container is usually much better than leaving it at the default 0px. This approach allows the container to grow if the final content is taller, but prevents it from starting at zero and causing a jarring shift for smaller content.

Leverage the back/forward cache

The back/forward cache (bfcache) is a browser feature that takes a full snapshot of a page when you navigate away, allowing for instant loading if you return via the back or forward buttons. This is a critical performance feature: when bfcache works correctly, it makes both LCP and CLS essentially a non-issue by restoring the page from memory rather than re-rendering it.

Despite its significant benefits—the feature's browser-wide introduction in 2022 was responsible for the largest single-year improvement in CLS—a notable number of sites are still ineligible for it. If your pages don't handle sensitive information that must be fresh on every visit, ensure they are bfcache candidates. Remove any blockers and use Chrome's DevTools tester or the Not Restored Reasons API in the field to verify eligibility and pinpoint any issues.

Animate with compositor-friendly properties

Cookie banners and notification toasts that slide in from the edge of the screen are notorious contributors to CLS. They cause a problem when they shove page content aside, but even those that float on top can cause shifts, because animating layout-inducing CSS properties forces the browser to recalculate the page's layout on every frame. Any resulting shift that occurs more than 500 milliseconds after a user interaction will count against your CLS score.

HTTP Archive data suggests this is a real problem. Pages that animate any property capable of affecting layout are 15% less likely to achieve a "good" CLS score. The issue is even worse for specific properties: pages animating margin or border widths are assessed as "poor" at nearly double the rate of the web as a whole.

The solution is to favor properties that can be handled on the compositor thread without triggering layout. Animating top or left, even for absolutely positioned elements, causes shifts. Swapping those for transform: translateX() or transform: translateY() achieves the same visual movement without touching the page layout at all, offloading the work to the GPU. As a rule, avoid animating layout-inducing CSS properties unless you are directly responding to a user tap or key press—hover does not count. The "Avoid non-composited animations" Lighthouse audit will flag any offending CSS in your code.