1. 개요
Next.js 의 <Image> 는 /_next/image 경로로 원격 이미지를 받아 리사이즈·포맷 변환해 준다. 좋은 기능이다. 그런데 이 프로젝트에서는 일부러 끄고 원본을 직접 서빙한다. 대신 업로드 시점에 서버가 축소한다. 왜 그렇게 했고, 그 결과 55페이지 전수 크롤이 102MB 에서 22MB 로 줄어든 과정을 적는다.
2. 핵심 내용
2-1. 첫 문제: 컨테이너 안의 Next 가 스토리지에 못 붙는다
초기(2026년 1월) 계획서의 문제는 이거였다. <Image> 최적화는 Next 서버 가 원격 이미지를 가져온다. 컨테이너 안의 Next 서버가 localhost:9000 의 스토리지를 부르면 실패한다. 그 localhost 는 컨테이너 자신이다. 400 이 났다.
remotePatterns 에 내부 호스트를 넣고 네트워크를 이어 주면 풀린다. 하지만 그러면 이미지 요청마다 Next 서버가 스토리지에서 원본을 받아 변환한다. 서버 하나로 두 사이트를 서빙하는 구조에서 이 CPU 는 아깝다.
2-2. 결정: /_next/image 를 우회한다
next.config.ts 에 images 설정이 없다. 원격 이미지를 쓰는 컴포넌트는 전부 <Image unoptimized> 다. 이미지는 공개 리소스 도메인에서 브라우저가 직접 받는다.
주석에 이렇게 적어 뒀다. remotePatterns 를 비워 두면 누가 unoptimized 를 빠뜨렸을 때 즉시 실패해 드러난다. 화이트리스트를 채워 두면 “가끔 최적화 경로를 타는” 이미지가 조용히 섞인다. 비워 두는 편이 더 안전하다.
캐시는 게이트웨이가 Cache-Control: max-age=31536000, immutable 을 붙인다. 파일명이 UUID 라 같은 URL 의 내용이 바뀔 일이 없다.
2-3. 대신 업로드 때 서버가 줄인다
최적화를 끄면 원본이 그대로 나간다. 관리자가 4608px 사진을 올리면 138px 썸네일 자리에 그 사진이 그대로 내려간다. 그래서 축소를 업로드 시점 으로 옮겼다.
- 긴 변 1920px 로 줄인다. 처음엔 2560 이었는데 1920 으로 낮추니 용량이 40% 더 줄었고 화질 차이는 눈으로 구분되지 않았다.
- EXIF 방향을 보정한다. 안 하면 폰 사진이 눕는다.
- GIF 는 건너뛴다. 애니메이션이 깨진다.
- 축소한 결과가 원본보다 크면 원본을 유지한다. 작은 PNG 를 다시 인코딩하면 커지는 경우가 있다.
- 고객이 첨부한 도면·스캔은 제외한다. 이건 축소하면 안 되는 문서다.
2-4. PNG 판정을 두 번 고쳤다
PNG 를 JPEG 로 바꾸면 사진은 크게 줄지만 로고·아이콘 같은 그래픽은 망가진다. “이 PNG 가 사진인가” 를 판정해야 했다.
첫 판정은 픽셀당 바이트 였다. 1.0 미만이면 사진. 잘 압축된 불투명 사진이 0.937 로 판정선 아래에 떨어져 그래픽으로 분류됐다. 임계값을 0.9 로 낮추면 다른 게 걸린다. 임계값 손질은 같은 실수를 반복한다.
두 번째 판정은 고유 색상 수 다. 사진은 수만 색, 그래픽은 수십~수백 색. 경계가 훨씬 넓다. 투명도가 있으면 그래픽으로 본다(전수 조사). 이 판정으로 바꾼 뒤 오탐이 없었다.
2-5. 부수 발견: w-full 이 저자 지정 폭을 덮었다
크롤하다 서명 이미지가 4배 확대돼 있는 걸 봤다. 에디터에서 관리자가 previewWidth 를 지정했는데, 정적 렌더 컴포넌트의 w-full 이 그걸 덮었다. 이미지 용량 문제가 아니라 CSS 우선순위 문제였다. 전수 크롤은 이런 걸 같이 잡아 준다.
2-6. 숫자
| 항목 | 전 | 후 |
|---|---|---|
| 55페이지 전수 크롤 | 102.5MB | 21.9MB |
| 이미지 캐시 헤더 | 없음(cf-cache-status: DYNAMIC) | 1년 immutable |
같은 세션에서 컨테이너 빌드를 돌리다 Node 20 EOL + jsdom 30 비호환 을 발견해 Node 22 로 올렸다. 호스트 Node 로 도는 로컬 빌드가 못 잡는 문제가 컨테이너 빌드에서 드러난다. 이미지 작업을 하다 런타임을 고친 셈이다.
3. 마무리
요약
/_next/image는 Next 서버의 CPU 를 쓴다. 서버 하나로 여러 사이트를 서빙하면 아깝다.- 대신 업로드 시점에 축소. 1920px, EXIF 보정, GIF 스킵, 결과가 크면 원본 유지, 문서 제외.
- PNG 사진 판정은 임계값이 아니라 고유 색상 수로. 임계값 손질은 같은 실수를 반복한다.
remotePatterns를 비워 두면unoptimized누락이 즉시 드러난다.
다음은 14시간 500 장애다.