Next.js Image Optimization: The Complete WebP Guide for 2026
Author
Muhammad Awais
Published
May 24, 2026
Reading Time
10 min read
Views
9.5k

The Next.js Image Component Doesn't Fix Everything - Here's What I Learned the Hard Way
Six months ago I shipped a Next.js 15 marketing site for a client. Lighthouse scores looked great in development 91, 94, occasionally 96. Then we pushed to production and ran a real-world audit. The score dropped to 67. The culprit? Images. Specifically, the images I had not routed through the next/image component.
Blog post cover images uploaded through Sanity CMS raw JPEGs, no optimization. Inline images embedded in MDX content same problem. A handful of product screenshots referenced directly in HTML untouched. The <Image> component had handled everything I'd explicitly imported, but a significant chunk of the social media preview assets, background banners, and dynamic user files had bypassed it entirely.
This is the gap most Next.js tutorials don't talk about. The framework's built-in optimization is genuinely excellent for what it covers but it only covers images you explicitly pass through the component. Everything else is your responsibility.
What you'll learn: Exactly what Next.js Image component does and doesn't do for WebP
How to configure AVIF and WebP output correctly in
next.config.jsWhich images escape automatic optimization and how to fix each one
The CMS image pipeline problem and the fastest way to solve it
How to verify your images are actually being served as WebP in production
What next/image Actually Does Under the Hood
Before fixing gaps, it helps to understand exactly what the component does when it works correctly. When you use <Image src="/photo.jpg" />, Next.js intercepts the image request and runs it through an on-demand optimization pipeline.
First, it checks the requesting browser's Accept header. If the browser sends Accept: image/avif,image/webp,*/* which Chrome, Firefox, and modern Safari all do Next.js knows the browser supports next-gen formats. It then re-encodes the source image on first request, caches the result, and serves the optimal format automatically.
Second, it handles responsive sizing. The sizes prop combined with srcSet generation means the browser downloads an appropriately-sized image rather than a 2000px wide image displayed at 400px. This alone can cut image payload by 50-70% on mobile devices.
Third, it adds loading="lazy" by default for below-fold images and priority for above-fold images you explicitly flag. This controls when the browser fetches each image relative to page load keeping LCP images from competing with below-fold assets for bandwidth.
The default format output in Next.js 15 is WebP, with AVIF available if you configure it. According to the Next.js Image component documentation, you can enable both formats and let Next.js serve AVIF to browsers that support it with WebP as fallback — add this to your next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
formats: ['image/avif', 'image/webp'],
// Optional: define which external domains are allowed
remotePatterns: [
{
protocol: 'https',
hostname: 'res.cloudinary.com',
},
],
},
}
module.exports = nextConfigWith this config, Chrome users get AVIF (smaller files), Safari and Firefox get WebP, and any browser that supports neither gets the original format. All handled automatically no manual <picture> elements needed.
The Four Image Types That Escape next/image Optimization
Here's where most Next.js image guides stop. They cover the happy path <Image> component, props, configuration and call it done. But in real production Next.js applications, a significant percentage of images never touch the component at all.
1. CMS-uploaded images referenced in rich text: If you're using Sanity, Contentful, Strapi, or any headless CMS, editors upload images directly to the CMS. Those images get embedded in rich text content as standard HTML <img> tags. Next.js has no way to intercept these they render as-is, in whatever format the editor uploaded. A common real-world scenario: an editor uploads a 2.8MB JPEG product photo. It renders on the page at 600px wide display size but downloads at full resolution, unoptimized.
2. Images in MDX content: Same problem. MDX files that use standard markdown image syntax  render as plain <img> tags unless you configure a custom MDX image component that wraps next/image. Most projects don't do this by default.
3. CSS background images: background-image: url('/hero-bg.jpg') in your CSS completely bypasses Next.js image optimization. The browser requests this directly with no interception from the image pipeline.
4. Open Graph and Twitter Card images: The images you reference in generateMetadata for social sharing openGraph.images are fetched by social media scrapers directly, not through the Next.js image pipeline. If you serve an unoptimized JPEG here, that's what gets crawled and potentially cached by social platforms.
Each of these requires a different fix. Understanding which category your unoptimized images fall into is the first step toward actually solving the problem rather than just improving your Lighthouse score in isolation.
Fixing CMS Images: The Pre-Upload Workflow
The most reliable fix for CMS image optimization isn't technical it's a workflow change. Before any image gets uploaded to your CMS, it should already be in WebP format at an appropriate size and quality.
This sounds simple but requires establishing it as a team habit. Editors who've been uploading PNGs and JPEGs for years won't change unless the workflow is easy enough that the optimized path is also the path of least resistance.
Our Image to WebP Converter handles batch conversion before upload editors can drop up to 25 images at once, convert to WebP at 85% quality in under a second, and download the batch as a ZIP. The tool runs entirely in the browser, so there are no server uploads of client content and no privacy concerns for proprietary images.
For Cloudinary-hosted content, a better solution is Cloudinary's transformation pipeline append f_auto,q_auto to your image URLs and Cloudinary serves the optimal format per browser automatically. This moves optimization out of the CMS upload workflow entirely:
// Before — static JPEG URL from CMS
const imageUrl = "https://res.cloudinary.com/yourcloud/image/upload/product.jpg"
// After — automatic format and quality optimization
const imageUrl = "https://res.cloudinary.com/yourcloud/image/upload/f_auto,q_auto/product.jpg"If you're using Sanity, their Image component (@sanity/image-url) can generate WebP URLs directly. For any other CMS, the pre-upload conversion workflow is the most universally applicable fix.
Fixing MDX Images: The Custom Component Approach
If your Next.js project uses MDX for blog content, fixing image optimization requires a custom MDX component that wraps next/image. Here's the pattern that works cleanly in Next.js 15:
// components/mdx/MdxImage.tsx
import Image from 'next/image'
interface MdxImageProps {
src: string
alt: string
width?: number
height?: number
}
export function MdxImage({ src, alt, width = 800, height = 450 }: MdxImageProps) {
return (
<div className="my-6 overflow-hidden rounded-lg">
<Image
src={src}
alt={alt}
width={width}
height={height}
className="w-full h-auto"
quality={85}
/>
</div>
)
}// app/blog/[slug]/page.tsx — pass the component to MDX renderer
import { MdxImage } from '@/components/mdx/MdxImage'
const components = {
img: MdxImage, // Replaces all <img> tags in MDX with optimized version
}
// Pass components to your MDX renderer (next-mdx-remote, etc.)
<MDXRemote source={content} components={components} />With this setup, every  in your MDX files automatically renders through Next.js image optimization WebP output, lazy loading, responsive sizing. The only requirement is that images are accessible via the configured domains in your next.config.js.
How to Verify Images Are Actually Being Served as WebP
This is something I check on every project before signing off, because it's easy to assume the optimization is working when it isn't. There are two reliable ways to verify.
Browser DevTools Network tab: Open DevTools, go to Network, filter by "Img". Reload the page. Click any image request and look at the Response Headers. You should see Content-Type: image/webp or Content-Type: image/avif. If you see image/jpeg or image/png, that image is not being optimized.
Lighthouse audit: Run a Lighthouse audit in production (not development Next.js optimization only runs in production mode by default). The "Serve images in next-gen formats" audit will flag any images that are being served as JPEG or PNG when WebP would be more appropriate. Each flagged image shows the potential savings.
A common gotcha: Next.js image optimization uses a cache. On first request, the image gets optimized and cached. On subsequent requests, the cached WebP is served. If you're checking immediately after changing your config, clear the .next/cache directory and restart, or test in an incognito window to avoid cached responses.
For a deeper look at how image format choices directly affect your Core Web Vitals scores including real Lighthouse data our guide on why you must convert to WebP in 2026 covers the LCP impact in detail.
CSS Background Images and OG Images - The Quick Fixes
For CSS background images, the fix is simple but requires manual work: convert the source files to WebP before referencing them in your CSS. Since CSS background images bypass the Next.js image pipeline entirely, you need the WebP file to exist at the path you're referencing.
Run your background images through a converter, rename the output, update your CSS references from .jpg to .webp. Done. If you need to support browsers that don't support WebP in CSS backgrounds (older Safari, IE), use the @supports query:
/* Modern browsers — WebP */
@supports (background-image: url("test.webp")) {
.hero {
background-image: url('/images/hero-bg.webp');
}
}
/* Fallback for browsers without WebP CSS support */
@supports not (background-image: url("test.webp")) {
.hero {
background-image: url('/images/hero-bg.jpg');
}
}For Open Graph images, the most practical approach is to pre-generate your OG images as WebP and host them at a stable URL. If you're generating OG images dynamically using Next.js's ImageResponse API (the opengraph-image.tsx file convention), the output format is controlled by the contentType export set it to image/webp for WebP output.
If you're building complex background patterns or UI textures, consider skipping raster images entirely. Our Tailwind SVG Background Generator creates geometric patterns and dot grids as pure CSS zero file size, infinitely scalable, no image requests required at all.
Common Next.js Image Optimization Mistakes
Using
unoptimized={true}and forgetting to remove it: This prop bypasses the entire optimization pipeline. It's sometimes added during debugging or for specific edge cases, then left in production by mistake. Do a project-wide search forunoptimizedbefore deploying.Not setting the
sizesprop on responsive images: Withoutsizes, Next.js generates a srcSet but the browser defaults to assuming the image is 100vw wide. A sidebar image displayed at 300px gets a 1200px version downloaded on desktop. Always setsizesto match your actual layoutsizes="(max-width: 768px) 100vw, 300px".Forgetting
priorityon above-fold images: The largest image above the fold usually the hero image should havepriorityset. Without it, it loads lazily and your LCP suffers. This is one of the most common and highest-impact LCP mistakes in Next.js projects.Not configuring
remotePatternsfor external images: If you're loading images from a CDN, CMS, or external URL, you must whitelist the domain innext.config.js. Without this, Next.js won't optimize external images and may throw a runtime error.Assuming development Lighthouse scores represent production: Next.js image optimization, including WebP conversion, only runs in production mode.
next devserves images as-is. Always run your final Lighthouse audit against the production build.
Frequently Asked Questions
Does Next.js automatically convert images to WebP?
Yes, but only for images routed through the next/image component. When a browser that supports WebP requests an image through the component, Next.js re-encodes the source image as WebP on first request and caches the result. Images referenced directly in CSS, standard <img> tags, MDX content without a custom component, or external HTML are not automatically optimized.
How do I enable AVIF output in Next.js?
Add formats: ['image/avif', 'image/webp'] to the images section of your next.config.js. Next.js will serve AVIF to browsers that support it and fall back to WebP for others. Note that AVIF encoding is significantly slower than WebP for large image sets, this can increase the time to first cached response. The cached version serves at normal speed on subsequent requests.
Why are my Lighthouse scores good in development but bad in production?
Next.js image optimization only runs in production mode (next build && next start). In development (next dev), images are served in their original format without WebP conversion or sizing optimization. Always run your final Lighthouse audit against a production build or your deployed URL, not the local development server.
How do I optimize images in my MDX blog posts?
Create a custom MDX image component that wraps next/image and pass it to your MDX renderer via the components prop, mapping img to your custom component. This intercepts all standard markdown image syntax and routes it through Next.js optimization. The source images still need to be accessible via a configured domain or stored in your /public folder.
What happens to images uploaded through a CMS like Sanity or Contentful?
CMS images embedded in rich text content render as standard <img> tags and bypass Next.js image optimization. The most reliable fix is a pre-upload workflow convert images to WebP before uploading to the CMS. Alternatively, Cloudinary users can append f_auto,q_auto to image URLs for automatic format optimization at the CDN level. Some CMS platforms also offer built-in image transformation APIs.
How do I verify that Next.js is actually serving WebP images?
Open browser DevTools, go to the Network tab, filter by Img, and reload the page. Click any image request and check the Response Headers for Content-Type. It should show image/webp or image/avif for optimized images. You can also run a Lighthouse audit the "Serve images in next-gen formats" check flags any images still being served as JPEG or PNG with the potential savings for each.
Continue Reading
View All HubLevel Up Your Workflow
Free professional tools mentioned in this article
Cron Job Expression Generator & Explainer
Generate cron expressions visually and instantly translate any cron schedule into plain English. Includes GitHub Actions, Vercel, and AWS presets.
Regex Tester & Debugger
Test, debug, and validate Regular Expressions (Regex) instantly. A free, client-side Regex Tester for developers to build safe patterns with zero logs.
JSON to TypeScript Converter
Convert any JSON object into clean TypeScript interfaces instantly. Supports nested objects, arrays, and optional fields free, no signup, runs entirely in your browser.
Fancy Font & Stylish Text Generator
Transform your text into 50+ stylish and aesthetic fonts instantly. Perfect for Instagram bios, TikTok captions, and PUBG nicknames. One-click copy & paste.




