gatsby

Gatsby+Contentfulで記事内に前後の記事へのリンクを付ける

2021年03月20日

記事の末尾から前後の記事に飛べるようにして、他の記事も読んでもらえるようにします。

記事ページの生成はgatsby-node.jsにGraphQLクエリーを投げて、記事ごとに生成していく形になっていると思います。実はgatsby-source-contentfulで引っ張ってくる記事データにはnextpreviousというオブジェクトが含まれており、そこから当該の記事の前後のデータが拾えます。試しにGraphiQLで見てみましょう。

スクリーンショット 2021-03-20 11.49.38

前後の記事のデータが取れていること、前後に記事がない場合はnullが帰ってくるのがわかると思います。gatsby-node.jsのGraphQLクエリーを、前後の記事のデータを取ってくるように変更しましょう。

...
const result = await graphql(
    `
    {
        allContentfulPost(sort: {fields: date, order: ASC}) {
            edges {
                node {
                    title
                    date(locale: "ja-JP", formatString: "YYYY年MM月DD日")
                    year: date(formatString: "YYYY")
                    month: date(formatString: "MM")
                    cover {
                        gatsbyImageData
                        title
                    }
                    description {
                        description
                    }
                    slug
                    body {
                        childMarkdownRemark {
                            html
                        }
                    }
                }
                next {
                    title
                    prefix: date(formatString: "/YYYY/MM/")
                    slug
                    cover {
                        gatsbyImageData
                    }
                }
                previous {
                    title
                    prefix: date(formatString: "/YYYY/MM/")
                    slug
                    cover {
                        gatsbyImageData
                    }
                }
            }
        }
    }
    `
)
...

このブログのパーマリンクの形式は/YYYY/MM/slugになっているので、日付を/YYYY/MM/の形式で取ってくることでリンクを作りやすくします。

忘れずにこれらの情報を生成するページに渡しておきましょう。

...
edges.forEach(edge => {
    createPage({
        // permalink
        path: `/${edge.node.year}/${edge.node.month}/${edge.node.slug}`,
        component: path.resolve("./src/templates/post.tsx"),
        context: { post: edge.node, next: edge.next, prev: edge.previous }
    })
});
...

templates/post.tsx でよしなに呼んでやることができます。前後にページがない時はnullが帰ってくるので、そこだけ検知してあげる必要があります。

...
export default function Post({ pageContext }) {
    const { title, date, description } = pageContext.post;
    const coverImg = getImage(pageContext.post.cover);
    const coverTitle = pageContext.post.cover.title;
    const body = pageContext.post.body.childMarkdownRemark.html;
 
    const next = pageContext.next;
    const prev = pageContext.prev;
 
    return (
        <Layout>
            <Head title={ title + ` :: Lon Sagisawa` } description={ description } />
            <GatsbyImage image={ coverImg } alt={ coverTitle } className="cover" />
            <article>
                <div>
                    <h1>{ title }</h1>
                    <p>{ date }</p>
                </div>
                <div dangerouslySetInnerHTML={{ __html: body }} />
            </article>
            <hr />
 
            <PostNav next={ next } prev={ prev } />
        </Layout>
    )
}
...

実際のナビゲーションはPostNavというコンポーネントで生やしています。

...
export default function PostNav({ next, prev }) {
    const prevCover = prev == null ? null : getImage( prev.cover )
    const nextCover = next == null ? null : getImage( next.cover )
 
    return (
        <nav className="post_nav">
            { prev == null ? null :
                <Link to={ prev.prefix + prev.slug } className="post_nav_wrapper" >
                    <div className="post_nav_item">
                        <GatsbyImage image={ prevCover } alt={ prev.cover.title } />
                        <p className="post_nav_side_prev">前の記事</p>
                        <p className="post_nav_title">{ prev.title }</p>
                    </div>
                </Link>
            }
            { next == null ? null : 
                <Link to={ next.prefix + next.slug } className="post_nav_wrapper">
                    <div className="post_nav_item">
                        <GatsbyImage image={ nextCover } alt={ next.cover.title } />
                        <p className="post_nav_side_next">次の記事</p>
                        <p className="post_nav_title">{ next.title }</p>
                    </div>
                </Link>
            }
        </nav>
    )
}

いちいちnullの判定をしているので、そこはもう少しエレガントにしたい所です。

スクリーンショット 2021-03-20 12.14.22

あとはスタイルを付けるだけです。