GatsbyJSでカテゴリー機能を実装する
01 26, 2019
カテゴリーページにもっと自由を
カテゴリー一覧を作っていきたい。
GatsbyのStarterではGraphQLのGroup機能を使ってやっている人が多い。
今回はMarkdownを使ってある程度自由にカスタマイズできるように実装していきたい。
それには理由があって、僕のサイトの作り方だとカテゴリーページが上がる傾向にある。そこでユーザーを離脱させない工夫としてカテゴリーをmarkdownで自由にカスタマイズできるようにしておきたい。
作っていくもの
- categoriesフォルダ
- gatsby-node.js
- /templete/categorypage.js
- /components/categoryPostlist/
こんだけで実装できそうだ。 まず、/contents/categoies/food.mdと /contents/categories/drink.mdを作成。
---
pagetype: "category"
categoryname: "食べ物"
categoryslug: "food"
---
と
---
pagetype: "category"
categoryname: "飲み物"
categoryslug: "drink"
---
YAMLで上記のように記載する。 カスタマイズしたければMarkdownで加工すればいいようにする。
gatsby-node.js
続いてgatsby-node.jsを書いていく前にGraphQLでどこにフィルターをかけるかを見てみる。
{
allMarkdownRemark(filter: {frontmatter: {pagetype: {eq: "category"}}}) {
edges {
node {
id
}
}
}
}
こんな感じでフィルターかけるのが簡単そう。 同じところから引っ張る場合エラーが出るのでそれぞれ名前を付ける
{
blogposts: allMarkdownRemark(sort: {fields: [frontmatter___date], order: DESC}, limit: 1000) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
categories: allMarkdownRemark(filter: {frontmatter: {pagetype: {eq: "category"}}}) {
edges {
node {
frontmatter {
categoryslug
}
}
}
}
}
最終的には
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
const blogPost = path.resolve(`./src/templates/blog-post.js`)
const categoryPage = path.resolve(`./src/templates/categorypage.js`)
resolve(
graphql(
`
{
blogposts: allMarkdownRemark(sort: {fields: [frontmatter___date], order: DESC}, limit: 1000) {
edges {
node {
fields {
slug
}
frontmatter {
title
}
}
}
}
categories: allMarkdownRemark(filter: {frontmatter: {pagetype: {eq: "category"}}}) {
edges {
node {
frontmatter {
categoryslug
}
}
}
}
}
`
).then(result => {
if (result.errors) {
console.log(result.errors)
reject(result.errors)
}
// Create blog posts pages.
const posts = result.data.blogposts.edges
const categories = result.data.categories.edges
posts.forEach((post, index) => {
const previous =
index === posts.length - 1 ? null : posts[index + 1].node
const next = index === 0 ? null : posts[index - 1].node
createPage({
path: post.node.fields.slug,
component: blogPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
})
categories.forEach((category) => {
createPage({
path: `/category/${(category.node.frontmatter.categoryslug)}`,
component: categoryPage,
context: {
slug: category.node.frontmatter.categoryslug,
}})
})
})
)
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
こうなった。 次は、テンプレを作る
import rehypeReact from "rehype-react"
import Sample from "../components/Sample"
import Categoryposts from "../components/categoryposts"
const renderAst = new rehypeReact({
createElement: React.createElement,
components: { "sample": Sample }
}).Compiler
class CategoryPageTemplate extends React.Component {
render() {
const post = this.props.data.markdownRemark
const siteTitle = this.props.data.site.siteMetadata.title
return (
<Layout location={this.props.location} title={siteTitle}>
<SEO title={post.frontmatter.categoryname} />
<h1>{post.frontmatter.categoryname}</h1>
<div>{renderAst(post.htmlAst)}</div>
<hr/>
<Categoryposts category={post.frontmatter.categoryslug} />
</Layout>
)
}
}
export default CategoryPageTemplate
export const pageQuery = graphql`
query CategoryPageBySlug ($slug: String!){
site {
siteMetadata {
title
author
}
}
markdownRemark (frontmatter:{categoryslug:{ eq: $slug}})
{
htmlAst
frontmatter {
categoryname
categoryslug
}
}
}
`
最終的にこうなった。 そして、categorylistを作る。 /src/components/categoryposts/index.js
import React from 'react'
import { StaticQuery, graphql } from 'gatsby'
const CategoryPosts = (props) => (
<StaticQuery
query={graphql`
query{
allMarkdownRemark{
edges{
node{
frontmatter{
pagetype
category
title
date
}
excerpt
}
}
}
}
`}
render={(data) => {
const postlists = data.allMarkdownRemark.edges
const posts = postlists.filter((category)=>{
return (category.node.frontmatter.category === props.category)
})
return (
<div>
{posts.map(({ node }) => {
return (
<div key={node.frontmatter.title}>
<p>{node.frontmatter.date}</p>
<p dangerouslySetInnerHTML={{ __html: node.excerpt }} />
</div>
)
})}
</div>
);
}}
/>
)
export default CategoryPosts
ポイントはStaticQueryを使って全部取得してからフィルタリングしているところ。 コンポーネントでGraphQLを使用する場合、普通のクエリではと動かない仕様になっている。 上手く表示できた!