Skip to content

Fix: Next.js Image Optimization Errors – Invalid src, Missing Loader, or Unoptimized

FixDevs ·

Quick Answer

How to fix Next.js Image component errors including 'Invalid src prop', 'hostname not configured', missing loader, and optimization failures in production.

The Error

You use the Next.js <Image> component and hit one of these errors at build time or in the browser console:

Error: Invalid src prop (https://example.com/photo.jpg) on `next/image`, hostname "example.com" is not configured under images in your `next.config.js`
Error: Image with src "/hero.png" must use "width" and "height" properties or "fill" property.
Error: Image Optimization using the default loader is not compatible with `next export`.

You may also see these related variants:

Error: Unable to optimize image and target URL is malformed
Error: 'images.remotePatterns' configuration is required to use external images
Module not found: Can't resolve 'sharp'
Warning: Image with src "..." was detected as the Largest Contentful Paint (LCP). Please add the "priority" prop to the Image component.

In development these errors usually appear as a red overlay or console warning. In production, images may fail to load entirely, display as broken thumbnails, or silently fall back to unoptimized versions that hurt your Core Web Vitals scores.

Why This Happens

The Next.js <Image> component is not a simple <img> tag. It wraps a full image optimization pipeline that resizes, converts, and caches images on the fly. To do this safely, Next.js enforces several constraints:

  1. External hostnames must be explicitly allowed. Next.js refuses to optimize images from domains you haven’t whitelisted. This prevents your server from being used as an open proxy to fetch and process arbitrary URLs.
  2. Width and height are required. The browser needs to know the image dimensions before it loads so it can reserve the correct amount of space and avoid layout shift (CLS). Next.js requires you to declare these dimensions upfront or use the fill prop.
  3. The default image loader requires a running server. When you use next export (or output: 'export' in next.config.js) to produce a static site, there is no server to handle on-the-fly image optimization. You need a custom loader or must mark images as unoptimized.
  4. The sharp package is needed in production. In development, Next.js uses squoosh for image optimization, but production builds expect sharp for better performance. If sharp is missing from your production dependencies, optimization fails silently or throws an error.

Each fix below addresses a specific cause. Most projects will need to apply more than one.

Fix 1: Configure remotePatterns for External Images

When you load an image from an external URL, Next.js blocks it unless you whitelist the hostname. The older domains configuration still works but remotePatterns is the recommended approach because it gives you finer-grained control over protocols, ports, and pathname patterns.

Broken code:

import Image from 'next/image';

function Avatar({ user }) {
  return (
    <Image
      src={`https://cdn.example.com/avatars/${user.id}.jpg`}
      alt={user.name}
      width={80}
      height={80}
    />
  );
}

This throws the “hostname not configured” error because cdn.example.com is not in your config.

Fix — add remotePatterns to next.config.js:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        pathname: '/avatars/**',
      },
      {
        protocol: 'https',
        hostname: '*.amazonaws.com',
      },
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
      },
    ],
  },
};

module.exports = nextConfig;

Each entry in remotePatterns specifies which protocol, hostname, port, and pathname pattern are allowed. The ** wildcard matches any number of path segments. You can use * in hostnames to match subdomains (e.g., *.amazonaws.com matches my-bucket.s3.amazonaws.com).

If you are migrating from the older domains array, the equivalent would be:

// Older approach — still works but less flexible
images: {
  domains: ['cdn.example.com', 'images.unsplash.com'],
},

The domains option only matches hostnames. It doesn’t let you restrict by protocol, port, or path. Prefer remotePatterns for new projects. If you have environment variables controlling your CDN hostname, make sure they are available at build time since next.config.js runs during the build.

Common Mistake: Adding every CDN domain you use to remotePatterns with a wildcard pathname: '/**'. This defeats the purpose of the allowlist. Be as specific as possible with the pathname pattern to prevent your optimization endpoint from being used as an open image proxy.

Fix 2: Provide width and height Props

