アイキャッチの画像

nextjs & tailwindでページネーション(Pagination)を実装する方法

公開日:

更新日:

デモ 完成イメージ

本サイトで使用しているPagination機能を紹介します。↓

主な機能をまとめると下記になります。

・記事(Article)を取得して記事数に応じて動的にページ数を組み立てる。

・ページ遷移の矢印ボタンは次の記事(前の記事)の有無によってDisableされる。

・記事データはJson形式で取得する。(データベースは使用しません)

react-paginateというライブラリを使用しています。

また本サイトではShinさんのチュートリアル動画を参考にページネーションを実装しました。

とても分かりやすいので皆さんも参考にご覧下さい。

ReactとTypescriptでページネーションを実装してみよう【react-paginateを利用】

ディレクトリ構成&環境情報

フォルダ構成は下記の形になります。↓

フォルダ構成図

1/
2└─ src
3   ├─ app
4   │  └─ components
5   │     ├─ article.json
6   │     ├─ Pagination.tsx
7   │     ├─ pagination.css
8   │     ├─ ArticleComponent.tsx
9   │     └─ NewArticleList.tsx
10   └─ lib
11      └─ types.ts

イメージとしては。 NewArticleList.tsxで全てのコンポーネントを使用する形です。

NewArticleList.tsxは新着記事一覧を表示するページです。

開発環境は下記です。↓

・言語: typescript

・ライブラリ: React

・フレームワーク: Nextjs (バージョン:15.0.3)

・CSSフレームワーク: tailwind

コード実例

article.json

1
2{
3    "articleList": [
4        {
5            "genre":"記事のジャンル名",
6            "id":2,
7            "url": "記事のURL",
8            "date": "2025/05/14",
9            "description": "記事の説明文",
10            "image":"サムネイル画像のURL"
11        },
12        {
13            "genre":"記事のジャンル名",
14            "id":1,
15            "url": "記事のURL",
16            "date": "2025/05/13",
17            "description": "記事の説明文",
18            "image":"サムネイル画像のURL"
19        },
20    ]
21}        
22
23

article.jsonでは記事の情報を持ちます。

・新しいものが上に来るように昇順で記事を格納しています。

・ここにあるデータの総数でページの数も決まります。

Pagination.tsx

1"use client";
2import Article from "@/lib/types";
3import React from "react";
4import ArticleComponent from "./ArticleComponent";
5import { useState } from "react";
6import ReactPaginate from "react-paginate";
7import "./pagination.css";
8import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
9import ArrowBackIcon from "@mui/icons-material/ArrowBack";
10type Props = {
11  articles: Article[];
12};
13function Pagination(props: Props) {
14  const { articles } = props;
15  const itemsPerPage = 6;
16  const [itemsOffset, setItemsOffset] = useState(0);
17  const endOffset = itemsOffset + itemsPerPage;
18  const currentItems = articles.slice(itemsOffset, endOffset);
19  const pageCount = Math.ceil(articles.length / itemsPerPage);
20  const handlePageClick = (event: { selected: number }) => {
21    const newOffset = (event.selected * itemsPerPage) % articles.length;
22    setItemsOffset(newOffset);
23  };
24  return (
25    <div className="">
26      <div className="grid sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3 mb-3  ">
27        {currentItems.map(
28          (article: {
29            id: number;
30            url: string;
31            date: string;
32            description: string;
33            image: string;
34            genre: string;
35          }) => (
36            <ArticleComponent
37              url={article.url}
38              date={article.date}
39              description={article.description}
40              genre={article.genre}
41              image={article.image}
42              key={article.id}
43            />
44          )
45        )}
46      </div>
47      <div className="w-full flex justify-center pt-10">
48        <ReactPaginate
49          pageCount={pageCount}
50          onPageChange={handlePageClick}
51          marginPagesDisplayed={4} // 先頭と末尾に表示するページ数
52          pageRangeDisplayed={2} // 現在のページの前後をいくつ表示させるか
53          containerClassName="pagination justify-center" // ul(pagination本体)
54          pageClassName="page-item" // li
55          pageLinkClassName="page-link rounded-full" // a
56          activeClassName="active" // active.li
57          activeLinkClassName="active" // active.li < a
58          // 戻る・進む関連
59          previousClassName="page-item" // li
60          nextClassName="page-item" // li
61          previousLabel={<ArrowBackIcon style={{ fontSize: 25, width: 50 }} />}
62          previousLinkClassName="previous-link"
63          nextLabel={<ArrowForwardIcon sx={{ fontSize: 25, width: 50 }} />}
64          nextLinkClassName="next-link"
65          // 先頭 or 末尾に行ったときにそれ以上戻れ(進め)なくする
66          disabledClassName="disabled"
67          // 中間ページの省略表記関連
68          breakLabel="..."
69          breakClassName="page-item"
70          breakLinkClassName="page-link"
71        />
72      </div>
73    </div>
74  );
75}
76
77export default Pagination;
78

