Qlitre's Blog

2022.04.29 Nuxt.js /microCMS

Nuxt3とmicroCMSで作るブログ ④記事一覧のページング

Nuxt3とmicroCMSで作るブログシリーズの4番目の記事です。
今回は記事一覧ページにページング処理を施していきます。

ページングの行い方

基本的にmicroCMSさんの公式ブログで紹介しているやり方を参考にしています。
NuxtのJamstack構成におけるページングの実装

microCMSの一覧GET APIにはlimitoffsetプロパティがあります。
limitはリクエスト時の取得件数の設定を、offsetは何件目から取得するか、という設定を行えます。
例えば記事が10個あって、一回の取得件数、つまりlimitを3に設定しているとします。

このとき、クライアントが2ページ目にアクセスしてきたときはlimitを3,offsetを3にしてGETリクエストを送ると4~6件目の記事が取得できます。
なので、offsetの計算としては(ページ数-1) * 3となります。
何ページ目まで表示させるかは記事の件数をlimitで割ればできそうです。

routeの追加

まずはユーザーが何ページ目にアクセスしているか、ということを示すルートを作成します。
/page/2だったら2ページ目という感じです。
これはnuxt.config.tsに追記をします。
この辺は理屈が良くわかってないですが、githubのissueを参考にしました。
https://github.com/nuxt/framework/issues/2041

import { defineNuxtConfig } from 'nuxt'
import { resolve } from 'path' //追加
import { createCommonJS } from 'mlly' //追加
const { __dirname } = createCommonJS(import.meta.url) //追加

const { API_KEY, SERVICE_DOMAIN } = process.env;

// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
    srcDir: 'client/',
    privateRuntimeConfig: {
        apiKey: API_KEY,
        serviceDomain: SERVICE_DOMAIN
    },
    publicRuntimeConfig: {
        apiKey: process.env.NODE_ENV !== 'production' ? API_KEY : undefined,
        serviceDomain: process.env.NODE_ENV !== 'production' ? SERVICE_DOMAIN : undefined,
    },
    link: [
        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ],
    css: ['@/assets/css/reset.css', '@/assets/css/style.css'],
    // 追加
    hooks: {
        'pages:extend'(pages) {
            pages.push({
                name: 'page',
                path: '/page/:p',
                file: resolve(__dirname, 'client/pages/index.vue')
            })
        }
    },
})


index.vueの編集

次にindex.vueを編集していきます。

<script setup lang="ts">
const route = useRoute()
const page: number = Number(route.params.p || 1)

const limit: number = 5
const offset: number = (page - 1) * limit
const params: object = { limit: limit, offset: offset }

const { data: posts } = await useFetch('/api/postList', { params: params })

// ページ数の計算
const totalCount: number = posts.value.totalCount
const numPages: number = Math.ceil(posts.value.totalCount / limit)

</script>


urlからページ数を取得して、offsetとページ数を計算しています。

記事一覧のAPIの編集

次に受け取ったパラメーターを元に返却する記事を変更するようにしていきます。

// client/server/api/postList.ts
import type { IncomingMessage, ServerResponse } from 'http'
import client from './client'
import { Post } from './types'
import * as url from "url";

export default async (req: IncomingMessage, res: ServerResponse) => {
    const params = url.parse(req.url as string, true).query;

    const queries = {
        fields: 'id,title,publishedAt,tag',
        limit: Number(params.limit),
        offset: Number(params.offset)
    }

    const data = client.getList<Post>({
        endpoint: 'post',
        queries: queries
    })

    return data
}


ここまででnpm run devしてURLに応じてページが切り替わることを確認してみましょう。

http://localhost:3000/page/1


http://localhost:3000/page/2



このように返却されるページが変わりました。

ページネーションコンポーネント

Pagination.vueをコンポートとして作ります。

<!-- client/components/Pagination.vue -->
<script setup lang="ts">

const props = defineProps({
    numPages: Number,
    current: Number,
})

// ページリンクを返す
function getPath(p: number) {
    return `/page/${p}`
}

</script>

<template>
    <div class="pagination">
        <NuxtLink v-for="num in numPages" :key="num" :class="[num == current ? 'current' : 'link']" :to="getPath(num)">
            {{ num }}
        </NuxtLink>
    </div>
</template>

<style scoped>
.pagination {
    position: relative;
    width: 100%;
    margin: 8em 0 8rem;
    font-family: 'Open Sans', sans-serif;
    font-weight: 300;
    line-height: 1.1;
    text-align: center;
    vertical-align: middle;
}

.current,
.link {
    display: inline-block;
    margin: 0 2rem;
    padding: 2px 0;
    text-align: center;
    font-size: 3rem;
    font-weight: lighter;
}

.current {
    color: #000;
}

.link {
    color: #A2A2A6;
}
</style>


ここの部分で現在表示中のページの場合はcurrentクラスを付与するようにしています。

<NuxtLink v-for="num in numPages" :key="num" :class="[num == current ? 'current' : 'link']" :to="getPath(num)">


親から現在ページ数と、総ページ数を渡して呼び出しましょう。

<!-- client/pages/index.vue -->
<template>
    <div>
        <div class="divider">
            <section class="container">
                <!-- 記事一覧 -->
                <PostList :posts="posts" />
            </section>
            <aside class="aside">
                <!-- キーワード検索、タグ一覧 -->
            </aside>
        </div>
        <!-- 追加 -->
        <Pagination :numPages="numPages" :current="page">
    </div>
</template>





次回はキーワード検索を実装していきます。

TOPページへ