Gatsby.jsのブログにページネーションとタグを導入する
このサイトには入れてないんだけど、Gatsbyを使った別のサイトでページネーションとタグの機能を追加したので、せっかくなのでメモしておく
hakozaru.com
=> 次ページへ =>hakozaru.com/page/2
みたいになる- タグの一覧でも
hakozaru.com/tags/hoge
=>hakozaru.com/tags/hoge/2
みたいになる
のが目的
前提としてGatsby公式のgatsby-starter-blogを使っているものとする
ページネーション機能の追加
タグ機能の追加でページネーション機能を使いたいので、先にページネーション機能を追加していく
ページネーションに関してはパッケージに頼ることにした(別に自前実装でもよい)
このページを参考に導入していく
-
yarnやnpmで
gatsby-awesome-pagination
をpackage.jsonへ追加する -
gatsby-node.js
でimport { paginate } from 'gatsby-awesome-pagination';
する -
再び
gatsby-node.js
をいじっていくが、おそらくGatsby.jsでブログを作成した時点で、すでに以下のようなクエリが書かれていてallMarkdownRemark( sort: { fields: [frontmatter___date], order: ASC } limit: 1000 ) { nodes { id fields { slug } } }
const posts = result.data.allMarkdownRemark.nodes
という感じで記事の一覧が取得できるようになっていると思うので、このposts
を使ってページネーションすればよいpaginate({ createPage, items: posts, itemsPerPage: 10, pathPrefix: ({ pageNumber }) => (pageNumber === 0 ? "/" : "/page"), component: path.resolve('src/templates/index.js') })
注意点としては
index.js
をtemplates/
配下に移動させること -
続いて
templates/index.js
で発行するGraphQLのクエリをページネーションに対応させる
参考ページに書いてあるとおり$skip
と$limit
を組み込むquery ($skip: Int!, $limit: Int!) { allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } skip: $skip # <- これを追加 limit: $limit # <- これを追加 ) { ... } }
-
↑のクエリに書き換えると、コンポーネントに渡される引数に
pageContext
が追加され、ページネーションに関する情報が取得できるので、適宜ページ移動するリンクを設置していくconst BlogIndex = ({ data, pageContext }) => { return( <div> ... <Link to={pageContext.previousPagePath}>前のページ</Link> <Link to={pageContext.nextPagePath}>次のページ</Link> </div> ) }
これで記事一覧のページネーションは完了
続いてタグ毎の一覧のページネーションをやっていく
タグ機能の追加
-
Gatsby.js公式スターターを使っている場合markdownで記事を作れるようになっていると思うので、まずは記事にタグ情報を追記する
記事のFront matterに↓のような感じで追加する(以下はこの記事のFront matterを使った一例)--- title: Gatsby.jsのブログにページネーションとタグを導入する date: 2020-12-11 slug: pagination-and-tags-with-gatsby tags: [Gatsby.js タグ ページネーション] # <- new! ---
-
この状態で以下のクエリを発行すると
{ allMarkdownRemark { group(field: frontmatter___tags) { fieldValue totalCount } } }
以下のような結果が得られる
{ "data": { "allMarkdownRemark": { "group": [ { "tag": "Gatsby.js", "totalCount": 2 }, { "tag": "タグ", "totalCount": 1 }, ... ] } } }
-
これだとどんなタグがあって、そのタグにどのくらい記事があるのかしかわからんので、さらに記事まで取得する
具体的には以下のようにnodes
を追加する{ allMarkdownRemark { group(field: frontmatter___tags) { fieldValue totalCount nodes { fields { slug } } } } }
すると、各タグ毎の記事のリストが得られる
{ "data": { "allMarkdownRemark": { "group": [ { "tag": "Gatsby.js", "totalCount": 2, "nodes": [ { "fields": { "slug": "/hogehoge/" } }, { "fields": { "slug": "/fugafuga/" } } ] }, { "tag": "タグ", "totalCount": 1, "nodes": [ { "fields": { "slug": "/hogehoge/" } } ] }, ... ] } } }
-
あとは通常の一覧のページネーションを作成する要領で、タグの一覧毎にページを生成してあげればよいので、
gatsby-node.js
のGraphQLのクエリに↑の内容を組み込むtags: allMarkdownRemark(limit: 1000) { group(field: frontmatter___tags) { fieldValue nodes { fields { slug } } } }
-
さらに
gatsby-node.js
でGraphQLのレスポンスからページを生成していくconst tags = result.data.tags.group tags.forEach((tag) => { paginate({ createPage, items: tag.nodes, itemsPerPage: 10, pathPrefix: `/tags/${tag.fieldValue}`, component: path.resolve('src/templates/tags.js'), context: { tag: tag.fieldValue } }) })
-
最後に
templates/tags.js
で一覧に並べる記事をGraphQLのクエリでフィルタリングして完成query($skip: Int!, $limit: Int!, $tag: String!) { allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } filter: {frontmatter: {tags: {in: [$tag]}}} # <- 該当するタグで絞りこむために必要 skip: $skip # <- ページネーションのために必要 limit: $limit # <- ページネーションのために必要 ) { ... } } }
多分
index.js
のクエリとはfilter: { ... }
の部分だけが異なるが、どうにも「$tagがあるときはこっちのクエリ、ないときはこっちのクエリ」と言う処理が良い感じに書く方法がわからなかったので、それぞれ専用のファイルを用意することにした
この辺り良い感じにDRYにできる方法があれば教えていただきたいちなみに僕は適当なフラグを用意して
allMarkdownRemark
に対して@include(if: XXX)
をぶっかますというどうしようもない方法を思い浮かべたが、結局コード少なくならない上に見た目があまりに酷いのでやめた
GraphQLは仕様をシンプルに保つために色々複雑なことはできないようなので、ある程度DRYについては割り切らないといけないところもあるのかもしれない(Rails脳)
まとめ
Gatsby.jsは公式の資料とコードいじったりして得た知識しかないので、今回の方法はベストな方法なのかはわからない
しかし割とコード量も増大しないで機能追加できたので個人的には満足している