The <Image> component requires explicit dimensions so the browser can allocate space before the image loads. Without them, the page layout shifts when the image appears, causing poor CLS scores.

Broken code:

<Image src="/hero.png" alt="Hero banner" />

Fix — add width and height:

<Image src="/hero.png" alt="Hero banner" width={1200} height={630} />

The values should match the intrinsic dimensions of the image (or the aspect ratio you want). Next.js uses these to calculate the correct srcset sizes. The rendered size is still controlled by CSS — the width and height props just define the aspect ratio and tell the optimization pipeline what dimensions to generate.

If you don’t know the exact dimensions ahead of time (e.g., user-uploaded images), you have two options:

Option A — use the fill prop (see Fix 3 below).

Option B — import the image statically:

import heroImage from '../public/hero.png';

// Next.js automatically reads the width and height from the file
<Image src={heroImage} alt="Hero banner" />

Static imports work for images in your project’s file system. Next.js reads the file at build time and injects the dimensions automatically. This is the simplest approach when the image is part of your codebase.

Fix 3: Use the fill Prop for Dynamic or Unknown Dimensions

When the image dimensions are not known at build time — for example, images from a CMS, user uploads, or an API — use the fill prop instead of width and height.

Fix:

function CoverImage({ url, altText }) {
  return (
    <div style={{ position: 'relative', width: '100%', height: '400px' }}>
      <Image
        src={url}
        alt={altText}
        fill
        style={{ objectFit: 'cover' }}
      />
    </div>
  );
}

When you use fill, the image expands to fill its parent container. The parent must have position: relative, position: absolute, or position: fixed so the absolutely positioned image has a reference frame. Set the container’s dimensions with CSS to control how large the image appears.

The style={{ objectFit: 'cover' }} tells the browser how to crop the image if its aspect ratio doesn’t match the container. Other options include contain (scales down to fit without cropping) and fill (stretches to fill, possibly distorting).

You should also provide a sizes prop when using fill so Next.js can generate the right srcset:

<Image
  src={url}
  alt={altText}
  fill
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  style={{ objectFit: 'cover' }}
/>

Without sizes, Next.js assumes the image could be as wide as the viewport, which may cause it to serve unnecessarily large files on desktop layouts where the image only takes up a fraction of the screen. This is one of those subtle issues that doesn’t produce an error but silently degrades performance, similar to how a misconfigured useEffect dependency array can silently degrade rendering performance.

Fix 4: Handle Static Export with a Custom Loader

When you use output: 'export' in your Next.js config (or the legacy next export command), there is no Node.js server at runtime. The default image loader can’t run because it needs a server to process images on the fly.

Error:

Error: Image Optimization using the default loader is not compatible with `next export`.

Fix Option A — use unoptimized globally:

// next.config.js
const nextConfig = {
  output: 'export',
  images: {
    unoptimized: true,
  },
};

module.exports = nextConfig;

This disables all image optimization. Images are served as-is. This is the simplest fix, but you lose all the performance benefits of Next.js image optimization.

Fix Option B — use a custom loader that points to an external optimization service:

// next.config.js
const nextConfig = {
  output: 'export',
  images: {
    loader: 'custom',
    loaderFile: './image-loader.js',
  },
};

module.exports = nextConfig;
// image-loader.js
export default function cloudflareLoader({ src, width, quality }) {
  const params = [`width=${width}`, `quality=${quality || 75}`, 'format=auto'];
  return `https://your-domain.com/cdn-cgi/image/${params.join(',')}/${src}`;
}

With a custom loader file, the <Image> component still generates srcset with multiple sizes, but instead of pointing to /_next/image, it points to your external image CDN. The optimization happens at the CDN level, not on a Next.js server.

You can also set a loader per image instead of globally:

const imgixLoader = ({ src, width, quality }) => {
  return `https://your-account.imgix.net${src}?w=${width}&q=${quality || 75}&auto=format`;
};

<Image
  loader={imgixLoader}
  src="/hero.png"
  alt="Hero"
  width={1200}
  height={630}
