React · 2026-02-22

React로 이미지 lazy loading 훅 만들기

IntersectionObserver를 활용해 React에서 재사용 가능한 이미지 lazy loading 훅을 만들고, 성능 개선과 접근성 요소를 포함한 구현 방식과 사용법 설명

작성일 : 2026-02-22 ㆍ 작성자 : 관리자
post
목차

왜 이미지 lazy loading이 필요한가

페이지에 많은 이미지가 있을 때 모두 한 번에 로드하면 초기 렌더링이 느려진다. 네트워크 비용도 증가하며 사용자 경험이 떨어진다. 따라서 필요한 시점에 이미지를 로드하는 lazy loading이 필요하다. 이 글에서는 React에서 간결하고 재사용 가능한 훅을 만들어 리액트 이미지 로딩 최적화 방법을 설명한다.

접근 방식 개요

브라우저의 IntersectionObserver API를 사용한다. 이 API는 요소가 뷰포트에 들어왔는지 여부를 비동기적으로 알려 준다. 이벤트 기반으로 동작하므로 스크롤 이벤트를 직접 다루는 것보다 성능이 좋다. 목표는 'react lazy image hook' 형태의 훅을 만들어 여러 컴포넌트에서 공유해 쓰는 것이다.

핵심 요구사항

  • 뷰포트에 진입할 때만 src를 설정
  • 지원되지 않는 브라우저를 위한 폴백 제공
  • 접근성(alt 처리)과 폴백 이미지 처리
  • 간단한 API: ref와 로딩 상태 반환

커스텀 훅 설계

훅은 다음을 반환한다: 엘리먼트 ref, 로딩 상태, 에러 상태. 사용자는 img 요소에 ref를 할당하고 조건부로 실제 src를 넣는다. 이렇게 하면 초기 렌더링 시 불필요한 네트워크 요청을 막는다.

구현 코드

아래 구현은 IntersectionObserver를 사용하며, 지원하지 않을 경우 즉시 로드를 수행한다.

import { useEffect, useRef, useState } from 'react';

export function useLazyImage({ root = null, rootMargin = '0px', threshold = 0.1 } = {}) {
  const ref = useRef(null);
  const [isVisible, setIsVisible] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const node = ref.current;
    if (!node) return;

    if ('IntersectionObserver' in window) {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
          if (entry.isIntersecting) {
            setIsVisible(true);
            observer.unobserve(node);
          }
        });
      }, { root, rootMargin, threshold });

      observer.observe(node);
      return () => observer.disconnect();
    } else {
      // 폴백: 즉시 보이도록 설정
      setIsVisible(true);
    }
  }, [root, rootMargin, threshold]);

  return { ref, isVisible, error, setError };
}

사용 예시

이제 훅을 사용하는 방법이다. 핵심은 실제 src를 isVisible 값에 따라 할당하는 것이다.

import React from 'react';
import { useLazyImage } from './useLazyImage';

export default function LazyImage({ src, alt = '', placeholder }) {
  const { ref, isVisible, setError } = useLazyImage();

  return (
    <img
      ref={ref}
      src={isVisible ? src : placeholder}
      alt={alt}
      onError={() => setError(true)}
      loading="lazy"
      style={{ width: '100%', height: 'auto' }}
    />
  );
}

추가 고려사항

다음 항목을 점검하면 안정성이 상승한다.

  • placeholder로 저용량 이미지 사용해 레이아웃 안정화
  • srcset과 sizes 지원으로 반응형 이미지 로드
  • 이미지 실패 시 대체 UI 제공
  • SEO와 접근성: 이미지 설명(alt)과 의미 있는 파일 이름
  • 서버 사이드 렌더링 환경에서는 클라이언트 전용 로직 분리

성능 검증 방법

성능 개선을 확인하려면 Lighthouse와 네트워크 패널을 사용한다. 최초 콘텐츠 렌더링 시간(LCP)과 네트워크 요청 수가 줄어드는지 확인한다. 또한 모바일 환경에서의 데이터 사용량 변화를 측정하면 실질 이득을 파악할 수 있다.

마무리

간단한 훅 하나로 이미지 로딩을 제어하면 렌더링 속도가 개선된다. 위 구현은 'image lazy loading 구현'과 '리액트 이미지 로딩 최적화' 목표를 달성하기 위한 출발점이다. 필요에 따라 지연 임계값, rootMargin, placeholder 전략을 조정해 실무에 맞게 확장하면 된다.

react lazy image hook image lazy loading 구현 리액트 이미지 로딩 최적화 IntersectionObserver lazy loading React 훅 이미지 성능 최적화 프론트엔드 성능