Image CompressionMehdi Tareghi10 min read

How to Compress Images for Next.js in 2026: App Router, Server Components, and Modern Formats

Complete guide to optimizing images for Next.js 15+ with App Router — use next/image for automatic optimization, ship AVIF and WebP with responsive srcset, implement blur placeholders, and prep assets locally before committing to your repo.

Next.js logo with compressed image files flowing through optimization pipeline

Next.js image optimization in 2026 is a partnership: next/image handles responsive delivery, format negotiation, and lazy loading, while you control source quality, dimensions, and format choice before assets reach the framework. Together they deliver fast LCP, minimal CLS, and smaller bundles — but only when you understand where Next.js helps and where manual prep still wins.

This guide covers the full pipeline for Next.js 15+ with App Router and Server Components — the default stack most teams are migrating to in late 2026. Everything here applies to Pages Router too, with routing-specific notes where the APIs differ.

What this guide covers

SectionWhat you'll learn
Why compress for Next.jsWhat next/image does and doesn't do
The optimization splitResponsibilities: you handle source, Next.js handles delivery
Local prep workflowResize, format, and strip metadata before git
Using next/imageProps, layouts, priority, and placeholder patterns
Static vs dynamic imagesImport vs URL, when each matters
AVIF and WebP strategyHow next/image picks formats and when to pre-convert
Blur placeholdersBase64, plaiceholder, and built-in blur generation
Custom loadersCloudinary, Imgix, and self-hosted origins
Build optimizationSharp config, static imports, and output modes
TroubleshootingCLS, slow builds, missing dimensions, format quirks

Why compress for Next.js when the framework optimizes automatically

Next.js 15's next/image is powerful — it serves WebP and AVIF to capable browsers, generates responsive srcset, lazy-loads below-the-fold images, and caches optimized variants. That sounds like "upload anything and it fixes it."

Not quite. Here's what Next.js cannot fix:

  • Oversized source dimensions — next/image resizes on request, but serving a 6000px original from origin before optimization wastes bandwidth on the first uncached load.
  • Already-bloated uploads — if you commit a 4 MB camera JPEG, git stores 4 MB forever. Clones are slower, LFS bills are higher.
  • Server load at scale — every new device width or format combination requires server-side encoding. Pre-compressed sources reduce CPU demand.
  • Metadata exposure — EXIF GPS and timestamps pass through unless you strip them before commit.

The winning strategy: compress locally → commit clean assets → let next/image handle responsive delivery and format negotiation for each visitor.

The optimization split: you vs Next.js

Think of image optimization as a two-stage pipeline:

StageYour responsibilityNext.js responsibility
Source prepResize to max display width × 2, export AVIF/WebP, strip metadata, commit small files
DeliveryGenerate responsive srcset, negotiate format, lazy-load, cache variants

You own the bytes before they hit the repo. Next.js owns the bytes served to each visitor.

What next/image does automatically

  • Serves WebP and AVIF to browsers that support them (via Accept header negotiation)
  • Generates device-optimized widths (srcset) based on sizes prop
  • Lazy-loads images below the fold by default
  • Applies blur placeholders when you provide placeholder="blur"
  • Reserves layout space when you set width and height (prevents CLS)

What you must do manually

  • Resize originals to reasonable max dimensions (e.g., 1920–2400px wide for heroes)
  • Export to efficient formats before commit (AVIF, WebP, or high-quality JPEG)
  • Strip EXIF metadata from public assets
  • Set correct width, height, and sizes props for every image
  • Mark LCP images with priority to prevent lazy-load delays

Local prep workflow: compress before commit

Before any image enters your Next.js project, run this checklist:

1. Resize to display width × 2

Most marketing heroes display at 1200–1600 CSS pixels wide. Export at 2400–3200px max to cover retina displays. Do not commit 6000px camera originals.

Open the batch image compressor, set max dimension to 2400px, and export.

2. Choose the right format

Asset typeBest source format for Next.js
Hero photographyAVIF or WebP (next/image will optimize further)
Blog inline imagesWebP or JPEG
UI screenshotsPNG or lossless WebP
Logos with transparencyPNG or SVG (use SVG when possible)
Product photosWebP or AVIF

For maximum compatibility, you can commit high-quality JPEGs and let next/image convert to WebP/AVIF on first request. For best performance, commit AVIF or WebP directly — next/image passes them through or optimizes minimally.

3. Strip metadata

Enable Strip metadata in Asset Melt's studio settings. Re-encoding removes EXIF GPS coordinates, device info, and timestamps before commit.

See privacy-first image compression for why metadata stripping matters for public repos.

