페이지네이션 vs 무한 스크롤: 콘텐츠 탐색 UX 선택 가이드

UX 디자인

페이지네이션무한 스크롤콘텐츠 탐색리스트 UX성능 최적화

이 글은 누구를 위한 것인가

  • 상품 목록에 페이지네이션과 무한 스크롤 중 무엇을 쓸지 고민하는 팀
  • 무한 스크롤 구현 후 "뒤로가기 시 위치 복원" 문제를 겪는 개발자
  • 리스트 페이지 UX 패턴을 정리하고 싶은 디자이너

들어가며

무한 스크롤은 소셜 피드에 최적화됐지만, 이커머스 상품 목록에서는 페이지네이션이 더 나을 때가 많다. 사용자가 특정 상품을 찾은 뒤 뒤로가기로 돌아올 때, 무한 스크롤은 처음부터 다시 보여주기 때문이다.

이 글은 bluefoxdev.kr의 리스트 UX 가이드 를 참고하여 작성했습니다.


1. 패턴 선택 가이드

[콘텐츠 유형별 패턴 선택]

페이지네이션 (전통적):
  ✅ 이커머스 상품 목록 (특정 페이지로 돌아올 필요)
  ✅ 검색 결과 (몇 페이지에 있는지 파악 필요)
  ✅ 데이터 테이블 (관리자, B2B)
  ✅ 특정 항목을 찾아야 하는 콘텐츠
  단점: 클릭 수 많음, 이탈 포인트

무한 스크롤:
  ✅ 소셜 피드 (시간순 콘텐츠)
  ✅ 뉴스 피드 (끝이 없어도 OK)
  ✅ 탐색형 콘텐츠 (딱히 찾는 게 없음)
  단점: 뒤로가기 위치 복원 어려움, 푸터 접근 불가

Load More 버튼:
  ✅ 댓글, 리뷰 목록
  ✅ 무한 스크롤의 단점을 줄이고 싶을 때
  ✅ 모바일 데이터 사용량 절약
  
[이커머스 권장]
  모바일: Load More 또는 페이지네이션 (무한 스크롤 하단 도달 어려움)
  데스크탑: 페이지네이션 또는 하이브리드
  
  하이브리드: 처음 20개 로드 → "더 보기" 버튼 → 이후 무한 스크롤

2. 페이지네이션 + 스크롤 위치 복원

// 뒤로가기 시 스크롤 위치 복원
function useScrollRestoration(key: string) {
  useEffect(() => {
    // 페이지 이탈 시 스크롤 위치 저장
    const handleBeforeUnload = () => {
      sessionStorage.setItem(`scroll_${key}`, window.scrollY.toString());
    };

    window.addEventListener("beforeunload", handleBeforeUnload);
    
    // 페이지 진입 시 복원
    const savedPosition = sessionStorage.getItem(`scroll_${key}`);
    if (savedPosition) {
      setTimeout(() => {
        window.scrollTo(0, parseInt(savedPosition));
        sessionStorage.removeItem(`scroll_${key}`);
      }, 100);
    }

    return () => window.removeEventListener("beforeunload", handleBeforeUnload);
  }, [key]);
}

// Intersection Observer 기반 무한 스크롤
function useInfiniteScroll(callback: () => void, threshold = 0.5) {
  const observerRef = useRef<IntersectionObserver | null>(null);
  const triggerRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting) {
          callback();
        }
      },
      { threshold }
    );

    if (triggerRef.current) {
      observerRef.current.observe(triggerRef.current);
    }

    return () => observerRef.current?.disconnect();
  }, [callback, threshold]);

  return triggerRef;
}

// 페이지네이션 컴포넌트
function Pagination({
  currentPage,
  totalPages,
  onPageChange,
}: PaginationProps) {
  const pages = generatePageNumbers(currentPage, totalPages);

  return (
    <nav aria-label="페이지 네비게이션">
      <ul className="flex items-center gap-1">
        <li>
          <button
            onClick={() => onPageChange(currentPage - 1)}
            disabled={currentPage === 1}
            className="px-3 py-2 rounded-lg disabled:opacity-40 hover:bg-gray-100"
            aria-label="이전 페이지"
          >
            ‹
          </button>
        </li>
        
        {pages.map((page, i) =>
          page === "..." ? (
            <li key={`ellipsis-${i}`}>
              <span className="px-3 py-2 text-gray-400">···</span>
            </li>
          ) : (
            <li key={page}>
              <button
                onClick={() => onPageChange(page as number)}
                className={`w-9 h-9 rounded-lg font-medium ${
                  currentPage === page
                    ? "bg-blue-600 text-white"
                    : "hover:bg-gray-100 text-gray-700"
                }`}
                aria-current={currentPage === page ? "page" : undefined}
              >
                {page}
              </button>
            </li>
          )
        )}
        
        <li>
          <button
            onClick={() => onPageChange(currentPage + 1)}
            disabled={currentPage === totalPages}
            className="px-3 py-2 rounded-lg disabled:opacity-40 hover:bg-gray-100"
            aria-label="다음 페이지"
          >
            ›
          </button>
        </li>
      </ul>
    </nav>
  );
}

마무리

이커머스에서 무한 스크롤의 가장 큰 문제는 "뒤로가기 시 처음으로 돌아가는 것"이다. 상품을 50개 스크롤해서 찾은 뒤 상세 페이지를 봤다가 돌아오면 다시 처음부터 스크롤해야 한다. 이 문제를 해결하기 전에는 이커머스에서 페이지네이션이나 Load More 버튼이 더 나은 선택이다.