Written by

Halkwinds Editorial Team

Halkwinds Research & Editorial

Published April 17, 2026
Blog image
Application

Frontend Performance Optimization: Core Web Vitals and Beyond

Practical techniques for LCP, CLS, INP, bundle splitting, image optimization, and rendering strategies that move the needle.

Frontend performance is no longer a nice-to-have you address in a pre-launch cleanup sprint. It directly shapes conversion rates, search ranking, and how your engineering team spends its time. For engineering managers, the challenge is rarely a lack of awareness — everyone knows Core Web Vitals matter. The real problem is prioritization: which of the dozens of possible optimizations actually move the needle, how do you measure improvement reliably, and how do you keep performance from regressing after every feature ships? This guide walks through the techniques that consistently deliver results — LCP, CLS, and INP improvements, bundle splitting, image optimization, and rendering strategy — with enough specificity that your team can act on Monday morning.

  • Background / Why This Matters
  • Core Concepts and Architecture
  • Implementation Strategy
  • Scaling and Operational Considerations
  • Common Mistakes / What to Avoid
  • Frequently Asked Questions
  • Conclusion

Background / Why This Matters

Google's Core Web Vitals became a ranking signal in 2021, and the metrics themselves have evolved since. As of March 2024, Interaction to Next Paint (INP) replaced First Input Delay (FID) as the responsiveness metric. That change matters because INP is harder to pass — it measures the latency of all interactions across a page session, not just the first one. Teams that comfortably passed FID often fail INP on interaction-heavy pages.

The three current Core Web Vitals are:

  • Largest Contentful Paint (LCP) — loading performance. Good: ≤ 2.5s.
  • Cumulative Layout Shift (CLS) — visual stability. Good: ≤ 0.1.
  • Interaction to Next Paint (INP) — responsiveness. Good: ≤ 200ms.

Why should an engineering manager care beyond SEO? Research consistently suggests that faster pages correlate with higher conversion and lower bounce rates, and case studies from major retailers have reported meaningful revenue lifts from load-time improvements — though exact figures vary by industry and baseline. Just as importantly, a slow frontend is a symptom of accumulating technical debt: oversized bundles, unoptimized third-party scripts, and render-blocking resources that also make your codebase harder to maintain.

Actionable takeaway: Establish your current baseline before doing anything else. Pull field data from the Chrome User Experience Report (CrUX) via PageSpeed Insights, and set up lab testing with Lighthouse. Field data reflects real users; lab data gives you a controlled, repeatable benchmark. You need both.

Core Concepts and Architecture

Before optimizing, understand what each metric actually measures at the browser level — this is where most teams go wrong by treating symptoms instead of causes.

LCP: what's really slow

LCP is almost always one of four things: slow server response (TTFB), render-blocking JavaScript and CSS, slow resource load time (usually a hero image or web font), or client-side rendering delay. On a client-rendered React SPA, the LCP element frequently can't even begin rendering until a large JavaScript bundle downloads, parses, and executes. This is why rendering strategy is foundational to LCP.

Rendering strategies compared

Your choice of rendering strategy sets a performance ceiling. Here's how the common approaches stack up:

Strategy LCP characteristics Best for Tooling example
Client-Side Rendering (CSR) Slow — blocked by JS download/parse Auth-gated dashboards, internal apps Create React App, Vite SPA
Server-Side Rendering (SSR) Fast first paint, TTFB-dependent Dynamic, personalized content Next.js, Remix
Static Site Generation (SSG) Fastest — served from CDN edge Marketing, blogs, docs Next.js, Astro
Incremental Static Regeneration (ISR) Fast, with fresh data Catalogs, high-traffic content Next.js
React Server Components (RSC) Fast — ships less client JS Mixed static/dynamic apps Next.js App Router

CLS: the layout stability model

CLS accumulates every time visible content shifts unexpectedly. The usual culprits are images without dimensions, ads or embeds injected after load, web fonts causing FOIT/FOUT reflow, and dynamically injected content pushing the page down. The mental model is simple: reserve space for anything that loads late.

INP: the main-thread bottleneck

