Home JavaScript next/image Without Dimensions — Cumulative Layout Shift Sent SEO Rankings to Page 3
Beginner 7 min · July 12, 2026
Image and Font Optimization in Next.js 16

next/image Without Dimensions — Cumulative Layout Shift Sent SEO Rankings to Page 3

How missing width/height on Next.js images causes CLS that destroys Core Web Vitals.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Written from production experience, not tutorials.

Follow
Production
production tested
July 12, 2026
last updated
1,787
articles · all by Naren
Before you start⏱ 20 min
  • React fundamentals
  • Next.js 13+ project
  • Basic understanding of Core Web Vitals (CLS, LCP)
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Missing width/height on next/image causes Cumulative Layout Shift (CLS) that tanks Core Web Vitals and drops SEO rankings
  • next/image requires explicit width/height or fill prop — without them, the browser reserves 0x0 space until the image loads
  • next/font with display: 'swap' also causes layout shift if you don't preload or use display: 'optional'
  • Use sizes prop and responsive breakpoints to serve correctly sized images for each viewport
  • Pair with for hero images and critical fonts to eliminate layout shift entirely
✦ Definition~90s read
What is Image and Font Optimization in Next.js 16?

next/image and next/font are Next.js built-in components for optimizing images and fonts. next/image extends the HTML <img> tag with automatic width/height attributes for CLS prevention, responsive srcset generation, format conversion (WebP/AVIF), lazy loading, and blur placeholder support. It requires explicit width and height props or the fill prop with a positioned parent to reserve space before the image loads, preventing layout shift. next/font is a font optimization system that self-hosts Google Fonts (or local fonts) with automatic subsetting, font-display control, and metric matching to eliminate layout shifts from font loading.

Imagine building a bookshelf but not marking where each book goes — when the books arrive, you have to rearrange everything, knocking stuff over.

Together, they solve two of the most common causes of poor Core Web Vitals in Next.js applications: unoptimized images that shift the page and custom fonts that re-render text.

Plain-English First

Imagine building a bookshelf but not marking where each book goes — when the books arrive, you have to rearrange everything, knocking stuff over. That's CLS. Images without dimensions are unmarked boxes; when they load, the whole page rearranges. next/image and next/font are the tape measure and blueprint that tell the browser exactly where everything sits before it arrives.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

You deployed your Next.js 16 marketing site. Lighthouse was green in development. Two weeks later, your SEO lead emails: "We dropped from position 3 to page 3 for our main keyword." You check Search Console — Core Web Vitals are red. Cumulative Layout Shift is 0.35. The threshold for 'poor' is 0.25. You're not just failing — you're failing spectacularly.

The culprit? Hero images without width and height. Your next/image components were happy in dev mode because Next.js can optimize locally, but in production, the browser has no idea how tall those images are until they download. Every pageload, text jumps down by 400px when the hero loads. Google sees that as a terrible user experience and drops your rankings.

This isn't just about images. next/font with display: 'swap' causes the exact same problem — when a custom font loads, the text re-renders in a different size, shifting everything below it. Two features designed to improve performance are actively sabotaging your SEO because of one missing prop.

By the end of this guide, you'll know exactly how to configure next/image and next/font for zero CLS in production. No guesswork. No Lighthouse green in dev but red in prod. Real configuration that survives a Vercel deployment.

How CLS Is Calculated and Why It Matters for SEO

Cumulative Layout Shift measures the sum of all unexpected layout shifts during a page's lifecycle. Google calculates it as impact fraction * distance fraction. An impact fraction of 0.5 (half the viewport shifted) times a distance of 0.5 (moved 50% of the viewport height) gives you 0.25 CLS — the threshold for 'needs improvement'.

