Configuring next/image External Image Domain Allowlists
How to properly configure external image domains and patterns in next/image for security and performance
Configuring next/image External Image Domain Allowlists
Introduction
Next.js restricts which external image domains can be used with the next/image component. This is a security feature that prevents your application from being used as an open proxy for image optimization. However, configuring the allowlist incorrectly leads to runtime errors and broken images.
This post explains the two approaches to allowing external image domains, their differences, and how to configure them correctly.
Environment
- OS: Windows 11
- Node.js: v20.10.0
- Next.js: 14.0.4
- External Image Sources: Unsplash, Cloudinary, S3-compatible storage
Problem
Using an unconfigured external image produces:
Error: Invalid src prop (https://images.unsplash.com/photo-123456) on `next/image`,
hostname "images.unsplash.com" is not configured under images in next.config.jsEven after adding the domain, images may still fail:
Error: Invalid remote config for image optimization
The "hostname" option is deprecated. Use "remotePatterns" instead.Error: Image optimization is not supported for the "http" protocol.
Use "https" instead.Analysis
Next.js provides two methods for configuring external image domains:
1. images.domains (deprecated). A simple array of hostnames. Works but lacks security features like pathname filtering and protocol restriction.
2. images.remotePatterns (recommended). A structured configuration that allows fine-grained control over which URLs are permitted.
The deprecated domains option does not support:
- Restricting by pathname
- Restricting by protocol (HTTP vs HTTPS)
- Wildcard subdomain matching with pattern syntax
Solution
Fix 1: Use remotePatterns (recommended)
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
// Unsplash - allow all images
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
// Cloudinary - allow specific account
{
protocol: 'https',
hostname: '*.cloudinary.com',
pathname: '/demo/**',
},
// S3 bucket - restrict to specific path
{
protocol: 'https',
hostname: 'my-bucket.s3.amazonaws.com',
pathname: '/images/**',
},
// localhost for development
{
protocol: 'http',
hostname: 'localhost',
port: '3000',
},
],
},
}
module.exports = nextConfigFix 2: Handle CDN and proxy configurations
// next.config.js
const nextConfig = {
images: {
remotePatterns: [
// Contentful CDN
{
protocol: 'https',
hostname: 'images.ctfassets.net',
pathname: '/**',
},
// WordPress media
{
protocol: 'https',
hostname: '*.wp.com',
pathname: '/**',
},
// Custom CDN with subdomains
{
protocol: 'https',
hostname: '*.cdn.example.com',
pathname: '/media/**',
},
],
// Set minimum cache TTL for external images
minimumCacheTTL: 60 * 60 * 24, // 24 hours
// Define device sizes for responsive images
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
},
}
module.exports = nextConfigFix 3: Environment-based configuration
// next.config.js
const isDev = process.env.NODE_ENV === 'development'
const devPatterns = [
{
protocol: 'http',
hostname: 'localhost',
},
]
const prodPatterns = [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
{
protocol: 'https',
hostname: '*.cloudinary.com',
pathname: '/my-app/**',
},
]
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: isDev ? devPatterns : prodPatterns,
},
}
module.exports = nextConfigFix 4: Use the image component correctly with allowed domains
// components/ExternalImage.tsx
import Image from 'next/image'
interface ExternalImageProps {
src: string
alt: string
width?: number
height?: number
}
export function ExternalImage({
src,
alt,
width = 800,
height = 600,
}: ExternalImageProps) {
// Validate that the image URL is from an allowed domain
const allowedHosts = [
'images.unsplash.com',
'images.ctfassets.net',
]
const url = new URL(src)
if (!allowedHosts.includes(url.hostname)) {
console.warn(`Image from ${url.hostname} is not in the allowlist`)
return null
}
return (
)
}Lessons Learned
Always use
remotePatternsoverdomains. Thedomainsoption is deprecated and will be removed in a future version.Be as specific as possible. Restricting by hostname and pathname reduces the attack surface if your application is compromised.
Use
httpprotocol only for localhost. Production external images should always usehttps.Test with actual image URLs from each domain. A typo in the hostname configuration will not produce a build error but will cause runtime failures.
Monitor image optimization bandwidth. Each optimized image consumes server resources. Use
minimumCacheTTLto prevent re-optimizing the same image repeatedly.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.