Next/Image는 자동 WebP 변환, 사이즈별 최적화, Lazy Loading 등 다양한 성능 최적화 기능을 내장한 강력한 솔루션입니다. 저는 이 기능을 '만능 해결책'으로 간주하고, 프로젝트 내 프로필 이미지, 아이콘, 썸네일 등 모든 이미지에 Next/Image
컴포넌트를 일괄적으로 적용했습니다.
그 결과, 프로덕트 배포 후 단 10분 만에 Vercel의 이미지 최적화 무료 사용량(월 5,000회)을 모두 소진하는 문제가 발생했습니다. 이는 즉각적인 추가 비용 발생으로 이어졌고, 향후 트래픽 증가 시 이미지 최적화 비용이 예측 불가능하게 증가할 수 있다는 것을 의미했습니다.
비용 문제를 해결하고 확장성을 확보하기 위해, 모든 이미지에 동일한 솔루션을 적용하는 대신 이미지의 중요도와 목적에 따라 최적화 전략을 분리하기로 결정했습니다.
첫째, Next/Image
는 SEO에 직접적인 영향을 주거나 여러 디바이스에서 최적의 해상도를 보여줘야 하는 핵심 이미지(예: 레시피 썸네일)에만 선별적으로 사용했습니다. 이때도 무분별한 최적화를 막기 위해 next.config.js
의 deviceSizes
옵션을 2~3개의 특정 사이즈로 제한하여, 생성되는 최적화 이미지의 가짓수를 통제했습니다.
둘째, 그 외의 모든 이미지(UI 아이콘, 정적 에셋 등)를 처리하기 위한 재사용 가능한 커스텀 Image
컴포넌트를 직접 구현했습니다. 이 컴포넌트는 IntersectionObserver
를 이용한 Lazy Loading, 로딩 중 레이아웃 쉬프트(CLS) 방지를 위한 고정 사이즈, 그리고 스켈레톤 UI를 기본으로 탑재하여 성능과 사용자 경험을 모두 고려했습니다.
()
커스텀 컴포넌트를 구현하며 두 가지 주요 기술적 문제에 직면했습니다.
첫째, 이미지별로 다른 로딩 UI를 제공해야 하는 요구사항이 있었습니다. 예를 들어, 메인 썸네일은 스켈레톤 UI가 필요하지만, 작은 아이콘은 로딩 UI 없이 즉시 나타나야 했습니다. 이는 컴포넌트 props
로 loadingType: 'skeleton' | 'none'
과 같이 로딩 상태의 타입을 명시적으로 제어하도록 설계하여 해결했습니다.
둘째, 스켈레톤 UI가 표시된 후 이미지가 점진적으로 로드(Progressive Loading)되며 화면이 깨져 보이는 현상이 발생했습니다. 사용자는 스켈레톤 UI가 사라진 후 완성된 이미지를 한 번에 보길 기대합니다. 이를 해결하기 위해 이미지 <img>
태그의 onLoad
이벤트를 활용했습니다. 이미지 로딩이 백그라운드에서 완전히 끝날 때까지 opacity
를 0으로 유지하고, 로딩이 완료되는 순간 onLoad
이벤트가 opacity
를 1로 변경하도록 구현하여 부드럽고 완성도 높은 시각적 전환을 만들어냈습니다.
()
이러한 전략적 접근을 통해 Vercel 이미지 최적화 API 호출을 80% 이상 절감하여 관련 비용 문제를 근본적으로 해결했습니다. 또한, 모든 이미지에 고정된 크기와 Lazy Loading을 기본 적용함으로써 CLS(누적 레이아웃 이동)를 원천적으로 방지하여 사이트의 시각적 안정성과 사용자 경험을 크게 향상시켰습니다.