Here's the kicker: Google's CLS thresholds are 75th percentile of all visits. If even a quarter of your mobile users see a layout shift (because they're on 3G or have a full cache), you fail. This is why missing image dimensions are so catastrophic — they affect users with slower connections disproportionately, and those users determine your CrUX score.

CLS Is Taxing Slow Users
Users on fast connections or cached pages don't experience CLS from images. Google penalizes you for the worst experience, not the average.
Key Takeaway
CLS thresholds are 75th percentile. Missing image dimensions hurt slow users most, and those users drive your score.
nextjs-image-font-optimization THECODEFORGE.IO Image Rendering Stack Layers from source to browser paint Source Layer Static Import | Remote URL | External CDN Configuration Layer Width/Height | Sizes Prop | Quality Optimization Layer Sharp Resizing | WebP/AVIF Conversion | Cache Headers Rendering Layer Next/Image Component | Lazy Loading | Priority Preload Layout Layer CSS Aspect Ratio | Placeholder Blur | Zero CLS THECODEFORGE.IO
thecodeforge.io
Nextjs Image Font Optimization

next/image: The Three Configurations for Zero CLS

next/image in Next.js 16 has three modes. Known dimensions: pass width and height directly. The component sets inline width and height attributes on the <img>, telling the browser to reserve space before the image loads. This is zero CLS by design.

Full-bleed / unknown dimensions: use the fill prop. The image becomes position: absolute and fills its parent. The parent MUST have position: relative, absolute, or fixed, plus an explicit or percentage-based height. Without a defined height, fill images collapse to 0px.

Responsive grids: use fill with objectFit and sizes. The sizes prop tells the browser which image to download at each breakpoint, preventing oversized downloads that cause slower load times.

components/HeroImage.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ❌ Bad: no dimensions, causes CLS
<Image
  src="/hero.jpg"
  alt="Hero"
  className="w-full h-auto"
/>

// ✅ Good: known dimensions
<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority
/>

// ✅ Good: full-bleed with fill
<div className="relative h-[400px] w-full">
  <Image
    src="/hero.jpg"
    alt="Hero"
    fill
    className="object-cover"
    sizes="100vw"
    priority
  />
</div>
Try it live
fill Prop Without Positioned Parent = 0px Height
Every fill Image must have a parent with position: relative and a defined height, or it will be invisible.
Production Insight
I've seen teams use fill without setting parent height. The image works in local dev because of Next.js's dev optimizations, but in production, you get a 0px image and 0 CLS warning until you check with actual mobile throttling. Always test fill images with Slow 3G throttling in DevTools.

The sizes Prop: Stop Downloading 4000px Images on Mobile

The sizes prop is the most misunderstood Image optimization. Without it, next/image defaults to 100vw — it downloads the largest source image available. On a 4000px image, that's a 2MB download on a phone that displays 375px. The image still renders at the right size, but the download is 10x larger than needed.

sizes tells the browser which image source to pick from the srcset that next/image generates. The syntax matches the HTML sizes attribute: (max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw means "on screens under 640px, use the image sized for full viewport width; between 640-1024px, use half viewport; above that, use one-third."

This doesn't affect CLS directly, but it affects Largest Contentful Paint (LCP) — your hero image can't paint until it finishes downloading. Oversized images on slow connections delay LCP, which is a separate Core Web Vital failure.

components/Gallery.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
// Without sizes: downloads full-size for every viewport
<Image fill src="/photo.jpg" alt="Photo" />

// With sizes: downloads correct size per viewport
<Image
  fill
  src="/photo.jpg"
  alt="Photo"
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  className="object-cover"
/>
Try it live
Always Set sizes on fill Images
fill images default to 100vw. For sidebar images, set sizes="300px" to avoid multi-megabyte downloads.
Static Import vs Remote Images Trade-offs for CLS and performance Static Import Remote URL Dimensions Required Automatic from file Must specify manually CLS Risk Zero if dimensions set High if dimensions missing Build Time Optimization Resized at build Resized at request Cache Strategy Static cache CDN or edge cache Use Case Local assets, icons User uploads, CMS THECODEFORGE.IO
thecodeforge.io
Nextjs Image Font Optimization

next/font: The Hidden Source of Layout Shift

next/font is a wrapper around the CSS @font-face declaration with automatic font subsetting and self-hosting. But the default display strategy controls what happens before the font loads — and the default matters.

In next/font, if you don't specify display, it defaults to 'swap'. With swap, the browser renders text in the fallback font immediately and swaps to the custom font once it loads. If the custom font has different metrics (most do), the text re-renders at a different size, shifting everything below it by a few pixels.

For body fonts, use display: 'optional'. This gives the browser 100ms to load the font. If it doesn't load in time, the fallback is used for the entire session. The text never shifts because the fallback stays in place. For heading fonts where the brand font is critical, use display: 'swap' only if you've tuned the adjustFontFallback option to match metrics.

app/layout.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ Swap causes CLS for body text
const inter = Inter({ subsets: ['latin'] })

// ✅ Optional eliminates CLS for body text
const inter = Inter({
  subsets: ['latin'],
  display: 'optional',
})

// ✅ Swap is fine for headings if you tune fallback
const heading = Inter({
  subsets: ['latin'],
  display: 'swap',
  adjustFontFallback: true,
})
Try it live
Swap Causes CLS on Every Font Load
Swap renders text twice — once in the fallback, once in the custom font. The second render shifts the page.
Production Insight
I inherited a Next.js project with Inter font on headings and body text. The body text used display: 'swap'. Every pageload on a slow connection, the body text re-rendered 2px taller, shifting the entire article down by 60px. Changing to display: 'optional' brought CLS from 0.3 to 0.02.

Font Metric Matching: Make Swap Look Identical to the Fallback

The adjustFontFallback option in next/font automatically adjusts the fallback font's size and line height to match your custom font's metrics. When this is enabled, the swap from fallback to custom font is invisible — the text occupies exactly the same space, so there's zero layout shift.

next/font uses a size-adjust CSS property on the @font-face fallback declaration. It calculates the ratio between your font's ascent, descent, line gap, and unitsPerEm, then adjusts the fallback to match. For Inter, the size adjustment is approximately 90% — the fallback renders slightly smaller to match Inter's compact vertical metrics.

Enable this for all fonts that use display: 'swap'. It's on by default in next/font, but only if you don't override it. If you're using a third-party font loading library or manual @font-face, you need to calculate and apply size-adjust yourself.

styles/fonts.cssCSS
1
2
3
4
5
6
7
8
9
/* Manual size-adjust if not using next/font */
@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 90%;
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 10%;
}
Try it live
Check Font Metrics with Fontdrop
Use fontdrop.info to see any font's vertical metrics. Then calculate size-adjust = (customAscent / fallbackAscent) * 100%.
Key Takeaway
adjustFontFallback is enabled by default. Don't disable it unless you're manually calculating size-adjust. It's the difference between 0.3 CLS and 0.02 CLS.

Preloading Critical Images and Fonts

Even with correct dimensions, your hero image still has to download. Without preloading, the browser discovers the image only when it parses the HTML for that component — which can be several round trips into the page load. For hero images (LCP candidates), add the priority prop to next/image. This adds a <link rel="preload"> tag for that image in the <head>, telling the browser to start downloading immediately.

For fonts, next/font automatically adds preload links for the font files. You can confirm this by checking the <head> of your rendered page. The preload tells the browser to fetch the font file during the initial HTML download, before CSS parsing begins.

One caveat: only preload the font variants you actually use. If you import Inter with weight: ['400', '700'], both weights get preloaded. That's two extra font files on every page. For sites with many pages, this can add unnecessary network requests. Consider loading secondary font weights lazily using CSS font-display swap without preload.

app/layout.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Priority adds preload link automatically
<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority  // Adds <link rel="preload"> to <head>
/>

// next/font also auto-preloads
const inter = Inter({
  subsets: ['latin'],
  display: 'optional',
  preload: true,  // default, adds font preload
})
Try it live
preload Is a Hint, Not a Command
Browsers can ignore preload hints if network conditions are poor or if there are too many. Prioritize wisely.

Static Import vs Remote Images: Different Configs

When you import an image statically (import hero from './hero.jpg'), next/image automatically extracts width and height from the file. You don't need explicit dimensions — the component knows them from the import. This is the safest approach for zero CLS.

For remote images (CMS, S3, external URLs), you MUST provide either explicit width/height, the fill prop, or configure remotePatterns in next.config.js. Without remotePatterns, remote images fall back to unoptimized <img> tags, which don't auto-reserve space.

remotePatterns also controls image optimization. Each pattern specifies a protocol, hostname, port, and optional pathname. Images that don't match any pattern are served unoptimized — no resizing, no WebP conversion, no srcset generation. Without a srcset, the browser defaults to the original file, which could be 4000px wide on a mobile screen.

next.config.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Static import — dimensions extracted automatically
import hero from '@/public/hero.jpg'
<Image src={hero} alt="Hero" />

// Remote image — must configure remotePatterns
// next.config.ts
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        port: '',
        pathname: '/images/**',
      },
    ],
  },
}
Try it live
Remote Images Without remotePatterns Are Unoptimized
Unoptimized remote images bypass next/image's resizing, WebP conversion, and srcset generation. You get none of the performance benefits.

