RA
RenovateAPIEngineering Hub
Frontend & Performance

Why Your Next.js Images Are Still Slow (Even With next/image)

The three next/image mistakes that quietly wreck your LCP score: missing priority on hero banners, lazy sizes attributes, and no cache directory on self-hosted deploys.

AAbhishek4 min read
Frontend & Performance4 min read

Why Your Next.js Images Are Still Slow (Even With next/image)

RENOVATEAPI ARCHITECTURAL SPEC
CANONICAL GUIDE

Most Next.js projects already use next/image. Almost none of them are actually getting the LCP numbers it's supposed to deliver.

That gap is usually not a next/image problem. It's three specific misconfigurations that are easy to ship without noticing, because the component still renders a perfectly fine-looking image either way. The page just quietly fails Core Web Vitals in the background.

The hero image is loading like it's not the hero

If the largest image above the fold doesn't have priority, the browser has no reason to treat it as urgent. It gets discovered the same way as any other image on the page: after the HTML parses, sometimes after hydration kicks in. For a hero banner, that delay usually is your LCP.

// components/HeroBanner.tsx
import Image from 'next/image';

export function HeroBanner({ src, alt }: { src: string; alt: string }) {
  return (
    <div className="relative h-[480px] w-full overflow-hidden rounded-2xl">
      <Image
        src={src}
        alt={alt}
        fill
        priority
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
        quality={85}
        className="object-cover"
      />
    </div>
  );
}

Setting priority makes Next.js emit a <link rel="preload" as="image"> tag directly in the server-rendered <head>, so the browser starts fetching before it's even finished parsing your CSS and JS bundles. On a typical hero banner this alone is worth 400ms to 1.2s off LCP. It costs nothing to add and there's rarely a reason not to use it on whatever image sits above the fold.

sizes is the one everyone gets wrong

Here's the part that trips people up even when they know priority exists: sizes isn't optional metadata, it's the instruction that decides which image width actually gets downloaded.

Leave it off fill images and Next.js assumes 100vw. On a 3-column product grid, that means a phone on a 390px screen ends up requesting the same 1920px desktop asset a full-width layout would need, then the browser (or your image pipeline) does the resizing math wrong at the CDN layer. You end up shipping a ~320KB image to display something that should have cost ~35KB.

// A 3-column desktop grid that drops to 2 columns on tablet, 1 on mobile
<Image
  src={product.imageUrl}
  alt={product.name}
  fill
  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
  className="object-cover"
/>

Getting sizes right, matched to your actual breakpoints, is where you recover most of the bandwidth: up to 75% on mobile, by some measurements. It also shaves roughly 250ms off mobile LCP, since the browser isn't waiting on a payload ten times bigger than what it needs to paint.

next.config.js still needs tuning, even with defaults

Two settings in next.config.js do a disproportionate amount of the work:

/** @type {import('next').NextConfig} */
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 2592000, // 1 month at the edge
    remotePatterns: [
      { protocol: 'https', hostname: 'cdn.yourdomain.com', pathname: '/uploads/**' },
    ],
  },
};

AVIF first, WebP as fallback, gets you roughly another 20% off file size over WebP alone, worth about 150ms of LCP on top of everything above. That part's a one-line change and there's no real downside.

minimumCacheTTL matters more than it looks like it should if you're self-hosting instead of deploying to Vercel. Vercel's edge network caches transformed images for you automatically. A self-hosted deployment on your own VPS or container doesn't get that for free — without a persistent cache directory, sharp reprocesses the same image transformation on every single request. I've seen this exact thing tank response times on a client project where the "optimized" images were actually slower than serving the originals, because the server was doing full resize-and-recompress work on every page load instead of once.

What actually moves the needle

Fix LCP impact Bandwidth saved CLS impact
priority on the hero image -400ms to -1.2s 0% 0.00
Accurate sizes breakpoints -250ms (mobile) up to 75% 0.00
formats: ['image/avif'] -150ms +20% on top of WebP 0.00
Explicit width/height or fill ~0ms 0% Eliminates shift (CLS < 0.01)

If you only fix one thing today, fix sizes. It's the setting most people skip because the image still looks right without it, and it's the one costing the most real bandwidth on mobile. priority is the easy win everyone already knows about. The cache directory issue is the one that only bites you once you're off Vercel, and by then it's usually already been quietly inflating your server bill for a while.

RenovateAPI Engineering Suite

Accelerate your Frontend & Performance Modernization Roadmap

Need custom architecture auditing, automated OpenAPI contract generation, or zero-downtime microservice migration guidance for your engineering team?

Frequently Asked Questions

Do I still need to write a sizes attribute if I'm using fill on a next/image?

Yes. fill controls how the image fits its container, but sizes is what tells the browser which of the generated srcset widths to actually request. Skip it and the browser defaults to 100vw, which on a responsive grid means it downloads way more image than the layout ever shows.

Does self-hosting Next.js need any extra image caching config?

It needs a persistent cache directory for the image optimizer, or every request re-runs the sharp transformation from scratch. Vercel handles this automatically at the edge; a self-hosted deployment on a VPS or container has to be told where to keep the processed files, otherwise you're paying the CPU cost of resizing the same image over and over.

Weekly Engineering Dispatch

Subscribe to RenovateAPI

Get weekly architectural guides, API refactoring strategies, and technical SEO updates delivered directly to your inbox.

Discussion (2)

A
Alex Rivera
2 hours ago

Extremely helpful breakdown of the Strangler Fig pattern! We're currently refactoring a legacy Java monolith at work and the OpenAPI gateway routing tips saved us weeks of experimentation.

S
Sophia Chen
1 day ago

The schema JSON-LD and FAQ block structure really helps with indexing. Great technical detail on entity mentions too.

Suggested Related Articles