LoginSignup
3
2

More than 1 year has passed since last update.

【SSG】Django & Next.jsでページネーションを分かりやすく

Last updated at Posted at 2022-06-30

初めに

SSG(Static Site Generation)を使ってページネーションを超簡単に実装しますっっ! 最低限の説明にするために、関係のない実装(CSS,データベース、等)はしていません。

使う技術

    ・Django-rest-frameworkのPaginator

    ページネーションのあるAPIを作ります。

    ・Next.jsの静的ジェネレーター(getStaticPathsとgetStaticProps)

    Django側に対して、ページ番号を付けたURLへアクセスしてデータを取得します。

Django側のコード

私は、プロジェクト名prjの中にappとapiというアプリケーションを作成しました。 prj(プロジェクト名) app(アプリケーション名) api(アプリケーション名)

プロジェクトの設定

settings.py
INSTALLED_APPASにrest_framework,app,apiを追加するだけでOK
データベースも標準のSQLiteを使っちゃってますがPostgreSQLとかでも同じことです
各々の設定はお任せで
prj/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),

    path('api/', include('api.urls')),# ←追加
]

appアプリケーション

models.py
from django.db import models

class Datas(models.Model):
    title = models.CharField(max_length=100)
    content = models.CharField(max_length=1000)

    def __str__(self):
        return self.title
admin.py
from django.contrib import admin
from app.models import Datas

admin.site.register(Datas)

apiアプリケーション

urls.py
from django.urls import path
from . import views

from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'data', views.DataViewSet, basename='data')

urlpatterns = router.urls

urlpatterns = [
    path('data/', views.DataViewSet.as_view({'get': 'list'})),
    path('data/pagination/', views.DataPaginationView.as_view({'get': 'list'}))
]
views.py
from rest_framework import viewsets
from app.models import Datas
from .serializers import DataSerializer
from .pagination import CustomPagiNumberPagination

class DataViewSet(viewsets.ModelViewSet):
    model = Datas
    serializer_class = DataSerializer
    queryset = Datas.objects.all().order_by('id')

class DataPaginationView(viewsets.ModelViewSet):
    model = Datas
    pagination_class = CustomPagiNumberPagination
    serializer_class = DataSerializer
    queryset = Datas.objects.all().order_by('id')
serializers.py(新規作成)
from app.models import Datas
from rest_framework import serializers

class DataSerializer(serializers.ModelSerializer):
    class Meta:
        model = Datas
        fields = '__all__'

シリアライザーはこれでOK。

pagination.py(新規作成)
from rest_framework import pagination

class CustomPagiNumberPagination(pagination.PageNumberPagination):
  page_size = 5# 1ページのデータ数を指定します。
  page_size_query_param = 'count'
  max_page_size = 5
  page_query_param = 'p'# api/data/pagination/?p=2 などとできます。

Django動作確認

http://127.0.0.1:8000/admin/
にアクセスし、Datasのデータを10個くらい追加しましょう。
今は、pagination.pyでデータ数を5に設定しています。

http://127.0.0.1:8000/api/data/pagination/?p=1
にアクセス↓
スクリーンショット (697).png
こんな感じで表示されれば成功です。

Next.js側のコード

Create Next App

任意の名前でアプリケーションを作成します。
コマンド
npx create-next-app@latest

元からあるpagesフォルダ内にdatas/page/[id].jsを作りました。
また、pagesと同じ階層に、lib/datas/datas.jsを作りました。

npm(もしくはyarn)でインストールするもの

以下の、dependencies内のモジュールを集めてきてください!
バージョンも合わせるとエラーを回避できます。

ちなみに、Paginationのスタイルと機能は、Material-uiにお任せしています。

package.json
{
  "name": "next-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@emotion/react": "^11.9.0",
    "@emotion/styled": "^11.8.1",
    "@mui/material": "^5.8.6",
    "next": "12.2.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "eslint": "8.18.0",
    "eslint-config-next": "12.2.0"
  }
}

データ取得をする2つの関数を定義します

lib/datas/datas.js
export async function getAllDatasId() {
  const res = await fetch(new URL(`http://127.0.0.1:8000/api/data/pagination/`));
  const data = await res.json();
  const totalPages = Math.ceil(Number(data.count)/Number(10))//6
  let paramId = []
  for (let i = 0; i < totalPages; i++) {
    const parameter = {
      params: {
        id:String(i+1)
      },
    };
    paramId.push(parameter);
  }
  return paramId
}
export async function getData(page) {
  const res = await fetch(new URL(`http://127.0.0.1:8000/api/data/pagination/?p=${page}`));
  const datas = await res.json();
  return datas;
}

