Lesson 43 of 81
next/image will not optimize a remote URL it doesn't recognize — on purpose. remotePatterns is the allowlist, and the 400 you get without one is the framework refusing to fetch and transform an arbitrary external image on your server's behalf.
10 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
next/image's built-in optimizer runs on your server (or at the edge), fetching whatever src you give it, resizing it, and re-encoding it. For a local file in public/ or a statically imported image, Next.js already knows exactly what it's allowed to touch. For an absolute external URL — src="https://cdn.example.com/photo.jpg" — it has no such guarantee, and optimizing an arbitrary attacker-supplied URL on your server's compute would be a real vector for abuse. So it refuses, with a 400, unless that exact host (and optionally port, path, and query string) is explicitly allow-listed via remotePatterns in next.config.ts.
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "cdn.example.com",
port: "",
pathname: "/avatars/**",
search: "",
},
],
},
};
export default nextConfig;
Every field narrows the allowlist: protocol must match exactly, hostname must match (wildcards allowed), pathname restricts which paths on that host are fair game, and search can pin an exact query string. A request that doesn't satisfy all of them gets a 400 Bad Request instead of an optimized image.
// next.config.ts
const nextConfig: NextConfig = {
images: {
remotePatterns: [
// A Supabase Storage bucket — one fixed host, scoped to one path prefix
{
protocol: "https",
hostname: "xyzcompany.supabase.co",
pathname: "/storage/v1/object/public/avatars/**",
},
// Any subdomain of a CDN provider
{
protocol: "https",
hostname: "**.imagecdn.example.com",
},
],
},
};
The ** wildcard in pathname means "any number of path segments from here on" — it has to sit at the end of the pattern. The ** in hostname: '**.imagecdn.example.com' means the opposite: "any number of subdomain segments at the start," allowing a.imagecdn.example.com and a.b.imagecdn.example.com alike, but not a different domain entirely.
import hero from './hero.jpg') or a path into public/ (src="/logo.png") never touches remotePatterns at all — Next.js already has direct filesystem access to those and knows they're safe.* and ** mean different things, and ** only works at one end. A single * matches exactly one path segment or one subdomain level. ** matches any number of segments, but only at the very end of a pathname or the very start of a hostname — it doesn't work in the middle of a pattern.domains: ['cdn.example.com'] array still parses, but it's a real security downgrade, not just a stylistic preference. It can't restrict protocol, port, or path at all — any content at any path on that domain becomes fair game, which matters a lot on a host you don't fully control.remotePatterns at the new location. If cdn.example.com redirects to somewhere else, Next.js follows it without re-validating the destination — maximumRedirects: 0 in next.config.ts is the knob if that's a real concern for a given source.remotePatterns fails loudly the first time a real user loads the page — worth checking explicitly before shipping a new image source, not just trusting that "it worked when I tested it."The lesson on image optimization with next/image mentions remotePatterns in passing, as the fix for a remote src that 400s. This lesson is that detail expanded — the actual field-by-field shape of a pattern, how the wildcard syntax behaves, and the specific tradeoffs that make a loosely-scoped pattern a real security question rather than just a configuration chore.