Next.js와 MongoDB로 손쉽게 구현하는 Pagination
KUKJIN LEE • 7개월 전 작성
※ 이 게시글은 Category가 FrontEnd로 설정되어 있지만, 실제로는 BackEnd 관련 내용도 포함하고 있습니다. FrontEnd 지식만으로는 이해하기 어려울 수 있으므로, 간단한 BackEnd 코드에 대한 이해가 필요합니다.
데이터가 많아질수록 모든 데이터를 한 번에 불러오는 것은 비효율적입니다. Pagination(페이징 처리)을 사용하면 사용자 경험을 개선하고 서버 부하를 줄일 수 있습니다. Next.js와 MongoDB를 사용하여 효율적인 Pagination을 구현하는 방법입니다.
Pagination 구현을 위한 코드
아래는 Next.js 애플리케이션에서 Pagination을 구현하는 예제입니다. MongoDB에서 데이터를 불러오고, 페이징 처리를 통해 효율적으로 데이터를 관리합니다.
1. 데이터베이스 연결 및 모델 설정 (lib/action)
먼저 데이터베이스와 연결하고, Tech
와 User
모델을 불러옵니다. 그 후, 페이징 처리를 위한 함수를 정의합니다.
- 데이터베이스 연결:
connectToDatabase
함수를 통해 MongoDB에 연결합니다. - 모델 불러오기:
Tech
와User
모델을 불러와서 사용합니다. - 페이징 처리 함수:
getFrontEnd
함수는 검색 조건, 페이지 번호, 페이지당 항목 수, 정렬 기준 등을 받아서 해당 조건에 맞는 데이터를 반환합니다. 또한, 다음 페이지가 있는지 여부도 반환합니다.
import { connectToDatabase } from "@/lib/database";
import Tech from "@/models/Tech";
import User from "@/models/User";
export async function getFrontEnd(params) {
try {
await connectToDatabase();
const { searchQuery, page = 1, limit = 10, sort = "-createdAt" } = params;
const query = { category: "frontend" };
if (searchQuery) {
query.$or = [
{ title: { $regex: searchQuery, $options: "i" } },
{ contents: { $regex: searchQuery, $options: "i" } },
];
}
const totalItems = await Tech.countDocuments(query);
const frontend = await Tech.find(query)
.populate({ path: "author", model: User })
.sort(sort)
.skip((page - 1) * limit)
.limit(limit);
const isNext = page * limit < totalItems;
return { data: frontend, pageNumber: page, isNext };
} catch (error) {
console.error(error);
throw new Error("Failed to fetch frontend data");
}
}
2. 페이지 컴포넌트 (Page.tsx)
이제 Next.js 페이지 컴포넌트에서 데이터를 불러오고, Pagination 컴포넌트를 사용하여 페이징 처리를 구현합니다.
- 상태 관리:
useState
훅을 사용하여 데이터를 저장할 상태와 현재 페이지 번호, 다음 페이지가 있는지를 관리합니다. - 라우터 사용:
useRouter
와useSearchParams
훅을 사용하여 현재 URL의 쿼리 파라미터를 가져오고, 페이지 변경 시 URL을 업데이트합니다. - 데이터 불러오기:
fetchData
함수를 통해 현재 페이지 번호와 검색 조건에 맞는 데이터를 불러옵니다. - 렌더링:
FrontEnd
컴포넌트에 데이터를 전달하여 렌더링하고,Pagination
컴포넌트를 사용하여 페이징 버튼을 표시합니다.
"use client";
import { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import FrontEnd from "./FrontEnd";
import Pagination from "./Pagination";
import { getFrontEnd } from "@/lib/actions/tech.action";
export default function Page() {
const [result, setResult] = useState({ data: [], pageNumber: 1, isNext: false });
const router = useRouter();
const searchParams = useSearchParams();
const fetchData = async (page = 1) => {
const params = {
page,
limit: 10,
sort: "-createdAt",
searchQuery: searchParams.get("search") || ""
};
const res = await getFrontEnd(params);
setResult(res);
};
useEffect(() => {
const page = parseInt(searchParams.get("page")) || 1;
fetchData(page);
}, [searchParams]);
return (
<>
<FrontEnd result={result.data} />
<Pagination pageNumber={result.pageNumber} isNext={result.isNext} />
</>
);
}
3. Pagination 컴포넌트
Pagination 컴포넌트를 공용 컴포넌트로 작성하여 재사용성을 높입니다.
- 페이지 변경 핸들러:
handlePageChange
함수는 새로운 페이지 번호를 받아 URL을 업데이트합니다. - 버튼 렌더링: 현재 페이지 번호와 다음 페이지가 있는지를 기준으로 이전 및 다음 버튼을 활성화 또는 비활성화합니다. 현재 페이지 번호를 표시하고, 이전 및 다음 버튼을 클릭할 때 페이지를 변경합니다.
import { useRouter } from "next/navigation";
const Pagination = ({ pageNumber, isNext }) => {
const router = useRouter();
const handlePageChange = (newPage) => {
router.push(`?page=${newPage}`);
};
return (
<div className="pagination">
<button
onClick={() => handlePageChange(pageNumber - 1)}
disabled={pageNumber === 1}
>
Previous
</button>
<span>{pageNumber}</span>
<button
onClick={() => handlePageChange(pageNumber + 1)}
disabled={!isNext}
>
Next
</button>
</div>
);
};
export default Pagination;