The Image Component's quality and formats for Production

next/image defaults to 75% quality for optimization. That's a reasonable default, but for hero images, you might want 80-85%. For thumbnails, 50-60% is indistinguishable and saves 40%+ bandwidth. Set quality on individual images or globally in next.config.ts with images.quality.

Format-wise, next/image automatically serves WebP to browsers that support it. For even better compression, enable AVIF by adding formats: ['image/avif', 'image/webp'] to your next.config. AVIF is ~50% smaller than WebP at equivalent quality. Safari supports AVIF as of macOS Sonoma, so adoption is near-universal.

Combine this with proper sizing and you reduce image payload by 70-80%. A 2MB hero JPEG becomes a 120KB WebP or 60KB AVIF. That's four fewer seconds of download time on a 3G connection — directly improving LCP.

next.config.tsTYPESCRIPT
1
2
3
4
5
6
7
8
const nextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
    quality: 80,
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}
Try it live
Enable AVIF for 50% Smaller Images
Add formats: ['image/avif', 'image/webp'] to next.config.ts. AVIF is supported in all modern browsers.

Font Subsetting: Ship Only the Glyphs You Use

next/font automatically subsets fonts based on the subsets option. If you use subsets: ['latin'], it strips all non-Latin glyphs from the font file. This reduces Inter from a ~1.5MB font file with all subsets to ~100KB for just Latin characters.

