Vibe Coding, Performance

How to Improve LCP in Next.js

How to Improve LCP in Next.js
To improve LCP in Next.js, the path is always the same: identify the single element that counts as your Largest Contentful Paint, measure how long it takes to render, then remove whatever is delaying it — usually an unoptimized image, a late-loading font, a slow server response, or render-blocking JavaScript. LCP is the Core Web Vital that most often drags down a Next.js app's page speed, and it's the hardest one to pass: per the 2025 Web Almanac, only about 62% of mobile pages hit a good score.

This guide walks the problem → diagnosis → fix path so you fix the byte that's actually on the critical path instead of guessing.

Join the Vibe Coding Community

Swap builds, trade feedback, and ship faster alongside other people vibe coding.

Join the Community

The One-Line Answer

To improve LCP in Next.js, find your LCP element in a Lighthouse or PageSpeed report, then make it render sooner: serve it as an optimized next/image with the priority prop, preload its font with next/font, speed up the server response for the page, and stop render-blocking scripts from holding up first paint. Re-measure at the 75th percentile after each change.

Don't optimize blind. It's the same measure-don't-guess discipline that works for any stubborn bug: almost every LCP problem traces back to one element loading too late — the whole job is to find that element and shorten its path.

What LCP Is, Precisely

Largest Contentful Paint (LCP) measures how long it takes for the largest visible content element — usually a hero image, a video poster frame, or a big block of heading text — to render in the viewport, timed from when the page starts loading.

Google's thresholds, measured at the 75th percentile of real users across mobile and desktop:

• Good: LCP ≤ 2.5 seconds
• Needs improvement: 2.5 s – 4.0 s
• Poor: > 4.0 seconds

The 75th-percentile part matters: 75% of your real visitors need a good experience for the page to pass. Your dev machine on fast Wi-Fi is not representative — this is a big reason a Next.js app can feel fast locally but slow in production.

Step 1 — Find Your LCP Element

You cannot fix LCP until you know which element it is. Two reliable ways:

Run Lighthouse or PageSpeed Insights. Use Chrome DevTools → Lighthouse, or npx lighthouse <url>, against a production build. Both name the LCP element explicitly and give a timing breakdown. PageSpeed Insights also shows field data (real Chrome User Experience Report numbers) alongside the lab test — the field data is what Google actually ranks on, so trust it over the lab score when they disagree.

Record a load in DevTools. For a live look, open DevTools → Performance, record a load, and check the LCP marker on the timeline. It points at the exact node and shows the four sub-parts of LCP: Time to First Byte (TTFB), resource load delay, resource load time, and render delay. Whichever sub-part is largest tells you which fix below to reach for.

Always test a next build && next start production build, not next dev. Dev mode is unoptimized and will lie to you about page speed — it's also where a whole class of production-only bugs hides.

Step 2 — Fix the LCP Element Itself

Fix A — Use next/image with priority for a hero image

If your LCP element is an image (it usually is), the biggest single win is next/image with the priority prop. priority preloads the image and sets fetchpriority="high", so the browser fetches it early instead of discovering it late:

import Image from 'next/image'

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Product screenshot"
      width={1200}
      height={600}
      priority          // preloads + fetchpriority="high"
      sizes="100vw"
    />
  )
}

Rules for the LCP image specifically:

• Add priority only to the above-the-fold LCP image. Adding it to everything defeats the purpose — the browser can only prioritize so much at once.
• Set correct sizes so next/image serves an appropriately sized source instead of a giant one.
• Never lazy-load the LCP image. If it's the largest element in the viewport, lazy-loading it guarantees a slow LCP.
• Prefer modern formats (next/image serves AVIF/WebP automatically when configured), and make sure the origin image isn't a 4MB PNG.

Fix B — If the LCP Element Is Text, Fix the Font

When the largest element is a heading or paragraph, LCP is often delayed by a web font that loads late or blocks rendering. Use next/font, which self-hosts the font, removes the render-blocking network request to a font CDN, and applies font-display automatically to avoid invisible text:

