Qlitre's Blog

2022.07.18 Next.js /microCMS

Next.js, microCMS, Chakra UIで作るブログ ④ページネーションの設置

Next.js, microCMS, Chakra-UIで作るブログシリーズの4つ目の記事です。
今回は一覧ページでのページ送り機能を実装させていきます。

公式のブログを参考に、queryパラメータのlimitoffsetを使って実装していきます。
Next.js(SSG)でページネーションを実装してみよう

共通セッティングファイルの作成

まず、ブログの一ページの記事数を定義するファイルを作ります。
なくてもいけるんですが、同じ数字を何回も使うので、一か所で定義しておいた方がいいでしょう。

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
        },
    };
};


現在のページ数が濃く表示されるようになりました。



次回はタグでの絞り込みと絞り込んだ場合のページネーションを実装していきます。