[기능] Next.js로 페이지네이션 구현하기

짜장킴·2025년 7월 15일

프로젝트

목록 보기
2/38

컴포넌트 개요

  • API를 통해 전체 데이터 개수를 받아오고, 페이지 수를 계산하여 현재 페이지 기반의 페이지네이션 버튼 렌더링
  • 최대 5개의 페이지 버튼이 한 번에 보이도록 구성하기

1. 필요한 Props 타입 정의

  • pageSize : 한 페이지에 보여줄 항목 개수
  • currentPage : 현재 선택된 페이지 번호
  • onPageChange : 페이지 번호 변경 시 실행할 콜백 함수
type PaginationProps = {
  pageSize: number;
  currentPage: number;
  onPageChange: (page: number) => void;
};

2. 전체 페이지 수 계산

  • 컴포넌트 마운트 시 또는 pageSize가 바뀔 때마다 전체 데이터 개수 가져와서 totalPage 계산하기
useEffect(() => {
  const fetchData = async () => {
    const { count } = await getSubjects();
    setTotalPage(Math.ceil(count / pageSize));
  };
  fetchData();
}, [pageSize]);

3. 현재 페이지 블록 계산

  • 한 번에 보여줄 최대 페이지 수 : 5개
  • 현재 페이지가 속한 블록의 시작/끝 번호를 계산해서 visiblePages 배열 구성
const maxVisible = 5;
const currentBlock = Math.floor((currentPage - 1) / maxVisible);
const startPage = currentBlock * maxVisible + 1;
const endPage = Math.min(startPage + maxVisible - 1, totalPage);

왜 currentBlock을 사용하는가?

  • 예를 들어, 페이지가 100개라면 사용자에게는 5개씩 묶인 페이지 버튼이 보이게 해야함
    - [1,2,3,4,5] -> currentBlock : 0
    - [6,7,8,9,10] -> currentBlock : 1
    - [11,12,13,14,15] -> currentBlock : 2
  • 따라서 현재 페이지가 어느 블록에 속하는지를 계산해야함
  • 이 값을 기준으로 startPage, endPage를 산출하여 보여줄 페이지 버튼만 추림

4. 페이지 버튼 리스트 생성

  • 위에서 계산된 startPage ~ endPage 사이의 번호를 visiblePages 배열에 저장
const visiblePages = [];
for (let i = startPage; i <= endPage; i++) {
  visiblePages.push(i);
}

5. 이전/다음 페이지 이동 함수

const gotoPrev = () => {
  if (currentPage > 1) {
    onPageChange(currentPage - 1);
  }
};
const gotoNext = () => {
  if (currentPage < totalPage) {
    onPageChange(currentPage + 1);
  }
};

6. 전체 코드

type PaginationProps = {
  pageSize: number;
  currentPage: number;
  onPageChage: (page: number) => void;
};

function Pagination({ pageSize, currentPage, onPageChage }: PaginationProps) {
  const [totalPage, setTotalPage] = useState(0);

  useEffect(() => {
    const fetchData = async () => {
      const { count } = await getSubjects();
      setTotalPage(Math.ceil(count / pageSize));
    };
    fetchData();
  }, [pageSize]);

  const maxVisible = 5;
  const currentBlock = Math.floor((currentPage - 1) / maxVisible);
  const startPage = currentBlock * maxVisible + 1;
  const endPage = Math.min(startPage + maxVisible - 1, totalPage);
  const visiblePages = [];

  for (let i = startPage; i <= endPage; i++) {
    visiblePages.push(i);
  }

  const gotoPrev = () => {
    if (currentPage > 1) {
      onPageChage(currentPage - 1);
    }
  };

  const gotoNext = () => {
    if (currentPage < totalPage) {
      onPageChage(currentPage + 1);
    }
  };

  return (
    <div className={styles.pagination}>
      <button
        className={styles["arrow-button"]}
        onClick={gotoPrev}
        disabled={currentPage === 1}
      >
        <Image src={arrowLeft} alt="이전 페이지" />
      </button>
      <div className={styles["pagination-button-wrapper"]}>
        {visiblePages.map((number) => (
          <button
            key={number}
            className={`${styles["pagination-button"]} ${
              currentPage === number ? styles.active : ""
            }`}
            onClick={() => onPageChage(number)}
          >
            {number}
          </button>
        ))}
      </div>
      <button
        className={styles["arrow-button"]}
        onClick={gotoNext}
        disabled={currentPage === totalPage}
      >
        <Image src={arrowRight} alt="다음 페이지" />
      </button>
    </div>
  );
}

7. 최적화(React.memo 사용)

  • props가 바뀌지 않으면 리렌더링 방지
export default React.memo(Pagination);
profile
프론트엔드

0개의 댓글