Next.js, microCMS, Chakra-UIで作るブログシリーズの4つ目の記事です。
今回は一覧ページでのページ送り機能を実装させていきます。
公式のブログを参考に、queryパラメータのlimit
とoffset
を使って実装していきます。
まず、ブログの一ページの記事数を定義するファイルを作ります。
なくてもいけるんですが、同じ数字を何回も使うので、一か所で定義しておいた方がいいでしょう。
srcディレクトリにsettings→siteSettings.tsを作成します。
/* src/settings/siteSettings.ts */
export const BLOG_PER_PAGE = 2
テストをしたいのでとりあえず2としました。
次にPagination.tsx
を作成し以下のようにします。
/* src/components/Pagination.tsx */
import {
Box,
HStack,
Link,
} from "@chakra-ui/react";
import { BLOG_PER_PAGE } from 'settings/siteSettings'
type Props = {
totalCount: number;
};
export const Pagination = ({ totalCount }: Props) => {
const range = (start: number, end: number) =>
[...Array(end - start + 1)].map((_, i) => start + i)
const pageCount = Math.ceil(totalCount / BLOG_PER_PAGE)
return (
<HStack spacing='10' justifyContent="center">
{range(1, pageCount).map((number, index) => (
<Box key={index}>
<Link href={`/page/${number}`} fontSize="3xl">{number}</Link>
</Box>
))}
</HStack >
);
};
次にindex.tsx
から呼び出します。
/* src/pages/index.tsx */
// ... 省略
// 追加
import { BLOG_PER_PAGE } from 'settings/siteSettings';
import { Pagination } from 'components/Pagination';
export const getStaticProps = async () => {
// 変更 limitに設定値を指定する
const data = await client.getList({ endpoint: "post", queries: { limit: BLOG_PER_PAGE } });
return {
props: {
posts: data.contents,
// 追加
totalCount: data.totalCount
},
};
};
type Props = {
posts: Post[];
// 追加
totalCount: number
};
// 変更
const Home: NextPage<Props> = ({ posts, totalCount }) => {
return (
<>
<Header />
<Container as="main" maxW="container.lg" marginTop="4" marginBottom="16">
<Heading as="h2" fontSize="2xl" fontWeight="bold" mb="8">
Home
</Heading>
<PostList posts={posts} />
{/* 追加 */}
<Pagination totalCount={totalCount}></Pagination>
</Container>
</>
)
}
export default Home
とりあえずページネーションリンクは表示されました。
しかし、アクセス先のページをつくっていないので、クリックしても今のところ何も表示されません。
/page/2
という風にリンクを指定したので、pagesディレクトリ配下にpageディレクトリ、その中に[id].tsxを作成します。
/* src/pages/page/[id].tsx */
import { Pagination } from 'components/Pagination';
import { client } from 'libs/client';
import { GetStaticPaths, GetStaticProps, } from "next";
import type { Post } from "types/blog";
import { Header } from 'components/Header'
import { PostList } from 'components/PostList';
import {
Box,
Container,
Heading,
} from "@chakra-ui/react";
import { BLOG_PER_PAGE } from 'settings/siteSettings';
type Props = {
posts: Post[]
totalCount: number
};
export default function BlogPageId({ posts, totalCount }: Props) {
return (
<Box>
<Header />
<Container as="main" maxW="container.lg" marginTop="4" marginBottom="16">
<Heading as="h1" marginBottom="8" fontSize="2xl">
Home
</Heading>
<PostList posts={posts} />
<Pagination totalCount={totalCount} />
</Container>
</Box>
);
}
// 静的ファイルの生成
export const getStaticPaths: GetStaticPaths = async () => {
const repos = await client.get({ endpoint: "post" });
const range = (start: number, end: number) => [...Array(end - start + 1)].map((_, i) => start + i);
const paths = range(1, Math.ceil(repos.totalCount / BLOG_PER_PAGE)).map((repo) => `/page/${repo}`);
return { paths, fallback: false };
};
// アクセスされたpageナンバーに応じたデータの取得
export const getStaticProps: GetStaticProps<Props, { id: string }> = async ({ params }) => {
if (!params) throw new Error("Error Page Number Not Found");
const pageId = Number(params.id);
const data = await client.getList({
endpoint: "post",
queries: {
offset: (Number(pageId) - 1) * BLOG_PER_PAGE,
limit: BLOG_PER_PAGE
}
});
return {
props: {
posts: data.contents,
totalCount: data.totalCount,
},
};
};
ポイントはoffsetの指定です。
const data = await client.getList({
endpoint: "post",
queries: {
offset: (Number(pageId) - 1) * BLOG_PER_PAGE,
limit: BLOG_PER_PAGE
}
});
今回はテスト的にBLOG_PER_PAGEに2を指定していました。
ユーザーが/page/2
にアクセスしたときは3件目の記事から、/page/3
にアクセスしたときは5件目の記事から表示する必要があります。
offsetクエリは次の記事から取得を指定するパラメータです。
なので、以下の部分で/page/2
だったら2、/page/3
だったら4を設定しています。
offset: (Number(pageId) - 1) * BLOG_PER_PAGE
ページ1
ページ2
これでページネーションができましたが、今のところ一見してユーザーがどのページにいるのかわかりづらいです。
ページリンクの見た目を少し工夫します。
まず、Pagination.tsx
を少し変更します。
/* src/components/Pagination.tsx */
import {
Box,
HStack,
Link,
// 追加
Text
} from "@chakra-ui/react";
import { BLOG_PER_PAGE } from 'settings/siteSettings'
type Props = {
totalCount: number;
// 追加 オプションで現在ページを受け取れるように
currentPage?: number;
};
// 変更 currentPageが渡されていない場合はデフォルトで1を指定
export const Pagination = ({ totalCount, currentPage = 1 }: Props) => {
const range = (start: number, end: number) =>
[...Array(end - start + 1)].map((_, i) => start + i)
const pageCount = Math.ceil(totalCount / BLOG_PER_PAGE)
// 追加 現在ページか否かで返す要素を分ける
const getPaginationItem = (p: number) => {
if (p === currentPage) return <Text color="gray.700" fontSize="3xl">{p}</Text>
return <Link href={`/page/${p}`} color="gray.400" fontSize="3xl">{p}</Link>
}
return (
<HStack spacing='10' justifyContent="center">
{range(1, pageCount).map((number, index) => (
<Box key={index}>
{getPaginationItem(number)}
</Box>
))}
</HStack >
);
};
次に[id].tsxからcurrentPageを渡すようにします。
/* src/pages/page/[id].tsx */
//...省略
type Props = {
posts: Post[]
totalCount: number
// 追加
currentPage: number
};
// 変更 currentPageを受け取る
export default function BlogPageId({ posts, totalCount, currentPage }: Props) {
return (
<Box>
<Header />
<Container as="main" maxW="container.lg" marginTop="4" marginBottom="16">
...省略
{/* 変更 */}
<Pagination totalCount={totalCount} currentPage={currentPage} />
</Container>
</Box>
);
}
// アクセスされたpageナンバーに応じたデータの取得
export const getStaticProps: GetStaticProps<Props, { id: string }> = async ({ params }) => {
if (!params) throw new Error("Error Page Number Not Found");
const pageId = Number(params.id);
// ...省略
return {
props: {
posts: data.contents,
totalCount: data.totalCount,
// 追加
currentPage: pageId
},
};
};
現在のページ数が濃く表示されるようになりました。
次回はタグでの絞り込みと絞り込んだ場合のページネーションを実装していきます。