- Published on
Next.js 이미지 최적화 실패? remotePatterns·403 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버 렌더링(SSR)과 성능 최적화를 위해 Next.js의 next/image를 도입했는데, 배포 후 갑자기 이미지가 안 뜨거나 콘솔에 Image Optimization using Next.js' default loader is not compatible with ... 류의 메시지, 혹은 네트워크 탭에서 403이 떨어지는 상황을 자주 만납니다. 특히 외부 도메인(예: S3, CloudFront, 사내 CDN, 인증이 걸린 이미지 서버)에서 이미지를 가져오는 경우 remotePatterns 설정과 원격 서버의 접근 정책이 동시에 얽히면서 문제를 더 복잡하게 만듭니다.
이 글에서는 (1) Next.js 설정(remotePatterns) 문제와 (2) 원격 서버가 Next.js 최적화 요청을 403으로 거부하는 문제를 분리해서, 재현 → 진단 → 해결까지 한 번에 정리합니다.
증상: “이미지 최적화 실패”는 대부분 두 갈래다
Next.js의 이미지 최적화는 브라우저가 원본 이미지를 직접 받는 구조가 아니라, 보통 아래처럼 동작합니다.
- 페이지에서
<Image src="https://remote/image.jpg" />렌더링 - 브라우저는 실제로
/_next/image?url=<encoded>&w=...&q=...로 요청 - Next.js 서버(또는 Vercel/런타임)가 원격
url로 원본 이미지를 서버 측에서 fetch - 최적화(리사이즈/포맷 변환) 후 브라우저로 전달
따라서 실패 원인은 크게 두 가지입니다.
- A. Next.js가 원격 URL을 허용하지 않음:
next.config.js의images.remotePatterns/domains설정 누락 또는 패턴 불일치 - B. 원격 서버가 Next.js 서버의 fetch를 거부: 핫링크 차단, User-Agent/Referer 기반 차단, 서명 URL/쿠키 필요, WAF, IP allowlist, S3/CloudFront 정책 등으로 403
특히 B는 “브라우저에서 직접 열면 잘 보이는데 Next.js에서만 403” 같은 형태로 나타납니다. 이때는 브라우저가 아니라 Next.js 서버가 요청 주체라는 점이 핵심입니다.
1) remotePatterns 제대로 설정하기 (가장 흔한 원인)
remotePatterns vs domains: 무엇을 써야 하나?
images.domains: 호스트만 허용(구버전부터 많이 사용). 경로/프로토콜/포트 제한이 약함.images.remotePatterns: 프로토콜/호스트/포트/패스까지 패턴으로 제한. 보안적으로 권장.
가능하면 remotePatterns를 쓰는 게 안전합니다.
예시: S3/CloudFront/사내 CDN 패턴 허용
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example-bucket.s3.ap-northeast-2.amazonaws.com',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'd111111abcdef8.cloudfront.net',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: 'cdn.company.internal',
pathname: '/assets/**',
},
],
},
}
module.exports = nextConfig
자주 하는 실수 체크리스트
protocol누락 또는http/https불일치hostname에 와일드카드 기대(버전에 따라 제한적). 안전하게 정확한 호스트를 명시pathname가 실제 URL과 불일치(예:/image/**로 해놓고 실제는/images/...)- 이미지 URL에 쿼리스트링이 붙는 경우(예:
?v=123)는 대개 pathname과 무관하지만, 서버 측에서 서명 검증 로직이 쿼리에 민감하면 403의 단초가 됩니다.
개발 환경에서는 되는데 배포에서만 실패?
로컬에서는 next dev가 느슨하게 보이거나, 이미지가 캐시된 상태로 보일 수 있습니다. 배포 환경(특히 서버리스/엣지)에서는 정확히 설정된 패턴만 통과하므로 remotePatterns를 기준으로 다시 검증하세요.
2) 403의 본질: Next.js가 “서버에서” 이미지를 가져오다 막힌다
/_next/image는 Next.js 서버가 원격 이미지를 fetch합니다. 즉, 원격 서버 입장에서는 요청이 이렇게 보입니다.
- 클라이언트 IP가 아니라 Next.js 서버 IP(Vercel이면 Vercel egress)
Referer가 없거나 기대와 다름User-Agent가 브라우저가 아니라 Node/undici 계열- 쿠키/세션/Authorization 헤더가 없음
따라서 “브라우저에서 직접 열면 200인데, /_next/image만 403”이면 원격 서버의 정책을 의심해야 합니다.
2-1) 진단: Next.js가 실제로 어떤 URL을 요청하는지 확인
브라우저 네트워크 탭에서 이미지 요청 URL이 보통 다음 형태입니다.
/_next/image?url=https%3A%2F%2Fd111111abcdef8.cloudfront.net%2Fimages%2Fcat.jpg&w=640&q=75
이때 서버 로그(Next.js 런타임 로그)에서 403이 난다면, 원격 서버가 거부한 것입니다.
로컬에서도 동일하게 재현하려면, 원격 이미지 URL을 서버에서 직접 fetch해보세요.
node -e "fetch('https://remote.example.com/images/cat.jpg').then(r=>console.log(r.status)).catch(console.error)"
여기서도 403이면 Next.js 문제가 아니라 원격 서버 접근 정책 문제가 맞습니다.
2-2) CloudFront/S3에서 흔한 403 원인
- S3 버킷이 퍼블릭이 아니고 OAC/OAI로 CloudFront만 허용하는데, Next.js가 S3로 직접 접근
- CloudFront가 Signed URL / Signed Cookie를 요구
- WAF/보안 정책이 특정 User-Agent/헤더/지역/IP를 차단
- 핫링크 방지:
Referer기반 차단
S3 403의 원인을 더 넓게 점검해야 할 때는 아래 글의 체크리스트가 도움이 됩니다.
(Next.js가 EKS에서 떠 있거나, 이미지가 S3 기반이면 진단 포인트가 겹칩니다.)
3) 해결 전략 1: “최적화는 하되” 원격 서버가 허용하도록 맞춘다
3-1) 이미지 서버에 핫링크/UA 차단이 있다면 정책 수정
원격 서버가 User-Agent가 브라우저가 아닐 때 막는 경우가 있습니다. Next.js는 내부적으로 fetch를 사용하므로 UA가 일반 브라우저와 다를 수 있습니다.
- 가능하면 특정 UA를 허용하거나
- 특정 경로(/images/)만 공개**하고 나머지는 보호
- 또는 Next.js 서버의 egress IP allowlist(고정 IP가 가능한 인프라에서)
다만 Vercel처럼 egress IP가 고정되지 않는 환경이면 allowlist 전략이 어려울 수 있습니다.
3-2) Signed URL이 필요한 이미지는 next/image 최적화가 구조적으로 불리할 수 있다
Signed URL은 만료 시간이 있고, 요청 주체/헤더 조건이 붙기도 합니다. Next.js 최적화는 서버가 원본을 다시 가져오기 때문에:
- 서명 만료로 403
- 서명 검증 조건 불일치
- 캐시된 최적화 이미지와 원본 URL 만료 타이밍 불일치
이 경우는 아래 “해결 전략 2”로 우회하는 편이 실전에서 더 안정적입니다.
4) 해결 전략 2: 최적화 파이프라인을 바꾼다(우회/대체)
4-1) 특정 이미지만 unoptimized로 처리
원격 정책 때문에 최적화가 실패한다면, 해당 이미지만 최적화를 끄고 브라우저가 원본을 직접 받게 할 수 있습니다.
import Image from 'next/image'
export default function Avatar() {
return (
<Image
src="https://secure.example.com/avatar?id=123"
alt="avatar"
width={80}
height={80}
unoptimized
/>
)
}
장점: 빠른 해결, 403 회피 가능 단점: 리사이즈/포맷 변환 이점 상실(대신 CDN에서 최적화 제공하면 괜찮음)
4-2) loader로 “이미 최적화된 CDN URL”을 반환
이미지 CDN(Cloudinary, Imgix, Akamai Image Manager, CloudFront Functions/Lambda@Edge 기반 변환 등)을 쓰는 경우, Next.js 서버가 원본을 fetch하지 않도록 loader에서 변환 URL을 직접 만들어 브라우저가 CDN을 바로 치게 할 수 있습니다.
import Image, { ImageLoaderProps } from 'next/image'
const cdnLoader = ({ src, width, quality }: ImageLoaderProps) => {
const q = quality ?? 75
// 예: https://cdn.example.com/resize?url=<src>&w=<width>&q=<q>
return `https://cdn.example.com/resize?url=${encodeURIComponent(src)}&w=${width}&q=${q}`
}
export default function ProductImage() {
return (
<Image
loader={cdnLoader}
src="https://origin.example.com/images/p1.jpg"
alt="p1"
width={800}
height={600}
/>
)
}
이 방식은 Next.js의 기본 최적화 엔드포인트(/_next/image)를 우회하므로, 원격 origin이 Next.js 서버를 403으로 막는 문제를 피해갈 수 있습니다.
4-3) 아예 next/image 대신 <img>를 쓰는 게 나은 경우
- 인증이 필요한 이미지(쿠키/세션 기반)
- 사용자별로 권한이 달라지는 이미지
- 매우 짧은 TTL의 서명 URL
이런 케이스는 next/image 최적화가 오히려 장애 포인트가 됩니다. 성능은 CDN/브라우저 캐시/loading="lazy"로 보완하고, 접근 제어는 원격 서버에서 일관되게 처리하는 편이 안정적입니다.
5) 운영 환경에서의 체크 포인트(로그/인프라 관점)
5-1) Next.js가 떠 있는 환경의 네트워크 제약
EKS/사내망에서 Next.js를 운영한다면, 원격 이미지 서버로 나가는 egress가 NAT/프록시/WAF를 거치며 403/401/timeout이 섞여 보일 수 있습니다. 특히 “인증 실패처럼 보이는 네트워크 문제”도 많습니다.
- 인증/권한 이슈를 체계적으로 분류하는 관점은 아래 글도 참고할 만합니다.
5-2) 403이 아니라 504/timeout로 보인다면
원격 이미지 서버가 느리거나, ALB/Ingress의 idle timeout에 걸리면 이미지 최적화 요청이 타임아웃으로 실패합니다. 겉으로는 “이미지 최적화가 안 됨”으로 보이지만 실제는 네트워크/타임아웃 문제일 수 있습니다.
6) 빠른 해결을 위한 결론: 원인별 처방전
A. 에러가 “허용되지 않은 호스트/패턴”이면
next.config.js의images.remotePatterns에 프로토콜/호스트/패스를 정확히 추가- 배포 후 재시작(빌드 산출물에 반영 필요)
B. /_next/image에서만 403이면
- 서버에서
fetch(remoteUrl)로 403 재현 → 원격 서버 정책 문제 확정 - 원격 서버에서 핫링크/UA/서명/쿠키 요구사항 점검
- 해결이 어렵다면
- 해당 이미지만
unoptimized적용 - 또는 커스텀 loader로 이미지 CDN을 직접 호출
- 또는
<img>로 전환
- 해당 이미지만
Next.js 이미지 최적화는 강력하지만, **원격 리소스를 “서버가 대신 가져오는 구조”**라는 사실 때문에 보안 정책과 충돌하기 쉽습니다. remotePatterns로 허용 범위를 정확히 잡고, 403이 뜨면 “Next.js 서버가 원격에서 거부당했다”는 관점으로 원격 정책/서명/핫링크 방지를 우선 점검하면 대부분 빠르게 해결됩니다.