📝
Gridsomeで月別アーカイブ機能を作成する。
もともと自分のブログに投稿する予定だったのですが、ZennにGridsomeの投稿が1つもなくて絶望したのでZennに書くことにしました。
(Gridsomeについて限りなく雑に説明すると、静的サイトジェネレーターGatsbyのVue版です。自分のブログもGridsomeで作っています。)
ちなみに私はVueもReactもほとんど触ったことがないフロントエンド初心者です。
実現したいこと
Gridsomeで月別アーカイブ機能を作成します。
ブログによくあるこういうやつです。
アーカイブのURLは
のように/archives/date/{Year}/{Month}
で閲覧できるようにします。
動作確認環境
$ gridsome info
System:
OS: Linux 4.19 Ubuntu 20.04.1 LTS (Focal Fossa)
Binaries:
Node: 12.18.3 - /usr/local/bin/node
Yarn: 1.22.5 - /usr/local/bin/yarn
npm: 6.14.8 - /usr/local/bin/npm
npmPackages:
gridsome: ^0.7.21 => 0.7.21
npmGlobalPackages:
@gridsome/cli: 0.3.4
GraphQLのクエリにallBlogPost
を使用する前提で話を進めていますが、allWordpressPost
等を使う場合は適宜読み替えてください。
やること
- 月別アーカイブを
/archives/date/{Year}/{Month}
で閲覧するために、Pages APIを使って個別ページを作る - サイドバーから月別アーカイブの一覧を表示するために、componentを作成する
月別アーカイブ用の個別ページ作成
Pages APIのcreatePages
を使用すると、特定のパスで使用するデータとテンプレートを自分で編集できます。
今回は以下の流れで個別ページを作りました。
- GraphQLの
allBlogPost
で全投稿から投稿年と投稿月だけ取得し、createPages
で対応する年月のページを作成 - template内でGraphQLの
filter
を使用し、前の工程で取得した年と月に該当する投稿を一覧表示する
実装にあたり、Gatsby.jsで年ごと、月ごとで記事一覧を表示したい - Qiitaを大変参考にいたしました。ありがとうございます。
gridsome.server.js
// gridsome.server.js
module.exports = api => {
api.createPages(async ({ graphql, createPage }) => {
// GraphQLで全投稿を検索
// 投稿年と投稿月だけ取得する
const { data } = await graphql(`{
allBlogPost {
edges {
node {
year: date(format: "YYYY")
month: date(format: "YYYY,MM")
}
}
}
}`)
const years = new Set();
const yearMonths = new Set();
// 全投稿から取得した投稿年と投稿月の重複を削除
data.allBlogPost.edges.forEach(({ node }) => {
years.add(node.year);
yearMonths.add(node.month);
});
// 年ページの作成
years.forEach(year => {
createPage({
path: `/archives/date/${year}`,
component: "./src/templates/Years.vue",
context: {
displayYear: year,
// template内で投稿年の1/1から12/31までの記事一覧を取得するために、年末の日時を呼び出せるようにする
periodStartDate: `${year}-01-01T00:00:00.000Z`,
periodEndDate: `${year}-12-31T23:59:59.999Z`
}
});
});
// 月ページの作成
yearMonths.forEach(yearMonthStr => {
const yearMonth = yearMonthStr.split(",");
// 指定した月の末日を取得
const date = new Date(yearMonth[0], yearMonth[1], 0);
const year = date.getFullYear();
const month = ("00" + (date.getMonth() + 1)).slice(-2);
const day = ("00" + date.getDate()).slice(-2);
createPage({
path: `/archives/date/${yearMonth[0]}/${yearMonth[1]}`,
component: "./src/templates/Years.vue",
context: {
displayYear: `${yearMonth[0]}/${yearMonth[1]}`,
// template内で投稿月の1日から月末までの記事一覧を取得するために、月末の日時を呼び出せるようにする
periodStartDate: `${year}-${month}-01T00:00:00.000Z`,
periodEndDate: `${year}-${month}-${day}T23:59:59.999Z`
}
});
});
})
}
template
(api.createPages
で設定した)periodStartDate
とperiodEndDate
の範囲内にある記事を一覧で出力します。
<!-- ./src/templates/Years.vue -->
<template>
<Layout>
<div>
<h1 class="entry-title" itemprop="headline">
{{ $context.displayYear }}
</h1>
<ul>
<li v-for="{ node } in $page.years.edges" :key="node.id">
<g-link :to="node.path">
<span v-html="node.title" />
</g-link>
{{ node.date }}
</li>
</ul>
</div>
</Layout>
</template>
<!-- periodStartDateとperiodEndDateの範囲内にある記事を検索する -->
<page-query>
query PostsByDate($periodStartDate: Date, $periodEndDate: Date) {
years: allBlogPost(filter: {date: {between: [$periodStartDate, $periodEndDate]} }) {
edges {
node {
id
title
path
date(format: "YYYY/MM/DD")
}
}
}
}
</page-query>
これで、/archives/date/{Year}/{Month}
にアクセスすると、該当する記事の一覧が表示できるようになりました。
月別アーカイブ一覧用のcomponentを作成
かなりインチキくさい実装ですが、以下の流れでコンポーネントを作りました。
-
allBlogPost
で全投稿から投稿年と投稿月だけ取得する - 投稿年を表示するために、
v-for
内でSetを使用し、重複した投稿年を削除してループを回す - 投稿月を表示するために、投稿年に対応する月を
filter
(GraphQLのfilterではなくArray.prototype.filter()
のほう)で絞り込んで表示する
<template>
<div>
<div
v-for="(year, yindex) in new Set(
$static.years.edges.map((e) => e.node.year)
)"
:key="`y-${yindex}`"
>
<h6>
» {{ year }}
</h6>
<div>
<g-link
v-for="(month, mindex) in new Set(
$static.years.edges
.map((e) => e.node.month)
.filter((e) => e.indexOf(year) === 0)
.reverse()
)"
:key="`m-${mindex}`"
:to="`/archives/date/${month}`"
>{{ month.slice(-2) }}</g-link
>
<g-link
:to="`/archives/date/${year}`"
>all</g-link
>
</div>
</div>
</div>
</template>
<static-query>
query {
years: allBlogPost(sortBy: "published_at", order: ASC) {
edges {
node {
year: date(format: "YYYY")
month: date(format: "YYYY/MM")
}
}
}
}
</static-query>
これでサイドバーに月別アーカイブ一覧が表示できるようになりました。
もともと自分のブログで受けた問い合わせの回答として書いた記事なのですが、誤りがあれば指摘いただけると幸いです。
Discussion