- Published on
Next.js 이미지 최적화 느림·깨짐 9가지 원인
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Next.js의 next/image는 “자동 최적화(리사이즈, WebP/AVIF 변환, 지연 로딩, 캐싱)”를 제공하지만, 운영 환경에서는 느려지거나(로딩 지연/서버 부하) 혹은 깨지는(404/500/빈 이미지/레이아웃 튐) 문제가 의외로 자주 발생합니다. 특히 CDN, 리버스 프록시(Nginx), 사설 스토리지(S3 호환), 외부 이미지 호스팅을 섞어 쓰면 작은 설정 하나로 최적화 파이프라인이 무너질 수 있습니다.
이 글은 흔한 증상을 단서로 9가지 원인을 빠르게 좁히고, 재현/검증 포인트와 함께 실전 해결책을 제공합니다. (대상: Next.js 13~15, App Router/Pages Router 공통)
1) images.remotePatterns/domains 누락으로 원격 이미지 차단
증상
- 개발에서는 보이는데 배포 후 이미지가 깨짐
- 콘솔에
Invalid src prop또는 “hostname is not configured” 류 오류 / _next/image?url=...요청이 400/500
원인
Next.js는 보안상 원격 이미지 도메인을 명시적으로 허용해야 합니다. next.config.js에서 images.domains(구 방식) 또는 images.remotePatterns(권장)를 설정하지 않으면 최적화 요청 자체가 실패합니다.
해결
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.example.com',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'cdn.example.net',
pathname: '/assets/**',
},
],
},
};
module.exports = nextConfig;
검증 포인트: 브라우저에서 깨지는 이미지의 네트워크 탭을 열고, 실제 호출되는 최적화 엔드포인트(/_next/image?...)가 400/500인지 확인합니다.
2) width/height 또는 fill 설정 불일치로 레이아웃 붕괴/빈 이미지
증상
- 이미지 영역이 0px로 접히거나, CLS(레이아웃 점프)가 심함
fill을 썼는데 이미지가 안 보임
원인
next/image는 레이아웃 계산을 위해 width/height 또는 fill+부모 컨테이너 스타일이 필요합니다. 특히 fill은 **부모가 position: relative**이고 명시적인 크기가 있어야 합니다.
해결
import Image from 'next/image'
export default function Hero() {
return (
<div style={{ position: 'relative', width: '100%', height: 320 }}>
<Image
src="https://images.example.com/hero.jpg"
alt="hero"
fill
sizes="(max-width: 768px) 100vw, 1200px"
style={{ objectFit: 'cover' }}
priority
/>
</div>
)
}
검증 포인트: 부모 요소의 computed style에서 height가 0이 아닌지, position: relative가 적용됐는지 확인합니다.
3) sizes 미설정으로 과도한 리사이즈/대역폭 낭비 → 느림
증상
- 모바일에서 특히 느림
- 이미지가 필요 이상으로 큰 크기로 다운로드됨
원인
fill 또는 반응형 레이아웃에서 sizes를 지정하지 않으면, 브라우저가 큰 리소스를 선택하거나 Next가 불필요하게 큰 변환을 수행할 수 있습니다.
해결
<Image
src="/product.jpg"
alt="product"
width={1200}
height={800}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 600px"
/>
검증 포인트: DevTools → Network에서 이미지 리소스의 실제 다운로드 크기와 srcset 선택 결과를 확인합니다.
4) 프록시/CDN이 /_next/image를 캐시하지 않거나, 반대로 잘못 캐시해서 깨짐
증상
- 첫 요청이 매우 느리고 이후도 계속 느림(캐시 미스 반복)
- 특정 이미지가 가끔 깨지거나 다른 이미지로 바뀜(캐시 키 충돌)
원인
/_next/image는 쿼리스트링(url, w, q) 기반으로 동작합니다. CDN/프록시가 쿼리스트링을 무시하거나, 캐시 정책이 비정상(캐시 안 함/너무 짧음/키 충돌)일 때 문제가 납니다.
또한 일부 프록시는 긴 URL을 제한해 414/413 류 에러를 유발할 수 있습니다. 이때는 “요청 크기 제한”도 함께 점검해야 합니다. 비슷한 성격의 장애 대응 관점은 Kubernetes API 413 Request Entity Too Large 해결 글의 트러블슈팅 흐름이 참고가 됩니다.
해결(개념)
- CDN에서
/_next/image경로를 쿼리스트링 포함 캐시 - 캐시 키에
url,w,q포함 - 오리진의
Cache-Control/ETag/Vary: Accept존중
Nginx 예시(쿼리 포함 캐시 키를 명시적으로 구성하는 편이 안전):
location /_next/image {
proxy_pass http://next_upstream;
# 쿼리스트링 포함 캐시 키
proxy_cache my_cache;
proxy_cache_key "$scheme$proxy_host$request_uri";
proxy_cache_valid 200 301 302 60m;
add_header X-Cache-Status $upstream_cache_status;
}
검증 포인트: 응답 헤더에 X-Cache-Status(HIT/MISS), Cache-Control, Vary를 확인하고, 같은 URL로 반복 요청 시 HIT가 나는지 확인합니다.
5) Vary: Accept 손실로 WebP/AVIF 협상이 꼬여 “깨진 이미지”처럼 보임
증상
- Safari/특정 브라우저에서만 이미지가 깨짐
- 어떤 환경에서는 정상, 어떤 환경에서는 “지원하지 않는 포맷”처럼 표시
원인
Next는 클라이언트의 Accept 헤더에 따라 AVIF/WebP/원본 포맷을 선택합니다. 이때 CDN/프록시가 Vary: Accept를 제거하거나, 잘못된 캐시로 다른 포맷을 내려주면 특정 브라우저에서 디코딩 실패가 발생합니다.
해결
Vary: Accept가 오리진에서 내려오면 CDN이 이를 유지하도록 설정- CDN이 강제로 이미지 변환을 한다면 Next의 변환과 충돌하지 않게 정리(한쪽만 담당)
검증 포인트: 같은 이미지 URL을 Chrome과 Safari에서 요청했을 때 Content-Type이 어떻게 달라지는지 비교합니다.
6) 외부 이미지 서버가 느리거나 리다이렉트/인증이 끼어 최적화가 병목
증상
/_next/image자체 응답이 느림(TTFB 증가)- 원본 이미지 서버가 간헐적으로 302/403/429/5xx
원인
Next의 최적화는 원본 이미지를 가져온 뒤 변환합니다. 원본이 느리면 최적화도 같이 느려집니다.
- 302 체인(HTTP→HTTPS, 지역 리다이렉트)
- 서명 URL 만료
- 핫링크 방지로 403
- 레이트 리밋(429)
레이트 리밋이 원인이라면 “재시도/백오프/큐잉” 같은 패턴이 근본 대응이 됩니다. 이미지 파이프라인은 다르지만 장애 대응 설계 관점은 OpenAI 429/Rate Limit 대응 - 재시도·백오프·큐잉 글과 유사합니다.
해결
- 원본 이미지를 CDN에 올려 지리적으로 가깝게 제공
- 불필요한 리다이렉트 제거
- 서명 URL을 쓰면 캐시 전략 재설계(만료 전 충분히 캐시되도록)
- 핫링크 방지 규칙에
/_next/image오리진을 예외 처리
검증 포인트: /_next/image가 내부적으로 가져오는 원본 URL을 직접 curl로 호출해 응답 시간/리다이렉트/상태코드를 확인합니다.
7) 서버리스/컨테이너에서 이미지 변환이 CPU/메모리 병목 → 타임아웃/500
증상
- 트래픽이 늘면 이미지가 500으로 깨짐
- 로그에 타임아웃, 메모리 부족, 프로세스 재시작
원인
AVIF/WebP 변환은 CPU를 많이 쓰고, 큰 원본 이미지는 메모리도 많이 사용합니다. 서버리스 함수 제한(메모리/실행시간)이나 컨테이너 리소스 제한이 낮으면 변환 중 실패합니다.
쿠버네티스 환경에서 OOM이 반복되면 이미지 최적화 워커가 계속 죽어 “간헐적 깨짐”으로 보일 수 있습니다. 이런 경우는 애플리케이션 코드보다 리소스/GC/Limit 튜닝이 우선인 경우가 많습니다. 관련 트러블슈팅 흐름은 EKS Pod OOMKilled 반복 원인과 메모리·GC·Limit 튜닝도 참고할 만합니다.
해결
next/image최적화를 오리진에서 하지 말고 CDN/이미지 전용 서비스로 위임- 혹은 변환 품질/사이즈 정책을 제한(너무 큰
w허용 금지) - 런타임 리소스 상향(메모리/CPU), 동시성 제한
검증 포인트: 피크 시간대에 /_next/image의 p95/p99 latency와 5xx 비율, 컨테이너 OOM/재시작 횟수를 함께 봅니다.
8) unoptimized/커스텀 loader 오용으로 “최적화가 안 되거나” URL이 깨짐
증상
- 이미지가 항상 원본 그대로 내려와 느림
- 혹은 loader가 만든 URL이 404
원인
unoptimized를 켜면 Next 변환 파이프라인을 건너뜁니다.- 커스텀 loader는 반드시 올바른 변환 URL 규칙을 만들어야 합니다. (예: Cloudinary, imgix)
해결
Cloudinary 예시:
import Image from 'next/image'
const cloudinaryLoader = ({ src, width, quality }) => {
const q = quality || 75
return `https://res.cloudinary.com/<cloud>/image/fetch/f_auto,q_${q},w_${width}/${encodeURIComponent(src)}`
}
export default function Card() {
return (
<Image
loader={cloudinaryLoader}
src="https://images.example.com/a/b/c.jpg"
alt="card"
width={640}
height={360}
/>
)
}
검증 포인트: loader가 생성한 최종 URL을 복사해 브라우저에서 직접 열어 200이 나는지 확인합니다.
9) next start가 아닌 방식/엣지 런타임 제약으로 최적화가 비활성/오동작
증상
- 로컬에서는 되는데 서버에서만 최적화가 동작하지 않음
next export(정적 export)에서next/image가 기대와 다르게 동작
원인
next/image의 기본 최적화는 Node 서버 기능에 의존합니다.- 정적 export 환경에서는 최적화가 제한되거나 별도 설정이 필요합니다.
- Edge Runtime에서는 일부 네이티브 모듈/변환 경로가 제약을 받을 수 있고, 배포 플랫폼에 따라 동작이 다릅니다.
해결
- SSR/Node 런타임으로 운영한다면 표준대로
next build+next start - 정적 사이트라면 이미지 최적화는 빌드 타임(사전 변환) 또는 CDN 이미지 변환으로 설계
- 플랫폼별 권장 방식(Vercel Image Optimization, CloudFront Functions/Lambda@Edge, Cloudflare Images 등) 검토
간단 체크 스크립트(운영 서버에서 실행)로 실제 실행 커맨드/환경변수를 확인해두면 좋습니다.
ps aux | grep next
node -p "process.versions"
node -p "process.env.NEXT_RUNTIME"
빠른 진단 체크리스트(현장용)
/_next/image요청이 400/403/404/500 중 무엇인지 먼저 본다.- 깨지는 이미지의
src가 원격이면remotePatterns부터 확인한다. fill을 썼다면 부모의position/size를 확인한다.- Network에서 실제 다운로드된 이미지 크기가 과한지(
sizes부재) 확인한다. - CDN/프록시가 쿼리스트링과
Vary: Accept를 제대로 캐시하는지 확인한다. - 원본 이미지 서버의 응답(리다이렉트/인증/레이트리밋)을 직접 호출로 확인한다.
- 피크 타임에 CPU/메모리/타임아웃으로 변환이 실패하는지 확인한다.
결론
Next.js 이미지 최적화 문제는 “Next가 느리다/깨진다”로 보이지만, 실제로는 (1) 원격 도메인 허용, (2) 레이아웃 파라미터, (3) sizes로 대표되는 리사이즈 정책, (4) 프록시/CDN 캐시와 헤더, (5) 원본 서버 품질, (6) 런타임 리소스로 쪼개서 보면 빠르게 해결됩니다.
운영에서 안정성을 최우선으로 한다면, next/image를 그대로 쓰되 캐시/헤더/리소스 한계를 먼저 정리하고, 트래픽이 커질수록 “오리진 변환”을 줄이는 방향(CDN/이미지 전용 서비스 위임)으로 아키텍처를 발전시키는 것이 가장 비용 대비 효과가 좋습니다.