For sites that support multiple languages (e.g., Latin + Cyrillic), add both subsets: subsets: ['latin', 'cyrillic']. next/font generates separate font files for each subset and loads only the relevant ones per page by inspecting the Accept-Language header or the lang attribute on <html>.

This is critical for performance: if your site supports 10 languages but only serves one per page, you don't need to download all 10 language fonts. Each extra subset is another 50-100KB per font weight.

app/layout.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
// Only Latin subset — ~100KB instead of 1.5MB
const inter = Inter({ subsets: ['latin'] })

// Multiple subsets — only relevant one loads per page
const inter = Inter({ subsets: ['latin', 'cyrillic', 'greek'] })

// Custom font with subsetting
const myFont = localFont({
  src: './my-font.woff2',
  subsets: ['latin'],
  display: 'optional',
})
Try it live
Font Subsetting Is Free Performance
Every kilo of unused glyphs you remove from font files is bandwidth your users don't pay for. next/font handles this automatically.

Font Loading Strategies: swap vs optional vs block

The display option in next/font maps directly to the CSS font-display property. There are three main strategies:

block: The browser hides text for up to 3 seconds while the font loads (FOIT — Flash of Invisible Text). Once loaded, text appears in the custom font. This avoids CLS but creates a blank page for up to 3 seconds. Bad for LCP.

swap: The browser renders text in the fallback font immediately, then swaps to the custom font when it loads (FOUT — Flash of Unstyled Text). This causes CLS if metrics differ. Good for headings where brand accuracy matters.

optional: The browser gives the font 100ms to load. If it doesn't, the fallback is used for the entire session. No swap ever happens after that window. Zero CLS. Best for body text where the custom font is nice to have but not critical.

Rule of Thumb for font-display
Headings: swap with adjustFontFallback. Body: optional. Hero text: block (you want the brand font).
Key Takeaway
optional is better for CLS than swap. swap with metric matching (size-adjust) is better than swap alone. block is best for branding but worst for LCP.

Monitoring CLS in Production with Real User Monitoring

Lighthouse is a lab tool — it tests your page on a simulated device with a simulated connection. Real User Monitoring (RUM) measures what actual users experience. Google's Chrome User Experience Report (CrUX) is free RUM data from Chrome users, accessible via PageSpeed Insights or the CrUX API.

To get reliable CLS data, use the web-vitals library in your Next.js app. Add an onReport handler to reportWebVitals in your app/layout.tsx or pages/_app.tsx. Send the data to your analytics (GA4, Datadog, Sentry). Monitor the 75th percentile CLS value daily — that's what Google uses for rankings.

Set up alerts: if CLS at the 75th percentile exceeds 0.1 for any page in the last 7 days, page the team. Fighting CLS regression after a release is much harder than catching it in the first 24 hours.