Pagination.tsxが今回のメインです。

・こちらに関してはShinさんのチュートリアル動画で詳しく解説されています。

・見た目を調整するためpagination.cssで定義したクラス名が何箇所か出てきています。

・レスポンシブ対応済でスマホでは記事が1列、PCだと2列、モニターなど大きな画面では3列に記事が並びます。

pagination.css

1.pagination {
2    display: flex;
3    align-items: center;
4    justify-content: center;
5    margin-bottom: 10px;
6    gap: 20px 6px;
7}
8
9.active{
10background-color:#798777 ;
11}
12
13.disabled{
14    color: #a9a9a9;
15}
16.page-item,
17.page-link {
18    display: inline-flex;
19    align-items: center;
20    border-radius: 30px;
21    justify-content: center;
22    font-weight: 700;
23    font-size: 16px;
24    height: 40px;
25    width: 40px;
26}

Pagination.tsxの見た目を調整するCSSファイルです。

Pagination.tsxと同階層に設置しています。

ArticleComponent.tsx

1import Link from "next/link";
2import React from "react";
3import Image from "next/image";
4
5export type ClassName = {
6  className: string;
7};
8
9type ArticleProps = {
10  url: string;
11  date: string;
12  description: string;
13  image: string;
14  genre: string;
15};
16
17const ArticleComponent = ({ url, description, date, image }: ArticleProps) => {
18  return (
19    <>
20      <Link
21        href={url}
22        className=" hover:opacity-75 hover:scale-105 duration-300  hover:shadow-lg hover:shadow-gray-400 "
23      >
24        <article className="shadow   bg-white rounded  h-96">
25          <div className="items-center  flex justify-center pt-3">
26            <Image
27              src={image}
28              height={180}
29              width={342}
30              alt="アイキャッチの画像"
31            />
32          </div>
33          <div className=" flex items-center  ">
34            <div className="p-3 flex flex-col ">
35              <p className="text-sm mb-1  text-slate-400 ">投稿日 {date}</p>
36              <p className="md:text-sm lg:text-lg font-semibold pb-2">
37                {description}
38              </p>
39            </div>
40          </div>
41        </article>
42      </Link>
43    </>
44  );
45};
46
47export default ArticleComponent;
48

article.jsonの記事をカード化して見た目を調整しているクラスです。

・hoverしたらスケールアップして大きくなったりとアニメーションを定義しています。

NewArticleList.tsx

1import { promises as fs } from "fs";
2import Pagination from "../components/Pagination";
3
4export default async function Page() {
5  const file = await fs.readFile(
6    process.cwd() + "/src/app/components/article.json",
7    "utf8"
8  );
9  const data = JSON.parse(file);
10
11  return (
12    <>
13      <div className="mb-3  w-full">
14        <h1 className="text-lg font-bold">新着記事</h1>
15      </div>
16      <div className=" w-full">
17        <div>
18          <div className="flex flex-row justify-between">
19            <Pagination articles={data.articleList} />
20          </div>
21        </div>
22      </div>
23    </>
24  );
25}
26

・ここではまず fs.readFileの部分でarticle.jsonより全ての記事を受け取っています。

・記事を受けとったらJSON.parseの部分でJsonをパースしてPaginationコンポーネントに読み込んだ記事を渡して完成です。

まとめ

上記で紹介しているコンポーネントでページネーション機能を実現できます。

Profile Image
コピペでもそのまま使用できると思うので参考にしてみて下さい。

本記事は以上になります。

ご一読頂きありがとうございました。

目次