/>

Fix 5: Custom Loaders for Cloudflare, Vercel, and Self-Hosted Deployments

Different hosting platforms handle image optimization differently. Using the wrong loader for your platform causes broken images or unnecessary round-trips.

Vercel (default — no configuration needed):

Vercel handles image optimization automatically with the default loader. No extra configuration is required. If you are deployed on Vercel and still seeing errors, check that your next.config.js doesn’t set a custom loader that overrides the default.

Cloudflare Pages:

Cloudflare Pages does not support the default Next.js image optimizer. Use Cloudflare Image Resizing:

// image-loader.js
export default function cloudflareLoader({ src, width, quality }) {
  if (src.startsWith('/')) {
    // relative path — prefix with your domain
    src = `https://your-domain.com${src}`;
  }
  return `https://your-domain.com/cdn-cgi/image/width=${width},quality=${quality || 75},format=auto/${src}`;
}

Self-hosted with Node.js:

When self-hosting, you need the sharp package installed in production:

npm install sharp

If you are using Docker, make sure sharp is not excluded from your production node_modules. A common mistake is using a multi-stage build that copies only certain files and leaves sharp’s native binaries behind:

# Make sure sharp is included in the production stage
FROM node:20-alpine AS runner
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.js ./next.config.js
COPY --from=builder /app/package.json ./package.json

If sharp fails to install on your target platform, it’s often a native dependency issue. Check that your build platform matches your deployment platform architecture (e.g., don’t build on macOS ARM and deploy to Linux x86). If you see a Cannot find module error referencing sharp, the package is either missing or built for the wrong platform.

Fix 6: Add priority to Above-the-Fold Images

This one is a warning, not an error, but ignoring it hurts your Largest Contentful Paint (LCP) score:

Warning: Image with src "..." was detected as the Largest Contentful Paint (LCP). Please add the "priority" prop.

Fix:

<Image
  src="/hero.png"
  alt="Hero banner"
  width={1200}
  height={630}
  priority
/>

The priority prop tells Next.js to preload this image in the <head> using <link rel="preload">. It also disables lazy loading for this image. Use it on the largest visible image above the fold — typically a hero banner, product image, or featured article thumbnail.

Only one or two images per page should have priority. Adding it to every image defeats the purpose and can actually slow down page loads by competing for bandwidth.

For images below the fold, Next.js automatically sets loading="lazy", which defers loading until the image is near the viewport. You can also set this explicitly:

<Image
  src="/gallery-item.png"
  alt="Gallery item"
  width={400}
  height={300}
  loading="lazy"
/>

The default behavior is already lazy loading for non-priority images, so setting loading="lazy" explicitly is only needed if you want to make the intent clear in your code.

Pro Tip: Only one or two images per page should have the priority prop. Adding it to every image actually hurts performance by creating bandwidth competition during the initial page load.

Fix 7: Handle the sharp Dependency in Production

In development, Next.js uses a JavaScript-based image optimizer (squoosh). In production, it looks for sharp, which uses native C libraries and is significantly faster. If sharp is not installed, you’ll see:

Warning: For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended.

Or in some cases, images fail to optimize silently and are served at full size.

Fix:

npm install sharp

Common issues with sharp installation:

Issue 1 — sharp is in devDependencies instead of dependencies:

# Wrong — sharp won't be available in production
npm install --save-dev sharp

# Right
npm install sharp

Issue 2 — platform mismatch in CI/CD:

If you build on a Mac but deploy to Linux, sharp’s native binaries won’t work. Force the correct platform during install:

npm install --os=linux --cpu=x64 sharp

Or use the SHARP_IGNORE_GLOBAL_LIBVIPS environment variable to force a fresh build:

SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install

Issue 3 — Docker Alpine missing dependencies:

RUN apk add --no-cache libc6-compat

Alpine Linux requires libc6-compat for sharp to work. Without it, the binary loads but crashes with a cryptic error about missing symbols.

