Back
Engineering Guide

Web Performance Optimization Guide 2026

The complete optimization reference for Core Web Vitals: LCP, INP (which replaced FID in March 2024), CLS, and TTFB. Covers HTML, CSS, JavaScript, images, fonts, and the network-level work that moves Search Console scores. Written for engineers who need to ship faster sites, not just hit a Lighthouse number.

Last updated: May 2026 · Reviewed by FreeDevTool web performance team · ~6,000 words · 24-minute read

1. Why Core Web Vitals matter

In May 2021 Google rolled Core Web Vitals into Page Experience as a confirmed ranking signal. In March 2024 they replaced First Input Delay (FID) with Interaction to Next Paint (INP), giving us a stricter definition of responsiveness. The current trio — LCP, INP, CLS — feeds the Search Console "Core Web Vitals" report and the Page Experience signal that influences rankings.

Beyond rankings, the business case is well-documented:

  • Google's own data: a 100ms LCP improvement correlated with 8% conversion lift in a 2017 retail study; subsequent studies have confirmed similar magnitudes.
  • Akamai found that a 100ms delay in load time can hurt conversion rates by 7%.
  • The BBC reported losing 10% of users for every additional second of load time.

Site speed is one of the few engineering investments with provable revenue lift. The framework below is what actually moves the metrics.

2. The four metrics — LCP, INP, CLS, TTFB

MetricMeasuresGoodNeeds workPoor
LCP (Largest Contentful Paint)Time until the largest element above the fold renders≤ 2.5s2.5–4s> 4s
INP (Interaction to Next Paint)Worst input latency across the page session≤ 200ms200–500ms> 500ms
CLS (Cumulative Layout Shift)Sum of unexpected layout shifts≤ 0.10.1–0.25> 0.25
TTFB (Time to First Byte)Time from request to first response byte≤ 0.8s0.8–1.8s> 1.8s

Google evaluates the 75th percentile of real-user data across the past 28 days. Synthetic Lighthouse scores are useful for development but Search Console uses field data from the Chrome User Experience Report (CrUX).

3. LCP optimization — the biggest lever

LCP is the metric that responds most to engineering work. The largest above-the-fold element is usually the hero image or the main headline. Make it appear faster.

  1. Identify your LCP element. Chrome DevTools → Performance → Web Vitals overlay highlights it. Often it is a hero image or the H1.
  2. Preload the LCP image. <link rel="preload" as="image" href="/hero.webp" fetchpriority="high"> in the head. Browser starts the download before HTML parsing reaches the <img>.
  3. Use fetchpriority="high" on the hero image element. Tells the browser to prioritize this download over other assets.
  4. Avoid lazy-loading above-the-fold images. loading="lazy" on a hero image hurts LCP; only use it for below-fold images.
  5. Use modern formats. WebP or AVIF for photos; SVG for logos and icons. WebP averages 30% smaller than JPEG; AVIF averages 50% smaller.
  6. Serve responsive images. <img srcset> with multiple sizes lets the browser pick the right one for the viewport. Don't ship a 4K image to a phone.
  7. Inline critical CSS. The first 14KB of HTML response can include the CSS needed for the above-the-fold render. Defer the rest.
  8. Reduce server response time. If TTFB is > 800ms, you have a backend problem to fix before any frontend optimization helps.
Image to Base64 EncoderFor tiny critical images (icons, logos under 2KB), inline them as data URIs to skip a request entirely.

4. INP optimization — the new metric

INP (Interaction to Next Paint) replaced FID in March 2024. Where FID measured only the first input delay, INP measures the worst input delay across the entire page session — clicks, taps, key presses. It is a much more honest measure of perceived responsiveness.

The 200ms threshold for "good" is strict. Most pages with heavy JavaScript fail it. Optimization patterns:

  1. Break up long tasks. Any JavaScript task > 50ms blocks the main thread and contributes to bad INP. Split with scheduler.yield() (Chrome 129+) or fall back to setTimeout(..., 0) / requestIdleCallback.
  2. Use content-visibility: auto on off-screen sections to skip rendering work until they scroll into view.
  3. Defer non-critical JavaScript. Analytics, A/B testing scripts, social embeds — load them after the page is interactive. Use <script defer> or <script async>.
  4. Audit third-party scripts. Tag managers, ad scripts, chat widgets are typical INP killers. Each one runs JavaScript on every interaction.
  5. Use Web Workers for heavy computation. JSON parsing of large blobs, image processing, encryption — push off the main thread.
  6. Code-split your JavaScript. Don't ship the checkout-flow code on the homepage. Use dynamic imports.
  7. Memoize React components. React.memo, useMemo, useCallback prevent re-renders. Profile with React DevTools.
  8. Debounce input handlers. Search-as-you-type with no debounce fires a request on every keystroke. 250ms debounce keeps INP healthy.
JavaScript MinifierSmaller JS = less parse + compile time = better INP. Pair with the HTML and CSS minifiers below.

5. CLS optimization — eliminate layout shift