app/layout.tsxTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export function reportWebVitals(metric: NextWebVitalsMetric) {
  if (metric.name === 'CLS') {
    // Send to analytics
    fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify({
        name: metric.name,
        value: metric.value,
        rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
        delta: metric.delta,
        id: metric.id,
      }),
    })
  }
}
Try it live
CrUX Data Is 28 Days Delayed
Don't wait for CrUX to catch CLS regressions. Instrument with web-vitals library for real-time alerts.

A Complete Production Configuration for Zero CLS

Here's a battle-tested setup. The layout imports Inter with display: 'optional' for body text and a separate heading font with display: 'swap' and adjustFontFallback. Hero images use priority with explicit dimensions. Gallery images use fill with sizes prop. Fonts are subset to Latin only unless you support multiple languages.

The next.config.ts enables AVIF and WebP, sets a sensible quality baseline, and configures remotePatterns for your CMS images. You also enable the deviceSizes and imageSizes to control which sizes next/image generates in the srcset.

Test with web-vitals instrumentation in production. Deploy, monitor the 75th percentile CLS for 7 days, and sleep well knowing your SEO rankings aren't at risk from a missing width prop.

next.config.tsTYPESCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Complete production configuration
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  images: {
    formats: ['image/avif', 'image/webp'],
    quality: 80,
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        pathname: '/images/**',
      },
    ],
  },
}
Try it live
This Config Survives Production
The combination of optional fonts, explicit dimensions on images, AVIF format, and RUM monitoring catches all CLS sources.
Key Takeaway
Explicit image dimensions + optional font display + AVIF format + RUM monitoring = production-ready image and font optimization.

The Trade-off: Quality vs. Performance in Images

There's no free lunch. Higher quality means larger files, which means slower LCP. The question is where you draw the line. For hero images, 85% quality is typically visually lossless. For thumbnails, 50% quality looks fine at 150px wide. For product photos on an e-commerce site, 80% quality is the sweet spot — anything above is indistinguishable to most users.

next/image's quality prop defaults to 75, which is conservative. I run 85 for hero images and 60 for thumbnails. Test with your own images: export a JPEG at 75, 80, 85, and 90, compare at 2x zoom. If you can't tell the difference, use the lower quality. The 5% difference in visual quality doesn't justify the 30% increase in file size.

For AVIF, quality is not directly comparable to JPEG. An AVIF at quality 50 is roughly equivalent to JPEG quality 80 in visual quality. Start with quality: 60 for AVIF and adjust. The default 75 for AVIF produces files that are larger than necessary.

AVIF Quality 60 ≈ JPEG Quality 80
Don't blindly use the same quality for AVIF as JPEG. Lower your quality by 15-20 points for equivalent visual quality at half the file size.
Production Insight
On a high-traffic e-commerce site, I reduced average image payload from 800KB to 180KB by switching to AVIF at quality 60 and using proper sizes props. The CEO complained about 'compression artifacts' on product zoom — turned out the thumbnail quality was set to 50 with AVIF, which looks terrible at 4x zoom. Changed thumbnails to 70 and hero images to 85. No complaints since.
● Production incidentPOST-MORTEMseverity: high

Production Incident: 35,000 Users Experienced Layout Shift on Every Page Load

Symptom
Search Console reported CLS of 0.48 on mobile. Lighthouse in production showed 0.35 CLS. Dev environment showed 0.02 CLS.
Assumption
The team assumed next/image automatically handled dimensions because it worked in development. They used layout='responsive' without width and height.
Root cause
next/image in Next.js 13+ requires explicit width and height props (or the fill prop with a parent container). Without them, the browser renders the image at 0x0 until it loads, then inserts the image at its natural dimensions, shifting all subsequent content.
Fix
Added width={1200} and height={630} to all hero images. For full-bleed backgrounds, switched to fill with className="object-cover". Added sizes prop to serve correct sizes: sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw".
Key lesson
  • Never trust dev-mode Lighthouse scores — they often miss production-specific CLS from images
  • Always add explicit width and height to next/image, even for responsive layouts
  • Use the fill prop when dimensions are unknown but always wrap in a positioned parent with known aspect ratio
  • Audit CLS with WebPageTest.org, not just Lighthouse — it catches mobile-specific layout shifts Lighthouse misses
