이 글은 누구를 위한 것인가
- 상품 목록에 페이지네이션과 무한 스크롤 중 무엇을 쓸지 고민하는 팀
- 무한 스크롤 구현 후 "뒤로가기 시 위치 복원" 문제를 겪는 개발자
- 리스트 페이지 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 버튼이 더 나은 선택이다.