Chapter 19

記事詳細ページ

記事の本文を見るための記事詳細ページを作成していきましょう。

動的にページを作成する

これまで作成してきたページはlocalhost:8000/localhost:8000/page2のようにURLが固定でした。src/pagesディレクトリに作成したファイル名がURLに反映されページが作成されました。

一方で、ブログ詳細ページはContentfulのデータをもとにページを動的に作成する必要があります。そのため、あらかじめsrc/pagesディレクトリにファイルを用意しておくことはできません。
どのようにページを作成するかというと、createPagesというGatsbyのAPIを用いてページを作成していきます。

プロジェクト直下にgatsby-node.jsというファイルを作成します。このファイルでcreatePagesで扱うことができます。
以下のように書いてみましょう。

// gatsby-node.js
const path = require("path")
exports.createPages = async ({ graphql, actions, reporter }) => {
    const { createPage } = actions

    const result = await graphql(
        `
        {
          allContentfulPost {
            edges {
              node {
                title
                image {
                  file {
                    url
                  }
                }
                body {
                  childMarkdownRemark {
                    html
                  }
                }
                updatedAt(locale: "ja-JP", formatString: "YYYY年MM月DD日")
                description {
                  description
                }
                slug
              }
            }
          }
        }
        `
    )

    if (result.errors) {
        reporter.panicOnBuild(`Error while running GraphQL query.`)
        return
    }

    const { edges } = result.data.allContentfulPost

    edges.forEach(edge => {
        createPage({
            path: `/post/${edge.node.slug}/`,
            component: path.resolve("./src/templates/post.js"),
            context: { post: edge.node }
        })
    });
}

前半のコードは、Contentfulから記事のデータ取得するGraphQLのクエリです。本文のbodyを新しく追加しています。

後半のコードでは取得した各記事データに対し、createPageを用いてページを作成しています。
createPageの引数はこのようになっています。

  • path: 作成するページのURLのパスを表します。slugをもとに作成しています。
  • component: ページを作成するためのもととなるコンポーネントのファイルを指定します。このファイルは後ほど作成します。
  • context: componentに渡すデータを設定します。記事データを渡すため、edge.nodeを指定しています。

テンプレートファイル

createPageの引数のcomponentに指定したsrc/templates/post.jsを作成していきましょう。このファイルをもとに各ページが生成されます。
5行目のpageContextには、createPageの引数のcontextで指定したデータが渡ってきます。

// src/templates/post.js
import React from "react"
import "../styles/post.css"
import Layout from "../components/layout";

export default function Post({ pageContext }) {
    const { title, updatedAt, image } = pageContext.post;
    const body = pageContext.post.body.childMarkdownRemark.html;

    return (
        <Layout>
            <div className="post-header">
                <h1>{title}</h1>
                <p className="post-date">{updatedAt}</p>
            </div>
            <img src={image.file.url} className="post-image" alt="post-cover"></img>
            <div dangerouslySetInnerHTML={{ __html: body }} className="post-body" />
        </Layout>
    )
}

スタイリング

コンポーネントのスタイリングを行います。

/* src/styles/post.css */
.post-header {
  margin: 80px auto;
  max-width: 700px;
}

.post-date {
  color: #434343;
}

.post-image {
  width: 100%;
  height: 100%;
  max-height: 450px;
  object-fit: cover;
  box-shadow: rgba(0, 0, 0, 0.2) 0px 30px 60px -10px, rgba(0, 0, 0, 0.22) 0px 18px 36px -18px;
}

.post-body {
  margin: 80px auto;
  max-width: 700px;
}

.post-body img{
  width: 100%;
  height: auto;
}

ページへのリンク作成

トップページから詳細ページへのリンクを作成します。

// src/components/post-link.js
~省略~
export default function PostLink({ post }) {
    const { title, updatedAt, image } = post;
    const description = post.description.description;
    const pageLink = `/post/${post.slug}/`

    return (
        <Link to={pageLink} className="post-link-anchor">
            <div className="post-link">
						~省略~
            </div>
        </Link>
    )
}

リンクのスタイリング

<Link>タグによってスタイリングが崩れないようにします。

/* src/styles/post-link.css:55 */
~省略~
.post-link-anchor {
  color: inherit;
  text-decoration: none;
}

プラグインの導入

Contentfulから取得したデータの中のbodyはmarkdown形式でデータが保存されており、表示する際にはHTMLに変換する必要があります。
MarkdownのデータをGraphQL上でHTMLに変換できるGatsbyのプラグインをインストールします。
このプラグインを利用することによってgatsby-node.jsの18 - 20行目の形式でデータをHTML受け取れるようになります。

$ npm install --save gatsby-transformer-remark

プラグインを有効化します。

// gatsby-config.js
  plugins: [
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.GATSBY_CONTENTFUL_SPACE_ID,
        accessToken: process.env.GATSBY_CONTENTFUL_API_KEY
      }
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        commonmark: true,
        footnotes: true,
        pedantic: true,
        gfm: true,
        plugins: [],
      },
    },

オプションを変更したり、プラグイン内にさらにプラグインを入れることでHTMLへの変換方法を変えることができます。プラグインの詳細はこちらに記載されているので参考にしてみてください。

https://www.gatsbyjs.org/packages/gatsby-transformer-remark/

表示結果

gatsby developを行い、トップページから詳細ページを表示します。
uploadedImage.png

createPageの実行タイミング

createPageによる動的なページの生成は開発サーバーを立ち上げたとき、つまりgatsby developのタイミングで1度だけ実行されます。取得したデータはpublic/page-data/postにJSON形式で保存されています。このデータをもとにページは表示されます。
そのため、ページのリクエストが来た際にContentfulからのデータを取得する必要はありません。これはページの描画速度を速くするとともにサーバー(Contentful)への負荷も減らすことができるという2つの利点を持っています。