getAllDatasId()はgetStaticPaths(ページネーションの動的なページ番号を取得します)のための関数です。django-rest-frameworkで用意したAPIのcountから求めています。
getData()はgetStaticPropsで使います。getStaticPropsから求めたページ番号を基に、データを取得します。
ぜひ、コンソールに出したりして確かめてみて下さい!

datas/page/[id].js
import { useEffect, useState } from 'react';
import { Pagination } from '@mui/material';
import { useRouter } from 'next/router';
import { getAllDatasId, getData } from '../../../lib/datas/datas';

export default function All(props) {
  const { datas } = props;
  const [page, setPage] = useState(1),//ページ番号
        [count, setCount] = useState();//総ページ数
  const router = useRouter();

  const clickPage = (e, page) => {
    setPage(page);
    router.push(`${page}`)
  }
  useEffect(() => {
    setCount(Math.ceil(Number(datas && datas.count && datas.count)/Number(10)))
  },[count,datas])

  return (
      <div>
        <div style={{marginTop: "50px", textAlign: "center"}}>
          <Pagination
            count={count}//総ページ数
            color="primary"
            variant="outlined"
            onChange={clickPage}//第2引数にページ番号が入る
            page={page}
          />
          {
            datas.results.map(data => {
              return (
                <div>
                  <hr/>
                  <p>{data.title}</p>
                  <p>{data.content}</p>
                  <hr/>
                </div>
              )
            })
          }
        </div>
      </div>
  )
}

export async function getStaticPaths() {
  const paths = await getAllDatasId();
  return {
    paths,
    fallback:true// ないページ番号にアクセスがあった際、falseの場合は404を返します。
                  // trueなら、同様に静的なページが表示されますが、propsが空になります。
  };
}
export async function getStaticProps({ params }) {
  const datas = await getData(params.id)
  return {
    props: {
      datas,
    },
    revalidate:100// 本番環境の時に100秒毎にデータを読み込みます。
  }
}

getStaticPathsでページ番号を取得し、getStaticPropsでページ毎のデータを取得できました。
Next.jsでは、SPAとしてURLを変更する際、useRouter()オブジェクトのrouter.push(相対パス)を使います。
clickPageという自作の関数は、Paginationのボタンがクリックされた時に、pageのステートを更新し、router.push()でURLに反映させるという処理をします。
こんな感じでuseStateで管理しているpageを[id].jsのidの部分と連動させています。

useEffect内では、Paginationに使う、ページ番号の総数を求めています。
3ページあるなら3が入る事で、Paginationが1,2,3と表示されます。

簡単な説明↓

・getStaticPathsで得られるもの
データの総数÷1ページのデータ数でページ数を求めて下のgetStaticPropsのためのページ番号をすべて準備しています。

[ 
    {
    params: {
        id: '1'
        } 
    }, 
    {
        params: {
            id: '2' 
        }
    } 
]

・getStaticPropsでえられるもの
上のgetStaticPathsから引数の{params}でページ番号を取得して、ページ毎にデータを取得しています。

{
  count: 11,
  next: 'http://127.0.0.1:8000/api/data/pagination/?p=2',
  previous: null,
  results: [
    {
      id: 1,
      title: 'タイトルです',
      content: '内容です。',
      savedat: '2022-06-30T18:25:53.505316+09:00'
    },
    {
      id: 2,
      title: 'タイトル2です',
      content: '内容2です。',
      savedat: '2022-06-30T18:26:04.498775+09:00'
    },
    {
      id: 3,
      title: 'タイトル3です',
      content: '内容3です。',
      savedat: '2022-06-30T18:26:15.109920+09:00'
    },
    {
      id: 4,
      title: 'タイトル4です',
      content: '内容4です。',
      savedat: '2022-06-30T18:26:28.785373+09:00'
    },
    {
      id: 5,
      title: 'タイトル5です',
      content: '内容5です。',
      savedat: '2022-06-30T18:26:43.141520+09:00'
    }
  ]
}

動作確認

スクリーンショット (699).png

おわり

DjangoバックエンドのNext.jsの静的サイトジェネレーションを使った時のページネーションの作り方をまとめました。 DjangoでページネーションのAPIを作ってNext.jsで受け取ればいいという事でした。

Django側での実装↓
  1. django-rest-frameworkのpaginatorを使ってpaginationのあるAPIを作る
  2. urls.pyで外部からアクセスできるようにする

Next.js側での実装↓

  1. getStaticPaths
  2. djangoのAPI上のcount(総データ数)からpage数を計算する。
    count/1pageのデータ数=page数

  3. getStaticPropsでデータを取得
  4. getStaticPathsからpage数を受け取ってDjangoのAPIにアクセスしてデータを取得できます。

以上です。
見て頂きありがとうございました!
質問や指摘等あれば気軽によろしくお願いします!

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2