Lazy Loading(지연 로딩)은 웹페이지의 초기 로드 시 모든 이미지를 한 번에 로드하지 않고, 사용자의 브라우저 뷰포트$_{viewport}$에 이미지가 들어올 때 해당 이미지를 로드하는 기술이다. 필요한 시점에 필요한 리소스를 로드한다
는 개념을 기반으로 수행한다.
모든 웹사이트에서 이미지는 필수적인 존재이기 때문에, Lazy Loading은 웹 성능 최적화 측면에서 중요한 요소가 될 수 있다. 웹 페이지 초기 로드 시 모든 이미지를 한꺼번에 다운로드 하면, 페이지 로딩 시간이 길어지고 불필요한 네트워크 대역폭을 사용한다. 특히 모바일 환경에서는 데이터 사용량과 직결되는 문제이기 때문에 더욱 조심해야 한다. 서버 측면에서도 동시 다운로드 요청이 감소해서 부하를 줄일 수 있다는 장점이 있다.
Lazy Loading을 구현하면 초기 페이지 로딩 속도가 개선되어 사용자가 콘텐츠를 더 빨리 볼 수 있다. 또한 스크롤할 때마다 필요한 이미지만 순차적으로 로드되므로, 자연스러운 사용자 경험을 제공할 수 있다. 여기에 스켈레톤 UI를 함께 구현하면 이미지 로딩 중에도 레이아웃이 깨지지 않고 안정적인 화면을 유지할 수 있다.
특히 이미지가 많은 갤러리, 그리드 레이아웃, 긴 스크롤이 필요한 페이지, 이미지 중심의 콘텐츠에서 Lazy Loading 효과가 더욱 두드러진다. 우리의 기술 블로그 모음 서비스는 다수의 블로그 포스트 썸네일을 그리드 형태로 보여주어야 하기 때문에, Lazy Loading 도입이 필수적이라고 판단하였다.
LazyImage 컴포넌트는 Intersection Observer API를 활용해 이미지의 지연 로딩을 구현한다. Intersection Observer는 브라우저의 뷰포트와 설정한 요소의 교차점을 관찰해 요소가 화면에 보이는지 여부를 확인할 수 있게 해준다. 먼저 컴포넌트의 기본 구조와 상태 관리를 살펴보자.
export const LazyImage = ({ src, alt, className, wrapperClassName }: LazyImageProps) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(false);
const imgRef = useRef<HTMLDivElement>(null);
// ...
}
여기서 isLoaded
는 이미지의 로드 완료 여부를, isInView
는 이미지가 화면에 보이는지 여부를 관리한다. imgRef
는 Intersection Observer가 관찰할 DOM 요소를 참조하기 위해 사용된다.
다음으로 Intersection Observer를 설정하고 관찰을 시작하는 부분이다.
// ...
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{ rootMargin: "50px" }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
// ...