프로젝트 개요

문제 정의

Next/Image는 자동 WebP 변환, 사이즈별 최적화, Lazy Loading 등 다양한 성능 최적화 기능을 내장한 강력한 솔루션입니다. 저는 이 기능을 '만능 해결책'으로 간주하고, 프로젝트 내 프로필 이미지, 아이콘, 썸네일 등 모든 이미지에 Next/Image 컴포넌트를 일괄적으로 적용했습니다.

그 결과, 프로덕트 배포 후 단 10분 만에 Vercel의 이미지 최적화 무료 사용량(월 5,000회)을 모두 소진하는 문제가 발생했습니다. 이는 즉각적인 추가 비용 발생으로 이어졌고, 향후 트래픽 증가 시 이미지 최적화 비용이 예측 불가능하게 증가할 수 있다는 것을 의미했습니다.

선별적 최적화와 커스텀 컴포넌트

비용 문제를 해결하고 확장성을 확보하기 위해, 모든 이미지에 동일한 솔루션을 적용하는 대신 이미지의 중요도와 목적에 따라 최적화 전략을 분리하기로 결정했습니다.

첫째, Next/Image는 SEO에 직접적인 영향을 주거나 여러 디바이스에서 최적의 해상도를 보여줘야 하는 핵심 이미지(예: 레시피 썸네일)에만 선별적으로 사용했습니다. 이때도 무분별한 최적화를 막기 위해 next.config.jsdeviceSizes 옵션을 2~3개의 특정 사이즈로 제한하여, 생성되는 최적화 이미지의 가짓수를 통제했습니다.

둘째, 그 외의 모든 이미지(UI 아이콘, 정적 에셋 등)를 처리하기 위한 재사용 가능한 커스텀 Image 컴포넌트를 직접 구현했습니다. 이 컴포넌트는 IntersectionObserver를 이용한 Lazy Loading, 로딩 중 레이아웃 쉬프트(CLS) 방지를 위한 고정 사이즈, 그리고 스켈레톤 UI를 기본으로 탑재하여 성능과 사용자 경험을 모두 고려했습니다.

()

구현 과정의 기술적 난관과 해결

커스텀 컴포넌트를 구현하며 두 가지 주요 기술적 문제에 직면했습니다.

첫째, 이미지별로 다른 로딩 UI를 제공해야 하는 요구사항이 있었습니다. 예를 들어, 메인 썸네일은 스켈레톤 UI가 필요하지만, 작은 아이콘은 로딩 UI 없이 즉시 나타나야 했습니다. 이는 컴포넌트 propsloadingType: 'skeleton' | 'none' 과 같이 로딩 상태의 타입을 명시적으로 제어하도록 설계하여 해결했습니다.

둘째, 스켈레톤 UI가 표시된 후 이미지가 점진적으로 로드(Progressive Loading)되며 화면이 깨져 보이는 현상이 발생했습니다. 사용자는 스켈레톤 UI가 사라진 후 완성된 이미지를 한 번에 보길 기대합니다. 이를 해결하기 위해 이미지 <img> 태그의 onLoad 이벤트를 활용했습니다. 이미지 로딩이 백그라운드에서 완전히 끝날 때까지 opacity를 0으로 유지하고, 로딩이 완료되는 순간 onLoad 이벤트가 opacity를 1로 변경하도록 구현하여 부드럽고 완성도 높은 시각적 전환을 만들어냈습니다.

()

마치며

이러한 전략적 접근을 통해 Vercel 이미지 최적화 API 호출을 80% 이상 절감하여 관련 비용 문제를 근본적으로 해결했습니다. 또한, 모든 이미지에 고정된 크기와 Lazy Loading을 기본 적용함으로써 CLS(누적 레이아웃 이동)를 원천적으로 방지하여 사이트의 시각적 안정성과 사용자 경험을 크게 향상시켰습니다.