WordPressなどのCMSではよくみられる「月別アーカイブ」。
Jamstackなサイトではどのように実装するのが良いでしょうか?
当記事ではNext.jsでの実装を例に、月別アーカイブを実装する手順を紹介します。
月別アーカイブを実装する
前提として、「microCMS + Next.jsでJamstackブログを作ってみよう」のチュートリアルで作成されるブログを題材にし、ここに「月別アーカイブ」機能を追加しく形で進めていきます。
アウトプットはこのようなイメージです。
先に実装の流れを確認しておきましょう。
以下のようなステップになると思います。
- ブログ記事をすべて取得する
- 月ごとに記事をグルーピングする
- 「2022年2月(13)」のような表示を作る
なお、今回題材とするブログはSSGで作っています。そのため、月別アーカイブはビルド時に構築する想定です。
それでは実装していきましょう。
1. ブログ記事をすべて取得する
月別アーカイブを作成するタイミングはNext.jsの getStaticProps
が良いでしょう。
まず、ブログ記事をすべて取得するには以下のように記述します。
// pages/index.js
export const getStaticProps = async () => {
// "blog" のコンテンツを全件取得
const data = await client.get({
endpoint: "blog",
queries: { fields: "publishedAt", limit: 3000 },
});
return {
props: {
contents: data.contents,
},
};
};
limitを指定しない場合、コンテンツは10件のみ取得されます。ここでは全件取得したいので大きめの値を指定しています。
また、ここでは記事の公開日付の情報だけがあれば良いので、fields
クエリを使って取得するフィールドを絞り、通信量を抑えています。(詳細)
2. 月ごとに記事をグルーピングする
microCMSで配信するコンテンツには publishedAt
という、コンテンツを公開した日付の情報が付与されます。
日付はISO 8601形式のUTCで、たとえば以下のような形式の値です。
'2022-04-06T05:03:27.510Z'
今回は「月」でグルーピングしたいので、日にちや時間の情報はカットして以下の形式に変換したいと思います。
'2022_04'
この日付の変換を実装していきます。
まず、日付を扱うのに便利な dayjs
というライブラリを準備します。
$ npm i -S dayjs
次に、日付をフォーマット変換する実装を追加します。
// libs/util.js
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
// UTC -> "2022_04" のフォーマットに変換
export const formatDate = (date) => {
const formattedDate = dayjs.utc(date).tz("Asia/Tokyo").format("YYYY_MM");
return formattedDate;
};
この関数はmicroCMSで取得したコンテンツと組み合わせて以下のように利用します。
console.log(data.contents[0].publishedAt) // -> '2022-04-06T05:03:27.510Z'
console.log(formatDate(data.contents[0].publishedAt)) // -> '2022_04'
記事が公開された月を表現できるようになりました。この値を使って記事をグルーピングしましょう。
先ほど定義した formatDate
を使い、以下のように実装できます。
// libs/util.js
export const groupBy = function (contents) {
return contents.reduce(function (group, x) {
const yearMonthString = formatDate(new Date(x["publishedAt"]));
(group[yearMonthString] = group[yearMonthString] || []).push(x);
return group;
}, {});
};
microCMSで取得したコンテンツを渡し、グルーピングしてみましょう。
// APIで取得
const data = await client.get({ endpoint: "blog", queries: { fields: "publishedAt", limit: 3000 } });
// 月別にグルーピング
const monthlyIndex = groupBy(data.contents);
console.log(monthlyIndex);
この出力は以下のようになります。(わかりやすくするため id や title を追記して表現しています)
keyに「2022_04」のような月を表現する文字列を、
valueにその月に書かれた記事の内容を持つ連想配列になっています。
これで月ごとにブログ記事をまとめられました。
「2022年2月(13)」のような表示を作る
ここまでの実装から、getStaticProps
は次のようになっています。
// pages/index.js
export const getStaticProps = async () => {
const data = await client.get({ endpoint: "blog", queries: { limit: 3000 } });
const monthlyIndex = groupBy(data.contents);
return {
props: {
blog: data.contents,
monthlyIndex: monthlyIndex,
},
};
};
これらの情報を使い、表示部分は以下のように実装します。
// pages/index.js
export default function Home({ blog, monthlyIndex }) {
return (
<div>
<div>
<h3>月別アーカイブ</h3>
<ul>
{Object.keys(monthlyIndex).map((index) => (
<li>
<Link href={`archive/${index}`}>
{index.split("_")[0] + "年" + index.split("_")[1] + "月"}
</Link>
({monthlyIndex[index].length})
</li>
))}
</ul>
</div>
</div>
);
}
うまく動作していれば以下のように表示されているはずです。
これで月別アーカイブの実装ができました。
最後に、このアーカイブのリンクをクリックしたときの遷移先ページを実装しましょう。
月ごとの記事一覧ページを作る
/archive/2022_04
のようなパスを追加し、対象の月に公開された記事の一覧を表示するよう実装してみます。
↓こういうイメージです
実装は以下のようになります。
// pages/archive/[date].js
import { client } from "../../libs/client";
import Link from "next/link";
import { groupBy } from "../../libs/util";
export default function BlogId({ title, blog }) {
return (
<div>
<h2>{title}</h2>
<ul>
{blog.map((blog) => (
<li key={blog.id}>
<Link href={`/blog/${blog.id}`}>
<a>{blog.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}
// 1. パスを生成
export const getStaticPaths = async () => {
const data = await client.get({ endpoint: "blog" });
const monthlyIndex = groupBy(data.contents, "publishedAt");
const paths = Object.keys(monthlyIndex).map((index) => `/archive/${index}`);
return { paths, fallback: false };
};
// 2. 該当月のブログ記事を取得
export const getStaticProps = async (context) => {
const date = context.params.date;
const year = date.split("_")[0];
const month = date.split("_")[1];
// microCMSのfiltersクエリは >= を表現できないので開始時刻は1ミリ秒引いておく
const startOfMonthTmp = new Date(year, month - 1, 1);
const startOfMonth = new Date(startOfMonthTmp.getTime() - 1);
const endOfMonth = new Date(year, month, 0);
// filtersクエリで該当月の記事のみを取得
const filters = `publishedAt[greater_than]${startOfMonth.toISOString()}[and]publishedAt[less_than]${endOfMonth.toISOString()}`;
const data = await client.get({
endpoint: "blog",
queries: {
filters: filters,
},
});
return {
props: {
title: `${year}年${month}月の記事一覧`,
blog: data.contents,
},
};
};
少しだけ補足します。
1. getStaticPaths(パスを生成)
先ほどと同様で、記事を全件取得→グルーピングして、記事が存在する月のリストを作成しています。/archive/2022_03
や /archive/2022_04
のような月の数だけパスが作られるイメージです。
2. getStaticProps(該当月のブログ記事を取得)
該当月の記事を取得するために、microCMSの filters
クエリを使って日付範囲を指定しています。
「publishedAt が4/1から4/30のもの」を表現するには greater_than と less_than を使います。
(>=
が表現できないため、月の開始日から1ミリ秒引いて工夫をしています)
これらを計算してfiltersパラメータを構築し、クエリパラメータに指定してコンテンツを取得します。
ページにアクセスするとこのようになっているはずです。
これで月ごとの記事一覧も完成です!
最後に
この記事ではJamstackなサイトで月別アーカイブを作成する手順を紹介させていただきました。
コンテンツの多いサイトでは役に立つ機能かと思います。ご参考になれば幸いです。
-----
microCMSは日々改善を進めています。
ご意見・ご要望は管理画面右下のチャット、公式Twitter、お問い合わせからお気軽にご連絡ください!
引き続きmicroCMSをよろしくお願いいたします!