4. Commit to public/ or import statically

/public/images/hero.avif
/public/images/hero-mobile.avif

Or import directly in components:

import heroImage from '@/public/images/hero.avif'

Smaller committed files = faster clones, cheaper LFS, and less origin bandwidth when next/image serves cached variants.

Using next/image correctly

Basic usage with static import

import Image from 'next/image'
import heroSrc from '@/public/images/hero.avif'

export default function HeroSection() {
  return (
    <Image
      src={heroSrc}
      alt="Asset Melt studio compressing images locally"
      priority // Above fold — prevents lazy load
      placeholder="blur"
      sizes="(min-width: 1024px) 1200px, 100vw"
    />
  )
}

Key props:

  • src — imported object or URL string
  • alt — required, descriptive alt text
  • priority — use on LCP image only; prevents lazy-load delay
  • placeholder="blur" — shows low-quality placeholder during load (requires static import or manual blurDataURL)
  • sizes — tells browser which srcset width to fetch; critical for correct responsive delivery

Width and height (preventing CLS)

Always provide dimensions:

<Image
  src="/images/product.webp"
  alt="Oak dining table"
  width={1200}
  height={800}
  sizes="(min-width: 768px) 50vw, 100vw"
/>

Or use fill for CSS-sized containers:

<div className="relative aspect-video">
  <Image
    src="/images/background.avif"
    alt=""
    fill
    className="object-cover"
    sizes="100vw"
  />
</div>

With fill, the container must have position: relative and an explicit height or aspect-ratio.

Static vs dynamic images

import hero from '@/public/hero.avif'

<Image src={hero} alt="Hero" priority />

Pros:

  • Next.js reads dimensions and generates blur placeholders automatically
  • Type-safe
  • Bundler warns if file is missing

Cons:

  • Cannot use for dynamic URLs from CMS or user uploads

Dynamic URLs (for CMS, user content)

<Image
  src={post.heroUrl}
  alt={post.heroAlt}
  width={1200}
  height={630}
  sizes="100vw"
/>

Pros:

  • Works with CMS, databases, external CDNs

Cons:

  • You must manually provide width and height
  • Blur placeholders require manual blurDataURL prop
  • Requires remotePatterns config in next.config.js

Configure allowed domains:

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

AVIF and WebP strategy in Next.js

Next.js negotiates formats automatically via the Accept header. When a browser requests an image:

  1. Next.js checks if the visitor supports AVIF (via Accept: image/avif)
  2. If not, checks WebP support
  3. Falls back to original format (JPEG, PNG)

Should you pre-convert to AVIF before commit?

Yes when:

  • The image is a hero or LCP candidate
  • You want to minimize origin bandwidth
  • Build performance matters (less server-side encoding)

Let Next.js convert when:

  • You're migrating existing JPEG assets gradually
  • The CMS only accepts JPEG uploads
  • You need one source format for simplicity

Example: commit both AVIF and WebP sources

/public/images/
  hero.avif      (120 KB)
  hero.webp      (180 KB)
  hero.jpg       (500 KB, fallback only)

Use a picture element with next/image sources:

<picture>
  <source srcSet="/images/hero.avif" type="image/avif" />
  <source srcSet="/images/hero.webp" type="image/webp" />
  <Image
    src="/images/hero.jpg"
    alt="Description"
    width={1920}
    height={1080}
    priority
  />
</picture>

Or let next/image pick automatically by committing only AVIF — it will convert to WebP and JPEG on demand.

Read AVIF vs WebP for format tradeoffs.

Blur placeholders and LQIP

Next.js supports low-quality image placeholders (LQIP) to prevent blank space during image load.

Automatic blur with static imports

import hero from '@/public/hero.avif'

<Image src={hero} alt="Hero" placeholder="blur" />

Next.js generates a tiny base64-encoded thumbnail at build time.

Manual blur for dynamic URLs

Generate a blurDataURL with plaiceholder or similar:

<Image
  src={post.heroUrl}
  alt={post.heroAlt}
  width={1200}
  height={630}
  placeholder="blur"
  blurDataURL={post.blurDataURL}
/>

Store blurDataURL in your CMS or generate it server-side.

Empty placeholder for decorative images

<Image
  src="/images/background.avif"
  alt=""
  fill
  placeholder="empty"
  className="object-cover -z-10"
/>

Use placeholder="empty" when the image is decorative and blur would confuse layout.

Custom loaders for external CDNs

If you host images on Cloudinary, Imgix, or a custom CDN, configure a loader:

