GatsbyJSでの動的ページ自動作成方法を理解したい
本文書は公開済みの”基礎から始めるGatsbyJS入門”の続きです。前回の文書ではGraphQLのqueryを利用してMarkdownファイルの内容を取得しブラウザに表示できることまでを確認しました。
本文書では一歩進みMarkdownファイル毎に個別ページのslug(URL or PATH)を動的に作成しそのslug(URL or PATH)にアクセスするとMarkdownファイルの内容が動的に表示できるコードを作成していきます。
¥src¥pagesの下にpostsディレクトリを作成し, first-article.md, second-article.mdの2つのファイルが保存されている状態から初めて動的なページの作成方法を確認していきます。これまでの記事を読んでおくことは必須ではありません。
slug(URL or PATH)の取得、ページの作成するために見慣れないGatsbyの関数(API)が出てくるのでJavascript、プログラミングの入門者の方は少し難しく感じるかもしれませんができるだけ詳細に説明しています。
目次
Slug(URL or PATH)の作成
slugについて
ブラウザからアクセスするURLやPATHを以下ではslugという名称を利用します。
本文書ではpostsディレクトリに保存したMarkdownファイルのパスを利用してslugを作成します。slugはMarkdownファイルのファイル名だけを利用して作成することもできます。
最終的に下記のように表示されているブログ一覧のタイトルをクリックすると各ブログの記事の内容が閲覧できるように設定を行なっていきます。
URLが少し見えにくいかもしれませんが、URLにはhttp://localhost:8000/posts/first-article/を設定しています。/posts/first-articleにアクセス後に表示される内容はファイルパスであるposts¥first-article.mdの内容に対応します。
gatsby-node.jsファイル
動的にslugとページを作成するためにnodeの設定ファイルであるgatsby-node.jsファイルをインストールディレクトリに作成します。デフォルトではこのファイルは存在しないので手動で作成する必要があります。gatsby-node.jsに記述したコードはビルド(gatsby develop)する際に実行されます。
slugの作成
本文書でのslugはmdファイルのファイルパスと名前から作成します。postsディレクトリのfirst-article.mdであればslugは/posts/first-articleとなります。
slugを作成するためにはslugの名前である/posts/first-articleをGatsby内の情報から取得する必要があります。直接/posts/first-articleと取得することができないため、これから説明を行う処理を行うことで/posts/first-articleを取得します。
/posts/fist-article/という情報が取得できたらnodeに取得したslugを追加します。追加が完了するとGraphQLのqueryからがslugを取得できるようになります。。
nodeに追加したslugはただの/posts/first-articleという文字列にすぎないためslugを作成しただけではページは作成できません。そのためslugを作成した後にページを作成するための処理を追加する必要があります。
slugの情報を作成するためにGatsbyのAPIであるonCreateNodeを利用します。onCreateNodeを利用してMarkdownファイルのパス名を取り出してslugの作成に使います。
slugを作成するためはMarkdownファイルの情報を取得する必要があります。Markdownファイルに関連するはnodeがslugを作成するための必要なデータを持っています。Markdownファイルのnode情報のみ取得するためnodeのデータ構造の一つであるinternalのtypeを利用します。
nodeのinternal typeを利用するのはnodeのデータ構造が下記のように決められておりMarkdownファイルの場合はtypeにMarkdownRemarkが設定されるためです。
typeにはプラグインのOwnerによって選択されグローバルでユニークの名前がつけられています。MarkdownファイルのOwnerはインストールしたgatsby-transformer-remarkです。MarkdownファイルのtypeはMarkdownRemarkに設定されているためtypeにMarkdownRemarkを指定することでnodeの中からMarkdownファイルのnode情報のみ取得することが可能となります。
実際にgatsby-node.jsファイルに以下のコードを追加して、typeにMarkdownRemakを持つnodeを取得します。
exports.onCreateNode = ({ node }) => {
if (node.internal.type === `MarkdownRemark`) {
console.log(node)
}
}
設定後にgatsby developを再実行するとビルドのメッセージの中にMarkDownRemarkのtypeを持つnodeの情報が表示されます。
// Result command:gatsby develop
//略
{
id: 'd9feff1d-fcea-5d55-9fdf-fdb773dc50b8',
children: [],
parent: '8c925fee-e2b5-5dd6-b3c9-91cc3f3f9193',
internal: {
content: '\n### Gatsby での最初の記事\n\nGatsby サイト上に表示するための初めての記事です。\n\nブラウザ上に表示されるのかの確認です。\n',
type: 'MarkdownRemark',
contentDigest: 'af41eff8bc81a0ba45d5106599ad5aa9',
counter: 34,
owner: 'gatsby-transformer-remark'
},
frontmatter: { title: 'はじめての記事', date: '2020-08-18' },
excerpt: '',
rawMarkdownBody: '\n### Gatsby での最初の記事\n\nGatsby サイト上に表示するための初めての記事です。\n\nブラウザ上に表示されるのかの確認です。\n',
fileAbsolutePath: '/Users/mac/Desktop/gatsby-site/src/posts/first-article.md'
}
//略
取得したMarkdownファイルの情報からMarkdownファイルのパスを取得してslugを作成します。fileAbsolutePathからfirst-articleという名前を取り出してslugにすることも可能ですが、Gatsbyのドキュメントを参考にgatsby-source-filesystemプラグインのcreateFilePathを利用して/posts/first-articleという名前を取り出します。
以下のコードを利用してMarkdownファイルのrelativePath(相対パス)の情報を取得します。Markdownファイルのnode自体にはrelativePathを持っていない(先ほどのconsole.log(node)の結果を参照)のでnode.parentにgetNodeメソッドでアクセスしてfileNodeの情報を取得し、そこからrelativePathの情報を取得します。
exports.onCreateNode = ({ node, getNode }) => {
if (node.internal.type === `MarkdownRemark`) {
const fileNode = getNode(node.parent)
console.log(`\n`, fileNode.relativePath)
}
}
gatsby developコマンドを再実行するとそのメッセージの中にMarkdownファイルのrelativePathの情報が表示されます。
//Result command: gatsby develop
//略
pages/posts/second-article.md
pages/posts/first-article.md
//略
拡張子mdなど不必要な情報が含まれているので、posts/first-article.mdをcreateFilePathを利用して/posts/first-article/の形に変更します。
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode }) => {
if (node.internal.type === `MarkdownRemark`) {
console.log(createFilePath({ node, getNode, basePath: `pages` }))
}
}
これでslugの作成は完了です。
//Result command: gatsby develop
/posts/first-article/
/posts/second-article/
取り出したslugをnodeに追加を行うためcreateNodeFieldを利用します。
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}
コードの内容はMarkdownファイルのパスを取り出しslugを作成、その後、crateNodeFieldのnameにslugを設定しその値には作成したslugを設定しています。
createNodeFieldでnodeに追加したslugはGraphiQLで確認することができます。GraphiQLのExplorerで確認するとnodeに新たにfieldが追加されその下にslugも追加されます。追加されていない場合はgatsby developコマンドの再実行を忘れている場合があるのでその場合は実行を行ってください。
ここまででslugの作成は完了です。次はこのslugにブラウザでアクセスした際にMarkdownファイルの内容が表示されるようにページの作成を行います。
動的ページの作成
templateファイルの作成
ブラウザから/posts/first-article/にアクセスした際にMarkdownファイルの内容を表示させる必要がありますがその前にtemplateファイルを作成します。templateファイルによってページのフォーマットが決まります。一般的にブログなどの記事はページによってページフォーマットを変える必要がないため、1つのtemplateファイルをすべての記事で共有します。templateファイルが作成できれば取得したデータを決められた場所に配置することで同じフォーマットの記事が表示されます。
srcディレクトリの下にtemplatesディレクトリを作成しさらにその下にpost.jsファイルを作成します。内容は下記の通りでこの時点では”ブログ記事のコンテンツ”と表示されるだけです。この時点では、Markdownファイルから内容を取得し表示させるといった設定は行っていません。
import React from "react"
import Layout from "../components/layout"
export default function Post() {
return (
<Layout>
<div>ブログ記事のコンテンツ</div>
</Layout>
)
}
createPageについて
ページを動的に作成する場合は、GatsbyのAPIのcreatePageを利用します。cratePageの構造は下記の通りでシンプルです。
createPage({
//ページのパス設定
path: node.fields.slug,
//ページテンプレートのパス設定
component: path.resolve(`./src/templates/post.js`),
//ページテンプレートに渡したいデータの設定
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug: node.fields.slug,
},
動的にページを作成するために利用するcreatePageの構造がわかったのでgatsby-node.jsファイルに動的ページ作成のコードを記述します。
動的ページの作成コード
前半のcreateNodeFiledへのslugについては説明済で前半のコードでMarkdownファイルのnodeにslugを追加していいます。exports.createPagesからが追加したコードになります。先頭にJavaScriptのpathモジュールをrequireしているので忘れないようにしてください。
前半のコードでslugはnodeに追加されるためGraphQLでnodeから追加したslugを取得しています。GraphQLを実行すると複数のMarkdownファイルがあるので複数のnodeが取得できます。取得したresultをforEachすることで各nodeのslugを取り出してcreatePageの引数に入れてページの作成を行っています。
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/post.js`),
context: {
slug: node.fields.slug,
},
})
})
}
gatsby-node.jsを保存したらgatsby developコマンドを再実行して変更を反映させます。
ブラウザからslugの/post/first-article/にアクセスして以下の内容が表示されたら動的なページ作成は成功しています。
templateファイルpost.jsに”ブログ記事のコンテンツ”と記述しているので/posts/second-article/とアクセスしても同じ内容になります。
Markdownファイルからの内容取得
Markdownファイルに記述したファイルの内容を表示させるためにpost.js内でGraphQLのqueryを実行してファイルの内容を取得する必要があります。どのファイルから取得するかはcreatePageの引数で設定したslugで渡された値を利用します。以下のqueryでは変数$slugに一致したMarkdownファイルの内容を取得することができます。
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`
このGraphQLのqueryをtemplateファイルに追加して、取得した内容でページの内容を表示させます。htmlの部分はdangerouslySetInnerHTMLを利用しています。
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"
export default function Post({ data }) {
return (
<Layout>
<h1>{data.markdownRemark.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.markdownRemark.html }} />
</Layout>
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
}
}
`
ブラウザで再度/posts/first-article/にアクセスするとfirst-article.mdファイルの中身が表示されます。
/posts/second-artcile/にアクセスするとsecond-article.mdファイルの内容が表示されることも確認きます。この後にthird-article.mdファイルを作成しても/posts/third-article.mdでアクセスすることが可能です。
リンクの設定
トップページのindex.jsファイルに表示されているブログのタイトルから各ページに移動できるようにLinkの設定を行います。
slugが取得できるようにqueryの中にslugを追加します。追加したslugはLinkタグを使って各ページへのリンクを貼ります。
import React from "react"
import Layout from "../components/layout"
import { graphql, Link } from "gatsby"
export default function Home({ data }) {
return (
<div>
<Layout>
<h1>Gatsby Blog Site</h1>
{data.allMarkdownRemark.nodes.map(node => (
<div key={node.id}>
<Link to={node.fields.slug}>
<h2>{node.frontmatter.title}</h2>
</Link>
<p>{node.frontmatter.date}</p>
</div>
))}
</Layout>
</div>
)
}
export const query = graphql`
{
allMarkdownRemark {
nodes {
html
fields {
slug
}
frontmatter {
date
title
}
}
}
}
`
トップページのブログ一覧にリンクが貼られ、クリックすると各ページにアクセスすることができます。
動的ページの作成とブログ記事の一覧からのリンクを設定することができました。Markdownファイルを使って記事を追加していけば動的にページが作成される仕組みができました。文字だけのブログであればここまでの知識でブログを構築することも可能です。
しかし、ブログの記事には画像を追加したいものです。
次はGatsbyで画像を扱えるように設定を行なっていきます。