INP is fundamentally about main-thread availability. When a user clicks and your app runs 300ms of synchronous JavaScript before painting, that's your INP. Long tasks — anything over 50ms — are the enemy. Hydration in SSR frameworks is a common INP killer because the entire page becomes interactive in one expensive pass.

Actionable takeaway: Map each of your key pages to a rendering strategy deliberately. A marketing homepage rendered as CSR is an unforced error; move it to SSG or SSR. Halkwinds regularly audits application architectures and finds that a rendering-strategy realignment alone resolves a large share of LCP failures before any micro-optimization begins.

Implementation Strategy

With the concepts clear, here is a concrete, sequenced plan. Work top-down — the earlier items have the highest leverage.

1. Fix bundle size with code splitting

Ship less JavaScript. Start by analyzing your bundle with webpack-bundle-analyzer or the Next.js bundle analyzer. Look for:

  • Route-based splitting — load code per route, not all upfront. Next.js does this automatically per page; in a plain React SPA use React.lazy and Suspense.
  • Heavy dependencies — a 300KB date library or charting package loaded on every page. Replace moment.js with date-fns or day.js, and lazy-load charts.
  • Duplicate dependencies — multiple versions of the same library pulled in by transitive dependencies. Deduplicate with your package manager.

Component-level dynamic imports (next/dynamic or React.lazy) let you defer non-critical UI like modals, rich-text editors, and below-the-fold widgets until they're needed.

2. Optimize images aggressively

Images are the LCP element on most content pages. Non-negotiables:

  • Serve modern formats — WebP or AVIF — with fallbacks.
  • Set explicit width and height (or CSS aspect-ratio) to prevent CLS.
  • Use responsive srcset so mobile users don't download desktop-sized images.
  • Add loading="lazy" for below-the-fold images, but never lazy-load the LCP image — it delays your most important paint.
  • Preload the hero image with <link rel="preload"> or Next.js priority.

The Next.js <Image> component handles most of this automatically, including format negotiation and lazy-loading defaults.

3. Break up long tasks for INP

Audit interactions with the Chrome DevTools Performance panel. When you find a long task, break it up:

  • Defer non-urgent state updates with startTransition in React 18+.
  • Yield to the main thread with scheduler.postTask or a setTimeout-based yield for heavy loops.
  • Debounce expensive handlers (search-as-you-type is a classic offender).
  • Memoize expensive renders with useMemo and React.memo — but profile first; premature memoization adds noise.

4. Tame third-party scripts

Analytics, chat widgets, tag managers, and A/B testing tools are frequently the single largest source of main-thread blocking. Use Next.js <Script> with the strategy="lazyOnload" or afterInteractive" options, load non-critical scripts after interaction, and consider running heavy third parties in a web worker via Partytown.

5. Optimize fonts

Use font-display: swap, self-host fonts (or use next/font which does this and eliminates layout shift by computing fallback metrics), preload the primary font file, and subset fonts to the characters you actually use.

Actionable takeaway: Sequence the work by leverage: rendering strategy and bundle size first, then images, then INP long tasks, then third parties and fonts. Measure after each change so you can attribute gains and avoid shipping optimizations that don't help.

Scaling and Operational Considerations

A one-time optimization sprint is worthless if performance silently regresses over the next quarter. The goal is to make performance a durable property of your delivery pipeline.

Continuous measurement in CI

Integrate Lighthouse CI into your pull-request pipeline. Configure performance budgets — for example, fail the build if the main bundle exceeds 200KB gzipped, or if LCP in the lab exceeds a threshold. This turns performance from a periodic fire drill into a guardrail that catches regressions before merge.

Field monitoring with RUM

Lab tests miss real-world conditions: slow devices, poor networks, and geographic latency. Deploy Real User Monitoring using the web-vitals JavaScript library to capture LCP, CLS, and INP from actual sessions, and segment by device type and route. You'll often discover that your INP problem is concentrated on mid-tier Android devices your team never tests on.

Budgets and ownership

Assign an owner for each Core Web Vital, or better, make performance a shared standard enforced by tooling rather than heroics. Establish budgets per route category — a data-heavy dashboard has a different realistic budget than a landing page.