// LOADING
// LOADING
// LOADING_ARTICLE
Images are the single largest contributor to slow Largest Contentful Paint (LCP) scores on most content-heavy sites. Next.js ships a built-in Image component that handles format conversion, resizing, and lazy loading—but only if you configure it correctly. This guide covers every lever worth pulling.
The plain <img> tag has no awareness of device pixel ratio, viewport width, or format support. You ship a 2 MB JPEG to a 375 px phone that only needed a 40 KB WebP. next/image fixes this at the framework level:
Accept headerssrcset so browsers download the right sizeloading="lazy" by default (overridable with priority)For a broader view of Core Web Vitals and how they affect ranking, see Core Web Vitals in 2026.
priority Prop and LCPThe most common mistake is forgetting priority on the hero image. Without it, Next.js emits loading="lazy", which blocks the browser from fetching the image during the critical render path.
tsximport Image from 'next/image'; export default function Hero() { return ( <Image src="/hero.jpg" alt="Manikanta Ketha — full-stack engineer" width={1200} height={600} priority // preloads, removes lazy loading sizes="100vw" // tells the browser this fills the viewport quality={80} // default is 75; 80 is a safe balance /> ); }
Rule of thumb: every image that appears above the fold without scrolling should carry priority. Everything else should not—lazy loading is a win for offscreen assets.
sizes Is the Most Under-Used Propsizes tells the browser how wide the image will render at various breakpoints before CSS is parsed. Without it, the browser assumes 100vw for every image and downloads much larger files than needed.
tsx<Image src="/blog-thumbnail.jpg" alt="Blog post cover" width={800} height={450} sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" />
This single change typically cuts image payload by 30–60% on grid layouts where thumbnails are displayed at a fraction of the viewport width.
Next.js serves AVIF first when the browser supports it, then WebP, then the original. AVIF offers better compression than WebP but encodes slower at build time. You can tune the preference in next.config.ts:
ts// next.config.ts import type { NextConfig } from 'next'; const config: NextConfig = { images: { formats: ['image/avif', 'image/webp'], deviceSizes: [640, 750, 828, 1080, 1200, 1920], imageSizes: [16, 32, 48, 64, 96, 128, 256], }, }; export default config;
If your build times are slow, swap the order to ['image/webp', 'image/avif'] and only flip back to AVIF-first once you've confirmed build capacity.
When images live on S3, Cloudinary, or any external host, you must explicitly allow the hostname. Otherwise Next.js refuses to proxy and optimize the image.
ts// next.config.ts const config: NextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'res.cloudinary.com', pathname: '/your-account/**', }, { protocol: 'https', hostname: '*.amazonaws.com', }, ], }, };
Avoid the old domains array—it was deprecated in Next.js 13 and remotePatterns gives you path-level control.
For images where LCP timing is fine but the layout jump is jarring, a blur-up placeholder prevents the empty-box flash:
tsximport Image from 'next/image'; import heroImg from '@/public/hero.jpg'; // static import gives you blurDataURL export default function Hero() { return ( <Image src={heroImg} alt="Hero" placeholder="blur" // uses auto-generated blurDataURL from static import priority sizes="100vw" /> ); }
For dynamic/remote images you'll need to supply blurDataURL manually—usually a tiny base64-encoded 10×10 pixel version of the image generated at upload time.
When you don't control the image's intrinsic dimensions (CMS content, user-uploaded avatars), use fill with a positioned container:
tsx<div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}> <Image src={post.coverImage} alt={post.title} fill style={{ objectFit: 'cover' }} sizes="(max-width: 768px) 100vw, 50vw" /> </div>
Without position: relative on the parent, the image escapes its container. Without sizes, the browser downloads the full-width version regardless of layout.
Next.js caches optimized images in .next/cache/images and sets a Cache-Control: public, max-age=... header based on the source image's Cache-Control or the minimumCacheTTL config. On Vercel, this cache is shared globally. For self-hosted deployments, make sure your reverse proxy forwards cache headers and does not re-compress images a second time.
Caching images is one piece of a broader caching strategy—see Caching Strategies: CDN, ISR, Redis for how ISR and CDN caching interact with dynamic pages.
After changes, measure with next build && next start locally using Lighthouse or the Chrome DevTools Network panel filtered to img. In production, track LCP via the Core Web Vitals report in Search Console—it reflects real user data, not lab conditions.
If you're also worried about JavaScript payload alongside image weight, read Reducing JavaScript Bundle Size in Next.js for the complementary performance levers.
priority to every above-the-fold imagesizes string on every Imagenext.config.tsremotePatterns (not domains) for external imagesplaceholder="blur" for static imports where CLS is a concern.next/cache/images is persistent across deploys on self-hosted infraThese changes compound. A site that ships raw 2 MB images can often reach sub-500 KB total image weight on mobile with nothing more than next/image configured correctly. That alone can take an LCP from 4 s to under 2.5 s.