September 16, 2019

GatsbyJSで次の記事、前の記事へのリンクを生成する方法

こんにちは。mono(@mono7555e)です。

記事ページで他の記事へ行く術がなく直帰率が高くなっていたので他の記事への遷移を追加することにしました。

ちょっとひと手間かかりますがGatsbyJSでも実現できましたのでご紹介したいと思います。

ページ生成時に前後の記事情報を渡す

gatsby-node.js
exports.createPages = async ({ actions, graphql }) => {
  const { createPage } = actions
  const template = path.resolve(`src/templates/blog.tsx`)
  await graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___published_at] }
        limit: 2000
      ) {
        edges {
          node {
            fields {
              slug
            }
            frontmatter {
              title
              image
            }
          }
        }
      }
    }
  `).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors)
    }
    const pages = result.data.allMarkdownRemark.edges
    pages.forEach(({ node }, index) => {
      const prev = index === 0 ? null : pages[index - 1].node      const next = index === pages.length - 1 ? null : pages[index + 1].node      createPage({
        path: node.fields.slug,
        component: template,
        context: {
          slug: node.fields.slug,
          prev,          next,        },
      })
    })
  })
}

重要なのはconst prev = ~const next ~の部分と、contextでそれを設定している部分です。
それ以外のところは自由です。

設定された前後の記事情報を元にリンクを生成

ちょっとごちゃごちゃしてしまったので前後の記事用のコンポーネントに分割しました。

src/components/molecules/prevNext.tsx
import React from "react"
import { Box, Card, CardActionArea, CardContent, Typography } from "@material-ui/core"

import Image from "../atoms/image"

const PrevNext = ({ prev, next }) => {
  return(
    <Box mt={5} display="flex" justifyContent="space-between">
      { prev == null ? null : _link(prev, `prev`)}
      { next == null ? null : _link(next, `next`)}
    </Box>
  )
}
export default PrevNext

const _link = ({ fields, frontmatter }, direction) =>{
  return(
    <Box display="flex" flexDirection="column" style={{ width: `40%` }}>
      <Typography gutterBottom variant="body2" component="h6">
        { direction == 'prev' ? '前の記事' : '次の記事'}
      </Typography>
      <Card style={{ width: `100%`, height: `100%`}}>
        <CardActionArea href={fields.slug} style={{ display: 'flex', alignItems: `stretch`, height: `100%` }}>
          <Box style={{ width: `35%` }}>
            <Image filename={frontmatter.image} style={{ height: `100%` }} />
          </Box>
          <CardContent style={{ flex: `1` }}>
            <Typography variant="body2" component="p">
              {frontmatter.title}
            </Typography>
          </CardContent>
        </CardActionArea>
      </Card>
    </Box>
  )
}

このブログはMaterial-UIを使っているのでちょっとややこしい感じになりますが、単純にリンクをするだけであればもう少し簡単に書けると思います。

あとは作った前後の記事用のコンポーネントをテンプレートから呼び出すだけです。

src/templates/blog.tsx
// importなどなど
export default function Template({ data, pageContext }) {
  // 中略
  return (
    <Layout>
      <Container maxWidth="md" component="article">
        {/* その他コンポーネント読み込み */}
        <PrevNext prev={pageContext.prev} next={pageContext.next} />      </Container>
    </Layout>
  )
}

まとめ

やってみると結構かんたんでした。

今はイメージとタイトルだけですが、タグや日付など各記事が持っている情報を利用できるのでよりリッチな前後の記事へのリンクが設置できると思います。

この投稿を書いた人

mono

ゲーム実況やったりVLOG撮ったりしているガジェット好きIT系の人です