If your production environment truly cannot run sharp (e.g., edge runtimes, serverless platforms with size limits), use a custom loader to offload optimization to an external service (see Fix 4).

Fix 8: Fixing next/image in the App Router vs. Pages Router

The <Image> component works the same way in both routers, but how you configure it differs slightly. A common mistake is putting the configuration in the wrong place or using outdated syntax.

App Router (Next.js 13+) — the import is the same:

import Image from 'next/image';

Pages Router — also the same import, but watch out for the legacy import:

// Correct
import Image from 'next/image';

// Legacy — still works but deprecated
import Image from 'next/legacy/image';

The legacy next/legacy/image component has a different API. It uses layout prop (layout="fill", layout="responsive") instead of the fill prop and style prop. If you’re following a tutorial written for Next.js 12 and using Next.js 13+, you’ll see errors because the props have changed.

Migration from legacy to current:

// Next.js 12 (legacy)
<Image src="/photo.jpg" alt="Photo" layout="fill" objectFit="cover" />

// Next.js 13+ (current)
<Image src="/photo.jpg" alt="Photo" fill style={{ objectFit: 'cover' }} />
// Next.js 12 (legacy)
<Image src="/photo.jpg" alt="Photo" layout="responsive" width={800} height={600} />

// Next.js 13+ (current)
<Image
  src="/photo.jpg"
  alt="Photo"
  width={800}
  height={600}
  style={{ width: '100%', height: 'auto' }}
/>

If you’re upgrading a project and see unfamiliar prop errors, check whether your code is using the legacy component. The Next.js codemod can automate this migration:

npx @next/codemod next-image-to-legacy-image .
npx @next/codemod next-image-experimental .

If modules fail to resolve during the codemod, you may be hitting a module resolution issue — make sure your node_modules are installed and up to date.

Still Not Working?

Verify Your Config Is Being Read

A surprisingly common mistake is editing the wrong next.config.js or having a syntax error that silently prevents the config from loading. Add a temporary console.log to verify:

// next.config.js
console.log('Config loaded:', JSON.stringify(nextConfig.images, null, 2));
module.exports = nextConfig;

Run npm run dev and check the terminal output. If you don’t see your images config printed, the file is not being read.

Clear the Image Cache

Next.js caches optimized images in .next/cache/images. If you’ve changed your config but images are still broken, clear the cache:

rm -rf .next/cache/images

Or delete the entire .next directory and rebuild:

rm -rf .next
npm run build

Check for Conflicting Middleware

If you have Next.js middleware that rewrites or redirects image requests, it can interfere with the /_next/image endpoint. Make sure your middleware matcher excludes image optimization routes:

// middleware.js
export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Test with a Minimal Example

If the error persists, create a minimal reproduction to isolate the issue. Strip your component down to just the <Image> tag with a local static image:

import Image from 'next/image';
import testImg from '../public/test.jpg';

export default function TestPage() {
  return <Image src={testImg} alt="Test" />;
}

If this works, the problem is with your specific src URL, configuration, or loader. Add back complexity one piece at a time until the error reappears.

Check Your Hosting Platform’s Documentation

Every hosting platform handles image optimization differently. Vercel supports it natively. Netlify requires the @netlify/plugin-nextjs plugin. AWS Amplify has its own limitations. Cloudflare Pages needs a custom loader. Docker deployments need sharp. Read your platform’s specific Next.js documentation — the default configuration rarely works everywhere without adjustment.

If your images load in development but break in production, the issue is almost always the loader or a missing sharp dependency. Check your deployment logs for warnings about image optimization. If you’re seeing environment-related issues where config values aren’t available at runtime, see Fix: Environment Variable is Undefined for common causes. And if your React components unmount unexpectedly during navigation, that can also cause image loading to abort mid-request.


Related: Fix: Next.js Hydration Mismatch | Fix: useEffect Infinite Loop | Fix: Cannot find module (TypeScript) | Fix: Environment Variable is Undefined | Fix: Cannot update unmounted component

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles