Next.js + contentlayerで作ったブログにページネーションを実装する
今回はlodashのchunk関数を使ってページネーションを実装しました。いろんな場面で使いまわせると思うので、作り方を記事にまとめておきます。
前提
以下を使う前提です。
- Next.js (App Router使用)
- contentlayer
- TailwindCSS
- lodash
呼び出し元ページ(page.tsx)
URLに与えられたパラメータから表示したいページ数を取得して、それに応じて表示する記事が切り替わるようになっています。
import { Post, allPosts } from "contentlayer/generated";
import _ from "lodash";
import Link from "next/link";
import { Pager } from "@/components/pager";
const getCorrectPage = ({
page,
max
}: {
page: string | number | undefined;
max: number;
}) => {
let _page = Number(page);
if (page === undefined || !_.isInteger(_page) || _page > max || _page <= 0) {
return 1;
} else {
return _page;
}
};
const PER_PAGE = 10;
interface PageProps {
searchParams: { page: number };
}
export default function Home({ searchParams }: PageProps) {
const sorted = _.orderBy(allPosts, "date", "desc");
const chunked = _.chunk(sorted, PER_PAGE);
let { page } = searchParams;
page = getCorrectPage(page);
const posts = chunked[page - 1];
return (
<div>
<div>
{posts.map((post: Post) => {
<Link key={post.slug} href={post.slug}>{post.title}</Link>;
})}
</div>
{chunked.length > 1 ? (
<Pager path="/posts" max={chunked.length} page={page} />
) : (
""
)}
</div>
);
}
解説
lodashのchunk関数は一定の数ごとに配列を分ける関数です。
const array = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(_.chunk(array, 3));
例えばこの場合、コンソールには[[1, 2, 3], [4, 5, 6], [7, 8]]
と3つずつに区切られた配列が出力されます。
上記のページコンポーネントでは記事が全件入った配列を日付(date
)が新しい順にソートした後、この要領で10個(PER_PAGE
)ずつの配列に分けて、page
から1引いた数をインデックスに渡す事で、渡されたパラメータによって表示する記事が切り替わるようになっています。
getCorrectPage
関数は、pageパラメータがなかった時や数字以外の文字列が渡された時に、初期値の1
を返す関数です。
そして<Pager />
コンポーネントには、max
とpage
とpath
というpropsを渡しています。
max
... 最大ページ数(chunked.length
)page
... パラメータに渡されたページ数path
... 呼び出し元のパス
では、次にページャーコンポーネントを作ります。
ページャーコンポーネント
import { cn } from "@/lib/utils";
import { ChevronRight, ChevronLeft } from "lucide-react";
import Link from "next/link";
export const Pager = ({
href,
page,
max,
}: {
href: string;
page: number;
max: number;
}) => {
const arr: number[] = [...Array(max)].map((_, i) => i + 1);
return (
<div className="flex w-[100%] justify-center justify-between pt-8 sm:justify-center">
{page > 1 ? (
<button>
<Link
href={`${href}${
page > 2
? `${href.includes("?") ? "&" : "?"}page=${page - 1}`
: ""
}`}
className={cn(
"hover flex h-[35px] w-[35px] select-none items-center justify-center rounded-full bg-card text-sm font-black shadow-sm",
)}
>
<ChevronLeft size={17} strokeWidth={1.5} />
</Link>
</button>
) : (
<button
className={cn(
"flex h-[35px] w-[35px] cursor-default select-none items-center justify-center rounded-full bg-[rgba(var(--muted-foreground),0.05)] text-sm",
)}
>
<ChevronLeft
size={17}
strokeWidth={1.5}
className="text-[rgba(var(--muted-foreground),0.6)]"
/>
</button>
)}
<ul className="mx-3 flex items-center gap-2">
{arr.map((e: any) => {
return (
<li key={e}>
<Link
href={`${href}${
e > 1 ? `${href.includes("?") ? "&" : "?"}page=${e}` : ""
}`}
className={cn(
"hover flex h-[35px] w-[35px] select-none items-center justify-center rounded-full text-sm shadow-sm",
e === page
? "bg-gradient font-bold text-card"
: "bg-card text-muted-foreground",
)}
>
{e}
</Link>
</li>
);
})}
</ul>
{page < max ? (
<button>
<Link
href={`${href}${href.includes("?") ? "&" : "?"}page=${page + 1}`}
className={cn(
"hover flex h-[35px] w-[35px] select-none items-center justify-center rounded-full bg-card text-sm font-black shadow-sm",
)}
>
<ChevronRight size={17} strokeWidth={1.5} />
</Link>
</button>
) : (
<button
className={cn(
"flex h-[35px] w-[35px] cursor-default select-none items-center justify-center rounded-full bg-[rgba(var(--muted-foreground),0.05)] text-sm",
)}
>
<ChevronRight
size={17}
strokeWidth={1.5}
className="text-[rgba(var(--muted-foreground),0.6)]"
/>
</button>
)}
</div>
);
};
解説
配列arr
の中には1
からmax
までの数字が入っています。(例えばmaxが3の場合は、[1,2,3]
という感じ)
これをループさせて、${path}?page=${n}
へのリンクを生成しています。
また、${path}?page=1
になる場合は、初期ページである${path}
へのリンクになるような分岐も入れています。
cn
関数は、TailwindCSSで当てるスタイルを動的に切り替える為の関数で、現在表示されているページのリンクをハイライトする為に使用しています。詳しくはこちらの記事を参考にしてください。
終わりに
このブログの場合、ページネーションを設置しているページは以下です。
- 記事一覧ページ (/posts)
- タグごとの記事一覧ページ (/tags/タグ名)
- 検索結果ページ (/s?keyword=検索ワード)
path
に渡す値をそれぞれカッコ内の値に変えるだけで、全てのページで同じ要領でページネーションを設置する事ができます。ページネーションは複数のページで必要になると思うので、こういう風に使い回せる形にしておくと便利だと思います。