Core Web Vitals in Next.js: next/image and next/font Basics
Learn web performance basics in Next.js: LCP, CLS and INP, next/image sizes and srcset, priority, and next/font, with the real files this site ships.
By Matthews WongReviewed on
Key takeaway
Core Web Vitals are Google's three field metrics for page experience: LCP for loading, INP for responsiveness and CLS for visual stability. In Next.js, next/image and next/font target the first and last: they size, preload and self-host the images and fonts that decide when the main content appears and whether it jumps.
You will be able to
- Name the three Core Web Vitals and the thresholds that count as good.
- Size images with width, height, fill and sizes, and use priority only where it helps.
- Load a font with next/font so it adds no request to a third party and no shift.
Step through the diagram
Press Play, or step with the arrows. Every step is written out below the diagram.
One hero image, start to paint
The HTML carries sizes and a srcset, the browser picks one width, the image optimizer returns a resized file, and priority plus reserved space keep the largest element fast and still.
Reviewed on 17 October 2026
Keys: ← → step · Home / End · K play or pause
LCP is the render time of the largest image or text block in view. Often that is a hero image, so the path from HTML to pixels is the one to fix.
The page ships a srcset of candidate widths and a sizes hint for how wide the image will be shown. The browser uses sizes to choose the most appropriate candidate.
sizes="(max-width: 768px) calc(100vw - 2rem), 768px"Predict the next step An image uses fill and a sizes string. What does the generated srcset hold? (Only 1x and 2x · A full list of widths such as 640w and 750w · One fixed file)With sizes, Next.js generates a full srcset of widths. Without it, a limited 1x and 2x set suits fixed-size images. The browser downloads only the one candidate it picked.
The optimizer returns the image at that width in a modern format. This site lists WebP only, a deliberate choice recorded in next.config.ts, so the origin encodes quickly.
Predict the next step The hero is the LCP element. How should it load? (Lazy, to save bytes · Preloaded, never lazy)priority preloads the image and disables lazy loading, which web.dev says never to do to the LCP image. Use it on the one or two images above the fold, not everywhere.
priorityWidth and height give the browser an aspect ratio, so space is held before bytes arrive and nothing jumps. That is how images stay out of Cumulative Layout Shift.
What are the three Core Web Vitals, and what counts as good?
Core Web Vitals are three metrics measured on real visits. Largest Contentful Paint, LCP, measures loading. Interaction to Next Paint, INP, measures responsiveness. Cumulative Layout Shift, CLS, measures visual stability. web.dev defines good as an LCP within 2.5 seconds of when the page starts loading, an INP of 200 milliseconds or less, and a CLS of 0.1 or less, each assessed at the 75th percentile of page loads.
Read more (2)Show less
INP replaced First Input Delay as a stable Core Web Vital in 2024, so older tutorials that teach FID are out of date. INP looks at the latency of all clicks, taps and key presses, and an interaction's latency has three parts: input delay, processing duration and presentation delay.
LCP itself splits into four subparts: time to first byte, resource load delay, resource load duration and element render delay. Knowing which part is slow tells you which fix to try. This lesson makes no promise about how many milliseconds any change saves; measure your own pages.
How do width, height and priority protect LCP and CLS?
width and height on next/image are the intrinsic size in pixels. They let the browser infer an aspect ratio and reserve space, which avoids layout shift while the image loads. They do not set the rendered size; CSS does. You must set both unless the image is statically imported or uses fill. web.dev gives the same advice for plain HTML: always include width and height on images.
Read more (2)Show less
fill makes the image expand to its parent, which must be positioned relative, fixed or absolute. This site's blog hero does exactly that inside a wrapper with a fixed aspect ratio, so the box exists before the image does.
priority preloads the image and turns off lazy loading; the default is to lazy load. web.dev warns never to lazy-load the LCP image, because that always adds resource load delay, and says setting high priority on more than one or two images stops it from helping. So the blog hero sets priority and nothing else on that page does.
What does sizes do, and why can fill bloat srcset?
The browser does not know how wide a CSS-sized image will be when it reads the HTML, so sizes tells it. If sizes is missing, the browser assumes the image is as wide as the viewport, which can download an unnecessarily large file. Use sizes whenever an image uses fill or CSS makes it responsive.
Read more (2)Show less
sizes also changes the markup. Without it, Next.js writes a limited srcset, for example 1x and 2x, suited to fixed-size images. With it, Next.js writes a full srcset of widths such as 640w and 750w. That is right for a hero that spans the screen, and wasteful for a small icon.
This site hit the wasteful case. The certification marquee shows 44 pixel logos, and a comment in the carousel records that fill with a pixel sizes lists every configured width in srcset, about 1 KB of HTML per tile. The fix was fixed width and height, which yields just 1x and 2x. Match the tool to the shape: fill and sizes for fluid images, width and height for fixed ones.
How does next/font avoid extra requests and layout shift?
next/font self-hosts font files with the rest of your static assets, so the browser sends no requests to a font provider. For Google fonts the CSS and files are downloaded at build time. The docs say this lets you load web fonts with no layout shift. It also sets display to swap by default and preloads the font by default.
Read more (2)Show less
The layout-shift part comes from the fallback. A web font and its fallback have different glyph sizes, so text can reflow when the font arrives. web.dev lists the fixes: size-adjust and the override descriptors to match the fallback, a generic fallback family, preload, or font-display optional. next/font has an adjustFontFallback option for this, on by default for Google fonts.
This site moved from next/font/google to next/font/local. The Docker build in CI had to download the font from Google, and when that failed the whole deploy failed. The Latin subset of a variable font now lives in the repo, one file covers weights 400 to 800, and the build needs no network for fonts. Load a font once in a font module and import it where needed, because each call hosts one more instance.
Where does INP come in?
next/image and next/font target the loading and stability metrics. INP is different: it is about how quickly the page responds after the user clicks, taps or types. Of its three parts, processing duration is the time your event handlers take to run, so heavy work inside a handler is the first place to look. Input delay and presentation delay depend on what else the browser is doing before and after.
Read more (1)Show less
web.dev rates an INP above 500 milliseconds as poor. Because INP counts every interaction in a visit, you cannot fix it with one image setting; it needs measurement on real interactions.
The real config behind this lesson
Excerpts from the repository that builds and deploys this website, quoted as they are.
export const jakarta = localFont({
src: "./plus-jakarta-sans-latin.woff2",
weight: "400 800",
style: "normal",
display: "swap",
preload: true,
variable: "--font-jakarta",
});One variable file, a weight range, swap display and a CSS variable. Naming display and preload explicitly records the decision even though both match the defaults, and the variable lets Tailwind read the font.
<ImageWithSkeleton
src={meta.image}
alt={title}
fill
sizes="(max-width: 768px) calc(100vw - 2rem), 768px"
className="object-cover"
priority
/>The hero fills a wrapper with a fixed aspect ratio, so space is reserved. sizes says the image is the full width minus padding on phones and 768 pixels otherwise, and priority is set because this is the LCP image.
Eager, not lazy: the marquee holds most tiles off-screen and scrolls
them in continuously, so lazy logos pop in blank mid-animation. All
11 distinct marks together are ~45 KB at this size. Fixed 44x44
rather than `fill`: with a px `sizes`, `fill` lists every
configured width in srcset (~1 KB of HTML a tile); fixed
dimensions give just 1x and 2x.
*/}
<Image
src={item.logo}
alt=""
width={44}
height={44}
loading="eager"
className="h-full w-full object-contain p-1"
/>The counter-example to the hero. A tiny fixed-size logo gets width and height, not fill, so srcset stays at 1x and 2x. loading eager is a separate choice, made because lazy logos popped in blank during the marquee.
// WebP only — AVIF is dropped deliberately. On the 1 vCPU / 512 MB VPS,
// encoding one cover to AVIF measured 4–9 s (x-nextjs-cache MISS) versus
// ~0.6 s for the same cover to WebP — a 7–16× penalty. Because Cloudflare
// does not currently cache /_next/image/ (cf-cache-status: DYNAMIC), that
// encode is paid on the origin far more often than once, so the fast
// format wins outright. Re-enable AVIF ("image/avif" first) only after the
// edge caches optimised variants, so the slow encode is amortised globally.
formats: ["image/webp"] as Array<"image/avif" | "image/webp">,
deviceSizes: [640, 750, 828, 1080, 1200, 1920],The image optimizer is a server, and its settings are a cost decision. deviceSizes is the list of widths srcset draws from, and formats is trimmed to WebP because encoding the smaller AVIF files was too slow on this host.
Mistakes that cost real time
priority on every image
Preloading many images makes them compete, and web.dev says high priority on more than one or two images stops helping LCP. Mark only the image that is the LCP element.
A lazy-loaded hero
next/image lazy loads by default. Leave that on the LCP image and it waits for layout before it even starts downloading. Add priority to the hero.
fill with a tiny pixel sizes bloats srcset
Every configured width lands in the HTML for each small image, which adds up across a grid. Use fixed width and height for fixed-size images so you get 1x and 2x.
A missing sizes on a responsive image
Without sizes the browser assumes the image spans the viewport and may download far more than needed. Describe the real layout width in sizes.
The font build needs the network
next/font/google downloads at build time. When a Docker build in CI cannot reach the provider, the build fails. This site self-hosts the font with next/font/local so no network call is needed.
| Choice | srcset you get | Use it for |
|---|---|---|
| fill and sizes | A full list of widths | Fluid images such as a hero or a card cover |
| width and height, no sizes | 1x and 2x | Fixed-size images such as a logo or an avatar |
Do it on your own machine
Inspect the HTML a page really ships. Run these against a local production server.
Fetch a page and list its sizes attributes. An image with fill should show your sizes string.
curl -s http://localhost:3000/en | grep -o 'sizes="[^"]*"' | headSwap a small image from fill to fixed width and height, rebuild, and compare the length of its srcset.
Give the hero priority and nothing else, then check the head for a preload link for that image.
curl -s http://localhost:3000/en | grep -o 'rel="preload"[^>]*' | headRead your font module. Confirm the font is loaded once and that display and a CSS variable are set on purpose.
Three questions before you move on
Pick an answer to see why it is right or wrong. Nothing is saved or graded, so guess freely.
Answer in your head, then check
1.Why does the logo marquee use width and height instead of fill?Show the answer
Answer:fill with a pixel sizes lists every configured width in srcset for each small tile; fixed dimensions give just 1x and 2x.
2.Name two ways next/font helps CLS and the network.Show the answer
Answer:It self-hosts the font so the browser makes no request to a font provider, and it can adjust the fallback font to reduce layout shift.
3.Which Core Web Vital replaced First Input Delay?Show the answer
Answer:Interaction to Next Paint, INP, which became a stable Core Web Vital in 2024.
Related deep dives on this site
Sources
Primary sources checked on the research date. Follow them for the current version.