import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',   // text renders immediately in a fallback, swaps when ready
})

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

Self-hosting via next/font also eliminates an extra DNS lookup and connection to a third-party font host — both of which push text-LCP later.

Step 3 — Fix a Slow Server Response (TTFB)

If your DevTools breakdown shows a large TTFB, the browser is waiting on the server before it can even start on the LCP element. In Next.js this usually means the page is doing slow work before it can respond:

• Cache what you can. Static or incrementally-regenerated routes serve near-instantly. If a page doesn't need per-request freshness, don't force it dynamic — an unnecessary cache: 'no-store' or dynamic = 'force-dynamic' turns a cacheable page into a slow one.
• Parallelize data fetching. Awaiting three requests in sequence in a Server Component stacks their latency. Fire them together with Promise.all so the page renders as soon as the slowest one returns.
• Stream with Suspense. Wrap slow, below-the-fold sections in <Suspense> so Next.js streams the fast shell (including your LCP element) immediately and fills in the rest later, instead of blocking the whole response on the slowest query.

Shaving TTFB helps every Core Web Vital at once, because everything downstream starts sooner.

Step 4 — Remove Render-Blocking JavaScript

Even with a fast server and an optimized image, heavy client JavaScript can delay when the LCP element paints, and it hurts INP (Interaction to Next Paint, "good" ≤ 200 ms) at the same time. Reduce it by:

• Keeping work on the server. In the App Router, components are Server Components by default and ship zero client JS. Only add 'use client' where you truly need interactivity, and push it to small leaf components rather than whole pages. (That same server/client boundary is what keeps your secrets out of the browser bundle, too.)
• Deferring non-critical scripts. Load third-party tags (analytics, chat widgets, A/B tools) with next/script using strategy="lazyOnload" or strategy="afterInteractive" so they don't compete with first paint.
• Cutting the bundle. A bloated first-load bundle is a common reason a Next.js app is slow in production. If your build's First Load JS is high, split it — trimming your client bundle is a topic of its own, but it's often the difference that pulls LCP back under 2.5 seconds.

Step 5 — Re-measure at the 75th Percentile

After each change, rebuild and re-test with Lighthouse or PageSpeed Insights, and watch the field data over the following days — lab scores update instantly, but the CrUX field numbers Google ranks on reflect real users and lag by up to 28 days. You're done when the 75th-percentile LCP sits comfortably under 2.5 seconds on mobile, not just on your laptop.

A quick note on the other vitals while you're in here: improving LCP often nudges CLS (visual stability) too, because giving images explicit width/height — which next/image requires — prevents the layout shift that a late-loading hero would otherwise cause.

Quick Checklist

• Identify the LCP element with Lighthouse or PageSpeed Insights on a production build.
• Read the LCP sub-part breakdown (TTFB / load delay / load time / render delay) to pick the right fix.
• Serve a hero-image LCP with next/image + priority; never lazy-load it.
• For a text LCP, self-host the font with next/font and use display: 'swap'.
• Cut TTFB: cache cacheable routes, parallelize fetches with Promise.all, stream with <Suspense>.
• Remove render-blocking JS: keep work on Server Components, defer third-party scripts with next/script, trim the bundle.
• Re-measure at the 75th percentile and trust field data over lab scores.

Find What's Slowing Your LCP — Without Setting Any of This Up

Running Lighthouse, reading the LCP sub-part breakdown, and tracing it back to the right image, font, or fetch is the reliable way to do this — but it's also the audit that's easiest to postpone while you're shipping features. That's the gap Differ closes. Connect your GitHub repo and Differ runs a code scan on your very first import — no live traffic, no Lighthouse runs, no config — and returns a free report that flags the performance issues hurting your page speed, including unoptimized images, render-blocking patterns, and oversized client bundles that push LCP past 2.5 seconds. It's the fastest way to see which parts of your Next.js app are slowing first paint, so you can go straight to the fixes above.

Connect your repo to Differ and get your free report →