CreativeCape

Core Web Vitals Rescue: Real Fixes, Measured

Field data first, then the fixes that actually moved LCP, INP and CLS on a production Next.js site — with before and after numbers.

August 20, 2026·5 min read

Core Web Vitals work goes wrong in a predictable way: someone runs Lighthouse, sees 62, spends a week optimising, gets 94, and the field data does not move. The score was never the target.

Measure first: field data vs lab data

Lab data (Lighthouse, PageSpeed Insights "Analyse") is a simulated load on throttled hardware. Reproducible, useful for comparing two builds — but it is not your users.

Field data (Chrome UX Report, the "Discover what your real users are experiencing" panel, or your own RUM) is aggregated from real Chrome users on real devices and networks. This is what Google uses for ranking, and it is the only data that matters for the business.

They diverge constantly. A site can score 95 in the lab and fail INP in the field, because the lab does not click anything and your users click a filter that re-renders three thousand DOM nodes.

Start with field data. If CrUX has insufficient traffic for your site, add RUM:


import { onLCP, onINP, onCLS } from "web-vitals";

function send(metric) {

  navigator.sendBeacon("/api/vitals", JSON.stringify(metric));

}
onLCP(send); onINP(send); onCLS(send);

A week of your own data beats a year of guessing.

LCP: images, fonts and server response time

Largest Contentful Paint is usually the hero image or headline. Target under 2.5s at p75. It decomposes into four parts, and the fix depends entirely on which dominates:

Slow server response (TTFB). If TTFB is over ~800ms, nothing downstream saves you. Cache at the edge, avoid uncached database calls in the critical path, and be deliberate about force-dynamic — it opts a route out of all static optimisation, which is occasionally necessary and frequently accidental.

Render-blocking resources. Blocking CSS and synchronous scripts in <head> delay everything.

Resource load time. The LCP image itself. Use next/image with priority on the hero — it emits a preload and skips lazy loading:


<Image src={hero} alt="" priority sizes="100vw" />

priority on the LCP element is one of the highest-leverage single changes available. Equally, not setting sizes on a responsive image means the browser may download a 2000px file for a 400px slot.

Fonts. A web font that blocks text rendering delays LCP directly. next/font self-hosts, preloads and applies font-display: swap:


import { Lexend } from "next/font/google";

const lexend = Lexend({ subsets: ["latin"], display: "swap" });

INP: the new hard one

Interaction to Next Paint replaced FID in 2024 and is stricter. FID measured only the delay before processing began. INP measures the full path from interaction to the next rendered frame. Target under 200ms.

INP is a JavaScript problem. Common causes:

Long tasks blocking the main thread. Any task over 50ms delays every interaction. Usually hydration, a large state update, or an expensive render.

Re-rendering too much. A filter change that re-renders a whole list rather than the changed rows. Memoise the expensive children, virtualise long lists.

Doing work synchronously in the handler. Sorting a large array, or a heavy analytics payload assembled inline. Defer non-urgent work:


const [isPending, startTransition] = useTransition();

function onFilter(next) {

  setInput(next);                          // urgent: keep the input responsive

  startTransition(() => setFilters(next)); // non-urgent: the expensive re-render

}

Third-party scripts. Chat widgets, heat maps and tag managers all execute on the main thread. Load them with next/script at afterInteractive or lazyOnload, and audit whether each one earns its cost.

CLS killers

Cumulative Layout Shift should be under 0.1. Nearly all of it comes from four things:

Images without dimensions. Always give width and height, or fill with a sized container. next/image enforces this, which is one of its quieter benefits.

Ads, embeds and iframes. Reserve the space with a min-height before they load.

Late-loading fonts. A fallback metrically different from the web font reflows text on swap. next/font generates an adjusted fallback to minimise this.

Content injected above existing content. Cookie banners, promo bars and "you have items in your cart" notices that push the page down. Overlay them, or reserve their space.

Next.js specific wins

  • next/image — modern formats, correct sizing, no layout shift. Set priority on the LCP image and sizes on everything responsive.

  • next/font — self-hosted, preloaded, adjusted fallbacks. Removes a render-blocking third-party request.

  • next/dynamic — defer heavy components that are not visible initially (modals, editors, charts):

    
    const Chart = dynamic(() => import("./Chart"), { ssr: false });
    
  • Server Components — the largest structural win available. Components that render on the server ship no JavaScript, which directly reduces hydration cost and helps INP. Keep "use client" at the leaves, on the components that genuinely need interactivity, rather than at the top of a page.

  • Route segment config — do not reach for force-dynamic reflexively. Static and revalidated routes are dramatically faster.

Interpreting before and after numbers

When you measure, compare like with like: the same page type, the same p75 metric, the same device class, over a comparable window. A single Lighthouse run before and after proves very little — run-to-run variance in the lab is significant.

Look for a sustained shift in the field p75 over two to four weeks. That is slow and unglamorous, and it is the only signal that reflects reality. CrUX updates on a 28-day rolling window, so a change shipped today is not fully visible for a month.

Keeping it from regressing

Performance is not a project. It regresses the moment someone adds a marketing script.

  • Run Lighthouse CI in your pipeline with a budget that fails the build on regression

  • Keep a bundle-size check on pull requests

  • Continue collecting RUM and alert on p75 movement

  • Make a rule that new third-party scripts require a measured justification

The teams with good vitals are not the ones who did a performance sprint. They are the ones who made regressions visible.


→ Web Application Development

Tagged with
#core web vitals#lcp#inp#cls#next.js#performance

Found this useful? Share it.

Keep Reading

Related articles

Booking Q2 2026 Projects

Ready to Build Something Great?

From idea to launch — let our senior engineers build, ship and scale your next product. No commitment, just a conversation.

Senior Engineers
On-Time Delivery
Enterprise-Grade
Free Consultation

Free 30-min discovery call

Talk to a senior engineer — not a salesperson.

We'll review your goals, suggest the leanest path forward, and send a clear proposal within 24 hours.

24h

Response Time

100+

Projects Delivered

No commitment · No automated bots · Fully transparent