Cumulative Layout Shift measures unexpected movement after initial render. Hero image without dimensions, web fonts that swap mid-render, late-loading ads — all cause CLS. The fixes are mostly about reserving space.

  1. Set explicit width and height on every image. Either via width and height attributes or CSS aspect-ratio. Prevents the post-load reflow.
  2. Reserve space for ads, embeds, iframes. A min-height placeholder beats a "the ad will load and push everything down" experience.
  3. Use font-display: optional or swap. Pair with size-adjust to match fallback metrics. The fontaine library and CSS @font-face descriptors automate this.
  4. Don't insert content above existing content. Cookie banners, alerts, "you're in EU" prompts — stack them at the bottom or use overlays, not inserts that push the page down.
  5. Animate with transform, not layout properties. transform: translate and opacity are GPU-accelerated and don't trigger layout. Avoid animating top, left, width, height.
  6. Test in slow 3G mode. CLS often hides on a fast connection. DevTools → Network → Slow 4G + CPU 4× slowdown reveals real shifts.

6. Asset optimization — HTML, CSS, JS

Every byte you ship has to be downloaded, parsed, executed. Minification, compression, and code-splitting are the three levers.

HTML

  • Minify in production. 15–35% byte reduction on hand-written HTML. The HTML Minifier handles strip-comments + collapse-whitespace + boolean-attribute compression.
  • Enable Brotli. Brotli compresses 15–25% better than gzip on text. Cloudflare, Fastly, Akamai, Vercel all have it; turn it on if it isn't already.
  • Inline critical CSS. The first 14KB matters most.

CSS

  • Minify and tree-shake unused CSS. The CSS Minifier handles compression. Tools like PurgeCSS or the built-in Tailwind JIT remove unused selectors.
  • Use CSS custom properties for theming. One stylesheet, dark/light variants via custom property reassignment.
  • Avoid @import in CSS. Each import adds a sequential request. Use bundler imports or HTTP/2 push.

JavaScript

  • Minify with Terser, esbuild, or SWC. The JS Minifier for one-off scripts. For builds, configure your bundler (Vite, Webpack, Rollup) to minify.
  • Code-split routes. Each route should be its own chunk so users only download what they need.
  • Preload critical chunks, defer the rest.
  • Audit your bundle. webpack-bundle-analyzer, vite-bundle-visualizer, source-map-explorer. Unused dependencies are the easiest wins.
  • Drop legacy polyfills. If you don't support IE11, you don't need most of core-js.

7. Image and font optimization

Images

  • Use AVIF for photos when possible (~50% smaller than JPEG). Fall back to WebP, then JPEG.
  • Use <picture> for format negotiation. <source type="image/avif">, <source type="image/webp">, <img> JPEG fallback.
  • Responsive images via srcset. Generate 1×, 1.5×, 2× variants; let browser pick.
  • Lazy-load below-fold images. loading="lazy" is browser-native; no library needed.
  • Set explicit dimensions. Prevents CLS.
  • Use a CDN with image transforms. Cloudflare Images, Cloudinary, Imgix, Vercel Image Optimization. Source-of-truth image; CDN serves transformed variants.
  • Inline tiny critical images. Logos, sprite icons under 2KB benefit from inlining as data URIs (use the Image to Base64 tool).

Fonts

  • Self-host or use the same CDN as your other assets. Cross-origin font requests block render until DNS + TLS + fetch complete.
  • Use font-display: optional (best for CLS) or swap (best for visibility). Never block.
  • Subset fonts to the characters you need. A 200KB font becomes 30KB after subsetting to Latin-only.
  • Preload critical fonts. <link rel="preload" as="font" type="font/woff2" crossorigin>.
  • Use system fonts for body copy when possible. Zero KB, instant render.
  • Match fallback font metrics. CSS size-adjust, ascent-override, descent-override, line-gap-override on the fallback prevent the layout shift when the web font loads.

8. Network-level wins (CDN, caching, HTTP/3)

  1. Use a CDN. Cloudflare, Fastly, Akamai, Vercel, Netlify. Static assets served from edge locations cut TTFB by hundreds of ms.
  2. Enable HTTP/3. QUIC over UDP eliminates TCP head-of-line blocking. Cloudflare has HTTP/3 on by default; check your CDN.
  3. Set proper cache headers. Static assets: Cache-Control: public, max-age=31536000, immutable. HTML: short max-age + revalidate. API responses: depends on freshness needs.
  4. Use stale-while-revalidate for HTML so the user gets a fast response while the CDN refreshes in the background.
  5. Preconnect / DNS-prefetch third-party origins. <link rel="preconnect" href="https://fonts.googleapis.com"> early in head.
  6. Audit what's blocking render. Synchronous third-party scripts, render-blocking CSS, fonts without font-display. DevTools → Network → Initiator column shows the chain.
  7. Use the Byte Converter to sanity-check asset sizes — KB vs KiB matters when you compare against budget targets.

9. Measuring real-user data

Synthetic tests (Lighthouse, WebPageTest) are useful for development and regression. But Google ranks on field data from real users. The measurement stack you actually need:

  • Google Search Console — Core Web Vitals report. Free, authoritative. Shows what Google ranks on. Set this up first.
  • Chrome User Experience Report (CrUX) dashboard. Free, public-domain field data from real Chrome users. g.co/web-vitals dashboard.
  • web-vitals JavaScript library. onLCP, onINP, onCLS hooks. Send real-user metrics to your analytics. Tiny (~3KB).
  • Real User Monitoring (RUM). Vercel Analytics, Cloudflare Web Analytics, Sentry, Datadog RUM, or roll your own using web-vitals → analytics endpoint.
  • Lighthouse in CI. Catches regressions on every PR. Configure performance budgets that fail the build.

10. Authoritative references

Performance tools referenced in this guide

Optimization helpers, all browser-based.