// next.config.js
module.exports = {
  images: {
    loader: 'custom',
    loaderFile: './lib/image-loader.js',
  },
}
// lib/image-loader.js
export default function cloudinaryLoader({ src, width, quality }) {
  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]
  return `https://res.cloudinary.com/your-cloud/image/upload/${params.join(',')}/${src}`
}

Even with custom loaders, pre-compress originals before uploading to the CDN origin. Cloudinary and Imgix optimize on the fly, but they perform best when source files are already clean.

Optimizing at build time

Sharp configuration

Next.js uses sharp for server-side image optimization. Tune quality in next.config.js:

module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },
}

Key options:

  • formats — order matters; AVIF first, WebP second
  • deviceSizes — breakpoints for full-width images
  • imageSizes — fixed sizes for constrained images (thumbnails, avatars)
  • minimumCacheTTL — how long optimized images are cached

Static export mode

When using output: 'export' for static hosting, next/image optimization is disabled. Pre-compress all images before build and use regular <img> tags or a custom image component.

Common Next.js image issues

CLS (Cumulative Layout Shift)

Problem: Image loads and pushes content down.

Fix: Always set width and height, or use fill with a container aspect-ratio.

<Image src={src} alt="Alt" width={1200} height={800} />

Slow local development

Problem: First image load is slow in dev mode.

Cause: Next.js optimizes images on demand. The first request encodes; subsequent requests serve from cache.

Fix: Accept dev mode slowness, or pre-compress aggressively so next/image passes through with minimal processing.

Missing remotePatterns error

Problem: Error: Invalid src prop ... hostname not configured

Fix: Add the domain to next.config.js:

images: {
  remotePatterns: [
    { protocol: 'https', hostname: 'cdn.example.com' },
  ],
}

AVIF not serving

Problem: Browser supports AVIF but receives WebP or JPEG.

Cause: Check formats in next.config.js — AVIF must come before WebP.

Fix:

images: {
  formats: ['image/avif', 'image/webp'],
}

LCP image lazy-loaded

Problem: Hero image has loading="lazy" even though it's above fold.

Fix: Add priority prop:

<Image src={hero} alt="Hero" priority />

priority sets loading="eager" and adds a preload link in <head>.

Real-world workflow

1. Export from design tool

Export at 2400px max width, AVIF or WebP.

2. Compress locally

Drop files into Asset Melt, enable metadata stripping, export to public/images/.

3. Commit

git add public/images/hero.avif
git commit -m "feat: add hero image for Q4 campaign"

4. Use in component

import Image from 'next/image'
import hero from '@/public/images/hero.avif'

export default function Hero() {
  return (
    <section className="relative h-screen">
      <Image
        src={hero}
        alt="Q4 campaign hero: woman using laptop in cafe"
        fill
        priority
        className="object-cover"
        sizes="100vw"
      />
    </section>
  )
}

5. Verify

Run Lighthouse on mobile, check LCP element. If it's the hero image and LCP is under 2.5s on 4G, you're good.

Next.js vs WordPress vs Shopify compression

PlatformOptimization approachYour prep step
Next.jsServer-side on demand; caches variantsResize and format before commit
WordPressPlugin-based; varies by CDNCompress before media library upload — see compress for WordPress
ShopifyShopify CDN auto-resizes; preserves originalsExport at 2048px max, WebP or JPEG — see compress for Shopify

Next.js gives you the most control. WordPress and Shopify optimize post-upload, so local prep before upload is even more critical there.

Server Components and image optimization

Next.js App Router Server Components fetch data on the server and render once. Images in Server Components work identically to Client Components:

// app/blog/[slug]/page.tsx (Server Component)
import Image from 'next/image'

export default async function BlogPost({ params }) {
  const post = await fetchPost(params.slug)

  return (
    <article>
      <Image
        src={post.heroUrl}
        alt={post.heroAlt}
        width={1200}
        height={630}
        priority
      />
      <h1>{post.title}</h1>
      {/* ... */}
    </article>
  )
}

No special handling required. next/image optimizes regardless of component type.

Getting started today

  1. Audit public/ and find images over 500 KB.
  2. Re-export at 1920–2400px max width as AVIF or WebP.
  3. Commit the smaller files and delete originals.
  4. Add priority to your LCP image component.
  5. Verify with Lighthouse that LCP improved.

Related reading: Compress images without losing quality · AVIF vs WebP · Privacy-first compression · Optimize for Core Web Vitals

Frequently asked questions

Related tools

Image Compression
View all guides
Pillar guide

How to Compress Images Without Losing Quality

Compressing images without losing quality is really about removing bytes people cannot see . Camera originals include oversized dimensions, metadata, and more…

Ready to compress images without uploading them?

Open Asset Melt Studio