Sitemap

Next.js Image Optimization: Fixing Blurry, Slow, and Layout-Shifting Images

5 min readSep 30, 2026

--

Press enter or click to view image in full size

The next/image component solves most image problems automatically, but only if you give it the information it needs. Most complaints about blurry, slow, or jumping images trace back to a few specific misconfigurations.

If you are a junior Next.js developer, images are usually the first thing that makes your Lighthouse score drop and your page feel cheap. The good news: four habits fix almost all of it.

What next/image actually does for you

Before fixing problems, it helps to know what you get for free when you use next/image instead of a plain <img> tag:

  • It resizes images to the sizes your users actually need
  • It serves modern formats like WebP or AVIF when the browser supports them
  • It lazy-loads images below the fold by default
  • It reserves space so the page does not jump while loading

Every problem in this article happens when one of those four things is missing its input. The component is powerful, but it cannot guess your layout.

Layout shift: always give it dimensions

Without dimensions, the browser can’t reserve space, so content jumps when the image loads. Users feel this as text sliding down right as they try to click something.

import Image from 'next/image';
<Image src="/hero.jpg" width={1200} height={600} alt="Hero" />

The width and height here are not the size it displays at. They tell the browser the aspect ratio, so it can reserve the right amount of space before the file arrives.

When the size depends on the container, use fill with a positioned parent instead:

<div style={{ position: 'relative', height: 400 }}>
<Image src={url} alt="" fill style={{ objectFit: 'cover' }} sizes="100vw" />
</div>

The parent must be positioned (relative, absolute, or fixed) and have a size. Otherwise the image collapses to zero height and looks like it vanished.

Want the basics first? If folders-as-URLs, layouts, and components still feel new, start here:

Learning Next.js — Basics takes a junior from the first page to a small shipped app, slowly enough that nothing is assumed.

Slow loading: set sizes and priority

The sizes prop tells the browser which resolution to download. Skipping it on a fill image often ships a far larger file than needed.

<Image
src={url}
alt=""
fill
sizes="(max-width: 768px) 100vw, 50vw"
/>

Read that value like a sentence: “on screens up to 768px wide, this image fills the whole width. Otherwise it fills half.” The browser uses it to pick the smallest file that still looks sharp.

Add priority only to the above-the-fold hero image, since it's likely your Largest Contentful Paint element. Adding it everywhere defeats the purpose, because every image then competes for bandwidth at the same time.

Remote images throwing errors

External hosts must be allowed explicitly. If you see an error about an unconfigured host, this is the fix:

// next.config.js
module.exports = {
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'images.example.com', pathname: '/photos/**' },
],
},
};

Be as specific as possible with pathname so you don't open your optimizer to arbitrary images. A wide-open pattern lets anyone use your server to resize their images, and you pay for it.

Restart the dev server after editing next.config.js. Config changes are not picked up live, and this trips up almost every junior at least once.

Blurry images and placeholders

Blur usually comes from a small source image scaled up, or an overly low quality value. The default is 75, which is fine for most photos.

For a smoother load, add a blur placeholder:

<Image src="/photo.jpg" alt="" width={800} height={500} placeholder="blur" />

Statically imported images get the blur data automatically. For remote images, supply a blurDataURL.

Get Syamkumar S’s stories in your inbox

Join Medium for free to get updates from this writer.

Do not confuse the two kinds of blur. A placeholder blur is temporary and disappears when the image loads. A permanently blurry image means the source file is too small or the quality is too low.

Common mistakes

  • Omitting sizes on responsive images. The browser assumes the image is full-viewport width and downloads an oversized file.
  • Putting priority on every image. They all compete for bandwidth and the benefit disappears.
  • Using fill without a positioned parent. The image collapses to zero height.
  • Passing huge source files. Optimization helps, but starting with a 6000-pixel-wide original still costs more processing and storage than a sensibly sized source.
  • Writing an empty or lazy alt. Use a real description for meaningful images, and alt="" only for purely decorative ones.

A responsive card grid example

A typical product grid shows how the props work together:

<div className="grid">
{products.map((p, i) => (
<div key={p.id} className="card" style={{ position: 'relative', aspectRatio: '4 / 3' }}>
<Image
src={p.image}
alt={p.name}
fill
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 25vw"
priority={i < 2}
style={{ objectFit: 'cover' }}
/>
</div>
))}
</div>

Three things are happening here:

  • The aspectRatio reserves space so nothing shifts
  • sizes matches the grid columns: full width on phones, half on tablets, a quarter on desktop
  • Only the first two images get priority, because they're likely visible without scrolling

Copy this pattern and change the numbers to match your own grid.

Shipping on Next.js 16? params is a Promise, Turbopack is the default, and a few surfaces moved. Use this checklist so your images and routes do not break in production:

Next.js 16 Migration — Avoid These Breaking Changes is the practical 15 → 16 path.

Troubleshooting checklist

If images still look wrong, work through this list:

  • Blurry: check the source resolution and the quality prop.
  • Slow: check sizes and whether below-the-fold images are lazy (the default).
  • Layout jumping: confirm width and height, or a reserved container.
  • Broken remote images: confirm remotePatterns matches the exact hostname and protocol.
  • Wrong format: Next.js serves WebP or AVIF when the browser supports it, and you can configure formats in next.config.js.

Use the Network tab in your browser dev tools to confirm the served size and format instead of guessing. Filter by “Img” and look at the actual file that arrived.

FAQ

Should I use the plain img tag anywhere? For tiny inline icons or SVGs, a plain tag or inline SVG is fine. For photos and large images, use next/image.

Why is my optimized image still large? Check the sizes prop. Without it, the browser may pick a larger variant than the layout actually needs.

Do I need width and height if I use fill? No. fill replaces them, but it needs a positioned parent with a size.

When should I use priority? Only for the one or two images visible without scrolling, usually the hero.

Bottom line

Give images dimensions, set sizes, reserve priority for the hero, and allow remote hosts explicitly. Those four habits fix nearly every Next.js image complaint.

Once you build one page this way, you will spot the missing prop in every slow site you visit.

Keep writing like this alive

This post is free. The handbooks take weeks.

If it saved you an afternoon of debugging, you can support the work here:

ovyqen.gumroad.com

Buy a book if you need one. Pay what you want if you just want to say thanks. That is what funds the next junior-friendly guide.

The Next.js set:

--

--

See my online store ⬇️ ⬇️ htttp://ovyqen.gumroad.com/