記事の本文を見るための記事詳細ページを作成していきましょう。
動的にページを作成する
これまで作成してきたページは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への変換方法を変えることができます。プラグインの詳細はこちらに記載されているので参考にしてみてください。
表示結果
gatsby develop
を行い、トップページから詳細ページを表示します。
createPageの実行タイミング
createPageによる動的なページの生成は開発サーバーを立ち上げたとき、つまりgatsby develop
のタイミングで1度だけ実行されます。取得したデータはpublic/page-data/post
にJSON形式で保存されています。このデータをもとにページは表示されます。
そのため、ページのリクエストが来た際にContentfulからのデータを取得する必要はありません。これはページの描画速度を速くするとともにサーバー(Contentful)への負荷も減らすことができるという2つの利点を持っています。