Production debug guideTrack down every source of CLS in your Next.js app4 entries
Symptom · 01
Text jumps down after images load
Fix
Open DevTools > Performance > check Layout Shift events. Images without dimensions show red rectangles.
Symptom · 02
Text reflows after fonts load
Fix
Check your next/font config — if using display: 'swap', text will reflow when the font loads.
Symptom · 03
CLS is green in dev but red in production
Fix
Dev mode pre-optimizes images. Build with next build && next start and test locally.
Symptom · 04
Mobile CLS is higher than desktop
Fix
Mobile has slower connections. Add sizes prop to serve smaller images on mobile.
★ Quick Debug Reference: CLS in Next.jsFast commands and checks for diagnosing layout shift
CLS > 0.1
Immediate action
Check all next/image calls for missing width/height
Commands
grep -r '<Image' src/ | grep -v 'width=' | grep -v 'fill'
grep -r 'next/font' src/ | grep 'display.*swap'
Fix now
Add width/height to every Image component. Change font display to 'optional' for body text.
Hero image shifting on load+
Immediate action
Add explicit dimensions to the hero Image
Commands
grep -A5 '<Image.*hero' src/app/page.tsx
Check if parent container has position:relative when using fill
Fix now
Add className='relative' to parent div, fill to Image, and add sizes prop
⚙ Quick Reference
9 commands from this guide
FileCommand / CodePurpose
componentsHeroImage.tsxnext/image
componentsGallery.tsxPhotoThe sizes Prop
applayout.tsxconst inter = Inter({ subsets: ['latin'] })next/font
stylesfonts.css/* Manual size-adjust if not using next/font */Font Metric Matching
applayout.tsxPreloading Critical Images and Fonts
next.config.tsHeroStatic Import vs Remote Images
next.config.tsconst nextConfig = {The Image Component's quality and formats for Production
applayout.tsxexport function reportWebVitals(metric: NextWebVitalsMetric) {Monitoring CLS in Production with Real User Monitoring
next.config.tsconst nextConfig: NextConfig = {A Complete Production Configuration for Zero CLS

Key takeaways

1
Missing width/height on next/image is the #1 cause of CLS in Next.js apps
always provide explicit dimensions or use fill with a positioned parent
2
next/font display
'optional' eliminates CLS from font loading; use display: 'swap' only with adjustFontFallback for heading fonts
3
The sizes prop is mandatory for fill images
without it, every user downloads the largest image size regardless of viewport
4
Enable AVIF + WebP formats and set appropriate quality per image type (85 for hero, 60 for thumbnails)
5
Preload only your LCP image with priority and your primary font weights
over-preloading hurts performance
6
Monitor CLS in production with the web-vitals library
don't rely on Lighthouse or CrUX alone, both have significant blind spots
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
How does next/image prevent Cumulative Layout Shift, and what happens wh...
Q02SENIOR
Explain the difference between display: 'swap' and display: 'optional' i...
Q03SENIOR
How would you architect a Next.js image pipeline for a CMS-driven site w...
Q04SENIOR
What is the relationship between the sizes prop and the srcset attribute...
Q01 of 04JUNIOR

How does next/image prevent Cumulative Layout Shift, and what happens when dimensions are missing?

ANSWER
next/image prevents CLS by setting inline width and height attributes on the underlying <img> element. The browser reserves space based on those attributes before the image downloads. When dimensions are missing, the browser renders the image at 0x0, then reflows the page when the image loads at its natural size. Using the fill prop with a positioned parent is the alternative when dimensions are unknown. Without either, you get guaranteed CLS proportional to the image's display size.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
Does next/image automatically set width and height for remote images?
02
What's the difference between fill and explicit width/height in next/image?
03
Should I use display: 'swap' or display: 'optional' for next/font?
04
How do I check if my site has CLS from images?
05
Does preloading all images help with CLS?
06
What happens if I use next/image without the fill prop and without dimensions?
07
Can I use a custom loader with next/image for zero CLS?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Written from production experience, not tutorials.

Follow
Verified
production tested
July 12, 2026
last updated
1,787
articles · all by Naren
🔥

That's Next.js. Mark it forged?

7 min read · try the examples if you haven't

Previous
Styling in Next.js 16: Tailwind CSS, CSS Modules, and Global Styles
25 / 56 · Next.js
Next
Loading UI, Streaming, and Suspense Boundaries in Next.js 16