← Blog
Next.jsCloudflare WorkersR2이미지 최적화

Cloudflare Workers에서 next/image가 404 날 때

·5분 읽기

증상

관리자가 업로드한 이미지가 로컬 개발 환경에서는 정상적으로 보였지만, Cloudflare Workers(OpenNext)에 배포한 뒤에는 특정 이미지들만 깨져서 404로 표시됐다. 브라우저 네트워크 탭을 보면 요청이 /_next/image로 나가고 있었다.

이미지가 저장되고 서빙되는 구조

이 프로젝트의 이미지 업로드 흐름은 다음과 같았다: 사용자가 이미지를 업로드하면 서버가 파일을 검증(용량·MIME 타입)한 뒤 Cloudflare R2 버킷에 저장하고, R2 오브젝트 키를 포함한 URL(/api/files/{key})을 반환한다. 이 URL은 그대로 DB 컬럼에 저장되고, 이후 페이지에서는 저장된 URL로 <Image> 컴포넌트를 렌더링한다.

즉 이미지 원본은 Vercel이나 Cloudflare Images 같은 표준 이미지 CDN이 아니라, 직접 만든 API 라우트가 R2에서 꺼내 서빙하는 구조였다.

원인 — 최적화 파이프라인이 커스텀 오리진을 처리하지 못함

next/image는 기본적으로 원본 이미지를 /_next/image 라우트로 프록시해 리사이즈·포맷 변환을 수행한 뒤 내려준다. 이 파이프라인이 우리가 만든 커스텀 API 라우트(R2에서 서빙하는 이미지)를 원본으로 받아 처리하려는 과정에서, Cloudflare Workers 런타임의 제약에 걸려 요청이 실패하고 404를 반환했다.

로컬(Node.js 서버)이나 Vercel에서는 이미지 최적화 서버가 별도로 동작해 문제가 드러나지 않았지만, Workers 런타임에서는 같은 파이프라인이 그대로 동작하지 않았다.

해결 — unoptimized로 최적화 파이프라인 우회

가장 빠르고 확실한 해결은 next/image에 unoptimized 옵션을 추가해 기본 최적화 파이프라인 자체를 건너뛰는 것이었다. 이렇게 하면 브라우저가 /api/files/{key} 원본 URL로 바로 요청을 보내고, R2에서 파일을 직접 서빙하는 API 라우트가 응답한다.

원본 서빙 라우트에는 Cache-Control: public, max-age=31536000, immutable 헤더를 붙여 리사이즈 없이도 캐싱 이점을 유지하도록 보완했다.

고려했던 다른 선택지

Cloudflare Images(자체 이미지 리사이징 서비스)를 붙이거나, next.config.ts의 images.loader를 커스텀 로더로 교체해 최적화 요청 자체를 다른 엔드포인트로 보내는 방법도 있다. 다만 이번 프로젝트는 이미지 개수와 트래픽 규모를 고려했을 때 unoptimized로 우회하는 것이 구현 비용 대비 합리적인 선택이었다.