skip to content

A Next.js page renders `<Image src="https://cdn.example.com/a.jpg" width={800} height={600} alt="" />` and fails with an error saying that hostname is not configured under images in next.config. What is the fix, and why does Next require that configuration at all?

level: middleimportance: should knowfreq 54%

answer

  1. the browser requests your server, not the CDN
  2. the source URL is a query parameter
  3. a public endpoint that fetches arbitrary URLs
  4. narrow the path, not just the host
  5. wildcards widen the trust boundary

basics

~20 s

Add the host to images.remotePatterns in next.config. Next blocks unlisted hosts because /_next/image would otherwise be an open image proxy: anyone could make your server fetch, transform and serve arbitrary remote files under your own origin.

solid answer

~50 s

The fix is an entry in `images.remotePatterns` in `next.config` — at minimum the `protocol` and `hostname`, and you should also narrow `pathname` (and `port` where relevant) rather than allowing the whole host. The reason the allow-list exists is that `next/image` rewrites a remote `src` into a request to your own optimizer route, roughly `/_next/image?url=<remote>&w=800&q=75`. That route is public and takes the target URL as a query parameter. Without a check, anybody could point it at any URL on the internet and your server would dutifully fetch, transform, cache and serve those bytes — an open proxy that burns your CPU and bandwidth, launders third-party content through your domain, and can be aimed at internal addresses your server can reach. The allow-list turns the `url` parameter from arbitrary into a closed set. If you genuinely do not want the optimizer involved, `unoptimized` or a custom loader is the alternative, not a wildcard pattern.

code

javascript · 14 lines
javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'cdn.example.com',
        pathname: '/product-images/**',
      },
    ],
  },
};

module.exports = nextConfig;

go deeper

for a junior

Know that remote image hosts must be declared in images.remotePatterns in next.config, and that the entry names at least the protocol and hostname.

for a middle

Explain the indirection: the markup points at /_next/image with the source URL as a query parameter, so your server does the fetching and the allow-list is what constrains which URLs it will fetch.

for a senior

Argue the threat model out loud — open proxy, bandwidth and CPU theft, content served from your origin, internal-address reach — and write patterns that pin protocol and path rather than just the host.

for a principal

Own the policy: who may add hosts, whether wildcard subdomains are ever allowed, and when the right answer is to move image delivery to an external service with signed URLs instead of widening the allow-list.

## What actually happens with a remote `src` When you hand `next/image` an absolute URL, the rendered markup does not point at that URL. It points at Next's optimizer: ```html <img src="/_next/image?url=https%3A%2F%2Fcdn.example.com%2Fa.jpg&w=828&q=75"> ``` So the browser asks *your* server for the image; your server fetches it from the remote host, re-encodes it to the requested width and format, caches the result and streams it back. That indirection is what makes format negotiation and resizing possible for images you do not host. It also means your application exposes a public endpoint whose behaviour is "fetch the URL in this query parameter". ## Why an allow-list is not optional An unrestricted `url` parameter is a textbook open proxy, with several concrete consequences: - **Resource theft.** A third party embeds `https://yoursite.com/_next/image?url=…&w=3840` on their own pages. Your infrastructure pays for every fetch and every transform, and image transforms are CPU-expensive. - **Content laundering.** Anything served through that endpoint is served from *your* origin and appears to come from your domain — reputationally and, depending on the content, legally your problem. - **Server-side request forgery reach.** Your server, unlike a visitor's browser, may be able to resolve internal hostnames and private addresses. An endpoint that fetches an arbitrary URL on request is exactly the primitive an attacker wants, even when the response is only echoed back as an image. - **Cache pollution.** Each unique URL and size combination creates a cache entry, so an attacker can fill your image cache with junk. Next therefore refuses any remote `src` whose host is not declared, and the error is deliberately loud rather than a silent fallback to the original URL. ## The fix, written narrowly ```js // next.config.js module.exports = { images: { remotePatterns: [ { protocol: 'https', hostname: 'cdn.example.com', pathname: '/product-images/**', }, ], }, }; ``` Points worth making in an interview: - `hostname` supports a leading `**` or `*` wildcard for subdomains, but each wildcard widens the trust boundary — `**.example.com` trusts every subdomain, including one that might be taken over. - `pathname` is the lever most teams skip. If assets live under one prefix, say so; that prevents an account on a shared bucket host from being proxied through your app. - `protocol: 'https'` should be stated. Allowing `http` means your server makes plaintext fetches on behalf of a public endpoint. - An older, coarser `images.domains` list existed as a bare host array; `remotePatterns` supersedes it precisely because it can constrain protocol and path, not just host. Because this is build-time configuration, changing it requires a rebuild and redeploy — which is the point. A user-supplied host can never widen the set at runtime. ## When configuration is the wrong answer Sometimes you do not want Next fetching the image at all: - **`unoptimized`** (per component, or `images.unoptimized` globally) renders the original URL directly. No transform, no allow-list needed, no responsive srcset either — reasonable for assets that are already correctly sized and formatted, or for SVG. - **A custom loader** (`images.loader: 'custom'` with `images.loaderFile`, or the per-component `loader` prop) makes you build the URL yourself, typically pointing at an external image service. The `/_next/image` route is bypassed, so `remotePatterns` no longer applies — the external service does the resizing and enforces its own access rules. What is *not* an acceptable answer is `hostname: '**'`. It compiles, the error goes away, and you have shipped the open proxy the check was written to prevent. ## The shape of a good answer Name the config key, then explain the endpoint underneath it: `/_next/image` takes the source URL as a parameter, so the allow-list is what stops arbitrary URLs from being fetched by your server. A candidate who only says "you have to whitelist the domain" knows the fix; one who explains the open-proxy risk knows why the fix exists.

  • Why is `hostname: '**'` a bad way to make the error go away?
    It restores exactly the condition the check exists to prevent: any URL on the internet can be fed through your optimizer. You inherit the bandwidth and CPU cost of other people's traffic, serve unknown content from your origin, and hand out a server-side fetch primitive. If you truly need to accept arbitrary sources, bypass the optimizer with `unoptimized` or a custom loader instead.
  • If you configure a custom loader pointing at an external image service, does `remotePatterns` still apply?
    No. A custom loader means you construct the image URL yourself and the markup points at that service directly, so `/_next/image` is never involved and there is nothing for the allow-list to guard. Access control moves to the external service — its own signed URLs or source allow-list — which is a real consideration when you choose that route.
  • Why is the `pathname` field worth filling in even when the hostname is one you control?
    Because a hostname is often shared. Object-storage and CDN hosts frequently serve many tenants or many buckets under one domain, so allowing the bare host can let content you do not own be proxied through your app. Constraining the path to the prefix your assets actually live under keeps the allowed set as small as the real requirement.

saying these in an interview costs you the question

  • Fixes it with a wildcard hostname to silence the error
  • Thinks the browser fetches the remote CDN directly
  • Believes the check is only about build-time type safety
  • Says the allow-list can be extended at runtime from user input
  • Assumes unoptimized still produces a responsive srcset

context