#動機
静的サイトジェネレーターのGatsby.jsでブログを開発するにあたり、Archiveを作ろうと思い挑戦。
私が調べた(対して調べてない・・・)範囲ではArchive作る便利なpluginとかないないみたい。なので、実作。ベストプラクティスではない、と思う。
#作り
記事を作成した月ごとにまとめるよくある感じにする。
まだ7、8月しか記事を書いてないので2ヶ月分しかない。もっと書かなきゃ。
#環境
・OS: macOS 10.15.5
・gatsby: 2.24.14
・ヘッドレスCMS: Contentful
・FW: React Bootstrap
#手順
1.templateフォルダにarchiveページ用のテンプレートを作成する。
2.gatsby-node.jsで、archiveページを生成する
3.archiveページのComponentを作成する。
まずはarchiveページ用のテンプレートページを作っていきます。テンプレートはページの構成が一緒で文章と画像を差し替えるだけでいいページをたくさん作る時に使います。便利。
import React from "react"
import Layout from "../components/layout"
import { graphql, Link } from "gatsby"
import { Container, Row, Col, Card, Button } from "react-bootstrap"
import Img from "gatsby-image"
import Archive from "../components/archive"
export const query = graphql`
query($skip: Int!, $limit: Int!, $currentYearMonth: Date!, $nextYearMonth: Date!){
allContentfulCode(
sort: {fields: createdDate, order: DESC},
limit: $limit,
skip: $skip,
filter: {createdDate :
{
lt: $nextYearMonth,
gte: $currentYearMonth
}
}
){
edges {
node {
title
slug
category {
category
categorySlug
id
}
description {
content {
content {
value
}
}
}
createdDate(formatString: "YYYY/MM/DD")
thumbnail{
fluid(maxWidth: 500) {
src
...GatsbyContentfulFluid_withWebp
}
description
}
}
}
}
}
`
function ArchiveTemplate(props) {
return (
<Layout>
<Container fluid >
<Row className="justify-content-md-center">
<Col xs={7} className="mt-3" style={{ borderBottom: "3px solid grey" }} >
<h1 className="m-3">{`${props.pageContext.currentYearMonth}`}</h1>
</Col>
<Col xs={3} >
</Col>
</Row>
<Row className="justify-content-md-center">
<Col xs={7} style={{ padding: "20px", textAlign: "center" }} className="mt-3" >
{props.data.allContentfulCode.edges.map((edge, index) => (
<Card className="m-5">
<Card.Body>
<Card.Text>
<Link to={`/code/${edge.node.category[0].categorySlug}/${edge.node.slug}`}>
<h3>{edge.node.title}</h3>
</Link>
{edge.node.category.map(x => (
<li className={x.categorySlug} key={x.id} style={{ listStyleType: "none" }}>
<Link to={`/code/${x.categorySlug}/`}>#{x.category}</Link>
</li>
))}
</Card.Text>
</Card.Body>
<figure>
<Link to={`/code/${edge.node.category[0].categorySlug}/${edge.node.slug}`}>
<Img
fluid={edge.node.thumbnail.fluid}
alt={edge.node.thumbnail.description}
style={{ height: "300px" }}
/>
</Link>
</figure>
<Card.Body>
<Card.Text>
{edge.node.description.content[0].content[0].value}
</Card.Text>
<Button variant="outline-secondary" href={`/code/${edge.node.category[0].categorySlug}/${edge.node.slug}`}>See More</Button>
</Card.Body>
<Card.Footer className="text-muted">作成日 {edge.node.createdDate}</Card.Footer>
</Card>
))}
<div>
{!props.pageContext.isFirst && (
<div className="text-left" style={{ display: "inline" }}>
<Link
to={
props.pageContext.currentPage === 2
? `/${props.pageContext.currentYear}/${props.pageContext.currentMonth}/`
: `/${props.pageContext.currentYear}/${props.pageContext.currentMonth}/${props.pageContext.currentPage - 1}`
}
rel='prev'>
<span className="m-5">前のページ </span>
</Link>
</div>
)}
{!props.pageContext.isLast && (
<div className="text-right" style={{ display: "inline" }}>
<Link to={`/${props.pageContext.currentYear}/${props.pageContext.currentMonth}/${props.pageContext.currentPage + 1}/`} rel='next'>
<span className="m-5">次のページ</span>
</Link>
</div>
)}
</div>
</Col>
<Col xs={3}>
<Archive />
</Col>
</Row>
</Container>
</Layout >
)
}
export default ArchiveTemplate
##ポイント
GraphQLのfilterを使って、データの絞り込みを行う。今回は対象の月のデータを、対象の月以上、翌月未満で絞り込んで取得。
{
lt: $nextYearMonth,
gte: $currentYearMonth
}
}```
次にarchiveページを生成します。生成場所は、gatsby-node.jsです。
まずはqraphqlを使って、全データを取得します。項目は記事生成日のみ。
```js:gatsby-node.js
const path = require('path')
module.exports.createPages = async ({ graphql, actions}) => {
const { createPage } = actions
const archiveTemplate = path.resolve('./src/template/archiveTemplate.js')
const res = await graphql(`
query{
allContentfulData(sort: {fields: createdDate, order: DESC}) {
edges {
node {
createdDate
}
}
}
}
`)
次にarchiveページを生成していきます。
// archive //
const archivePerpage = 5 //1ページに表示する記事の数
const yearMonth = ['2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12'];
const archiveByMonth = yearMonth.map(x => {
const obj = {};
obj[x] = (res.data.allContentfulData.edges.filter(y => y.node.createdDate.match(x))).length;
return obj;
});
archiveByMonth.forEach((x) => {
const archivePosts = x[Object.keys(x)] //記事の総数
if (archivePosts === 0) return;
const archivePages = Math.ceil(archivePosts / archivePerpage) //記事一覧ページの総数
const currentYear = Object.keys(x)[0].split('-')[0];
const currentMonth = Object.keys(x)[0].split('-')[1];
const nextMonth = Number(currentMonth) + 1 === 13 ? '01' : ('00' + String(Number(currentMonth) + 1)).slice(-2);
const nextYear = (nextMonth === '01') ? String(Number(nextMonth) + 1) : currentYear;
Array.from({ length: archivePages }).forEach((_, i) => {
createPage({
path:
i === 0
? `/${currentYear}/${currentMonth}`
: `/${currentYear}/${currentMonth}/${i + 1}/`,
component: archiveTemplate,
context: {
currentYearMonth: currentYear + '-' + currentMonth,
nextYearMonth: nextYear + '-' + nextMonth,
currentYear,
currentMonth,
skip: archivePerpage * i,
limit: archivePerpage,
currentPage: i + 1, // 現在のページ番号
isFirst: i + 1 === 1, //最初のページ
isLast: i + 1 === archivePages, // 最後のページ
}
})
})
})
##ポイント
月ごとにデータ数をカウントする。
const archiveByMonth = yearMonth.map(x => {
const obj = {};
obj[x] = (res.data.allContentfulData.edges.filter(y => y.node.createdDate.match(x))).length;
return obj;
URLは/YYYY/MM/ページ番号にする。
``` ? /${currentYear}/${currentMonth}
: `/${currentYear}/${currentMonth}/${i + 1}/`,
最後にarchiveのCompenentを作成する。
```js:archive.js
import React from 'react';
import { useStaticQuery, graphql, Link } from 'gatsby'
import { Container, Row, Col } from 'react-bootstrap';
import Style from './layout.module.scss'
const Archive = (props) => {
const datas = useStaticQuery(graphql`
query{
allContentfulCode {
edges {
node {
createdDate(formatString:"YYYY/MM/DD")
}
}
}
}
`)
const date = ['2020/07', '2020/08', '2020/09', '2020/10', '2020/11', '2020/12'];
const count = date.map(x => {
const obj = {};
obj[x] = (datas.allContentfulCode.edges.filter(y => y.node.createdDate.match(x))).length;
return obj;
});
return (
<div className={Style.archive_wrap}>
<Container className={Style.content}>
<Row>
<Col>
<h3>Archive</h3>
<ui>
{count.map((x, index) => {
if (x[Object.keys(x)] === 0) return false;
return <li key={index}><Link to={`/${Object.keys(x)}`}>{`${Object.keys(x) + ' (' + x[Object.keys(x)] + ')'}`} </Link> </li>
}
)}
</ui>
</Col>
</Row>
</Container>
</div>
);
}
export default Archive
##ポイント
記事数が0の月はarchiveリストに表示しない。
if (x[Object.keys(x)] === 0) return false;