ダッシュで奪取

ゲーム、読書、人生

【Gatsby.js】個別ページをマークダウンで書きたい

前回:スターターを使ってブログ立ち上げまで

kyoruni.hatenablog.com

「このブログについて」みたいなページを作成しようと思います。

どうやら .js ファイルで作成するのがよくあるケースのようなのですが、個人的には全ページ .md ファイルに統一したいのでそんな感じにします。

環境

目次

  1. ページ生成のためのテンプレート作成
  2. 個別ページの元になる、マークダウンファイルを作成
  3. ページの生成
  4. メニュー表示用のコンポーネントを作成
  5. トップページからリンクを繋げる
  6. 参考 URL
  7. おわりに

1. ページ生成のためのテンプレート作成

ブログ記事と同じテンプレートを使ってしまうと「前の記事へ」「次の記事へ」が表示されるので、個別ページ専用のものを作成します。

まだよく分かっていないので(ひどい)、ブログ用のものをコピーして、使わなさそうなところを削除します。

src/templates/page.js
import * as React from "react"
import { graphql } from "gatsby"

import Layout from "../components/layout"
import Seo from "../components/seo"

const PageTemplate = ({
  data: { site, markdownRemark: page },
  location,
}) => {
  const siteTitle = site.siteMetadata?.title || `Title`

  return (
    <Layout location={location} title={siteTitle}>
      <article
        className="blog-post"
        itemScope
        itemType="http://schema.org/Article"
      >
        <header>
          <h1 itemProp="headline">{page.frontmatter.title}</h1>
        </header>
        <section
          dangerouslySetInnerHTML={{ __html: page.html }}
          itemProp="articleBody"
        />
      </article>
    </Layout>
  )
}

export const Head = ({ data: { markdownRemark: page } }) => {
  return (
    <Seo
      title={page.frontmatter.title}
      description={page.frontmatter.description || page.excerpt}
    />
  )
}

export default PageTemplate

export const pageQuery = graphql`
  query PageBySlug(
    $id: String!
  ) {
    site {
      siteMetadata {
        title
      }
    }
    markdownRemark(id: { eq: $id }) {
      id
      excerpt(pruneLength: 160)
      html
      frontmatter {
        title
        description
      }
    }
  }
`

2. 個別ページの元になる、マークダウンファイルを作成

とりあえずふたつ作成します。

  • template で使用するテンプレートを指定して、ブログ記事と出し分けします。

  • 既存のブログ記事( content/blog 配下 )の frontmatter--- で囲まれているところ ) にも template: "post" を追加しておきます。

content/pages/about.md
---
title: このブログについて
description: "おはようございます"
template: "page"
---

## おはようございます
good morning
## こんにちは
hello
## こんばんは
good evening
content/pages/test.md
---
title: テスト
description: "あいうえお"
template: "page"
---

## あいうえお
aiueo
## かきくけこ
kakikukeko
## さしすせそ
sashisuseso
content/blog 配下の全 .md ファイル
  • 例として content/blog/hello-world/index.md
   ---
   title: Hello World
   date: "2015-05-01T22:12:03.284Z"
   description: "Hello World"
+  template: "post"
   ---

   This is my first post on my new fake blog! How exciting!
   ...

3. ページの生成

content/pages 配下も、ページ生成させる仲間に加えます。

gatsby-config.js
  • 個別ページへのリンクは、menu で表示させます。

  • 取得できた順に表示だと任意の順番にできなさそうと思った & frontmatterseq のような値を持たせるのは嫌だというのが理由です。もうちょっとスマートな書き方はないのだろうか……。

  module.exports = {
    siteMetadata: {
      title: `アルミ缶の上にあるミカン`,
      author: {
        name: `kyoruni`,
        summary: `ほげ`,
      },
      ...
+     menu: [
+       {
+         label: 'このブログについて',
+         path: '/pages/about'
+       },
+       {
+         label: 'テスト',
+         path: '/pages/test'
+       }
+     ]
    },
    plugins: [
      `gatsby-plugin-image`,
      {
        resolve: `gatsby-source-filesystem`,
        options: {
-         name: `blog`,
-         path: `${__dirname}/content/blog`,
+         name: `pages`,
+         path: `${__dirname}/content`,
        },
      },
      ...
gatsby-node.js
  • frontmattertemplatepagepost かで、使うテンプレートを変更しています。

  • allMarkdownRemarknodenext previous を持っていたので、最初はこちらを「次の記事 / 前の記事」に使おうと思っていたのですが、pagepost も含む全ページが対象になってしまったのでやめました。スターターに元から入っていた処理( 配列の1つ前が前の記事、1つ後ろが次の記事 )そのままにしています。

    ...
    // Define a template for blog post
-   const blogPost = path.resolve(`./src/templates/blog-post.js`)
+   const postTemplate = path.resolve(`./src/templates/blog-post.js`)
+   const pageTemplate = path.resolve(`./src/templates/page.js`)
  
    // Get all markdown blog posts sorted by date
    const result = await graphql(
              ...
              fields {
                slug
              }
+             frontmatter {
+               template
+             }
            }
          }
        }
      ...
      return
    }

-   const posts = result.data.allMarkdownRemark.nodes
+   const nodes = result.data.allMarkdownRemark.nodes
+   const posts = nodes.filter(node => node.frontmatter.template === 'post')
+   const pages = nodes.filter(node => node.frontmatter.template === 'page')
  
    ...
    if (posts.length > 0) {
      posts.forEach((post, index) => {
        const previousPostId = index === 0 ? null : posts[index - 1].id
        const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id
        createPage({
          path: post.fields.slug,
-         component: blogPost,
+         component: postTemplate,
          context: {
            id: post.id,
            previousPostId,
            nextPostId,
          },
        })
      })
    }
  
+   if (pages.length > 0) {
+     pages.forEach(page => {
+       createPage({
+         path: page.fields.slug,
+         component: pageTemplate,
+         context: {
+           id: page.id,
+         },
+       })
+     })
+   }
  }
  
  exports.onCreateNode = ({ node, actions, getNode }) => {
    const { createNodeField } = actions
  
    if (node.internal.type === `MarkdownRemark`) {
      const value = createFilePath({ node, getNode })
  
      createNodeField({
        name: `slug`,
        node,
        value,
      })
    }
  }
  
  exports.createSchemaCustomization = ({ actions }) => {
    const { createTypes } = actions
  
    ...
    createTypes(`
      ...
      type Frontmatter {
        title: String
        description: String
        date: Date @dateformat
+       template: String
      }
  
      type Fields {
        slug: String
      }
    `)
  }

4. メニュー表示用のコンポーネントを作成

とりあえず表示できれば良いので、雑にリストを作ります。

src/components/menu.js
import * as React from "react"
import { Link } from "gatsby"
import { useStaticQuery, graphql } from "gatsby"

const Menu = () => {
  const data = useStaticQuery(graphql`
    query MenuQuery {
      site {
        siteMetadata {
          menu {
            label
            path
          }
        }
      }
    }
  `)

  let menuItems = []

  data.site.siteMetadata.menu.forEach(menuItem => {
    menuItems.push(<li><Link to={menuItem.path}>{menuItem.label}</Link></li>)
  })
  return (
    <ul className="menu">
      {menuItems}
    </ul>
  )
}

export default Menu

5. トップページからリンクを繋げる

  • 個別ページへのリンクを繋げているだけの、 Menu コンポーネントを設置しています。

  • トップページの記事一覧には、frontmattertemplatepost のページのみを表示します。

src/pages/index.js
  import * as React from "react"
  import { Link, graphql } from "gatsby"
  
  import Bio from "../components/bio"
  import Layout from "../components/layout"
  import Seo from "../components/seo"
+ import Menu from "../components/menu"
  
  const BlogIndex = ({ data, location }) => {
    const siteTitle = data.site.siteMetadata?.title || `Title`
    ...
      return (
        <Layout location={location} title={siteTitle}>
          <Bio />
+         <Menu />
          <p>
            No blog posts found. Add markdown posts to "content/blog" (or the
            ...
    return (
      <Layout location={location} title={siteTitle}>
        <Bio />
+       <Menu />
        <ol style={{ listStyle: `none` }}>
          {posts.map(post => {
            const title = post.frontmatter.title || post.fields.slug
            ...
  export const pageQuery = graphql`
    query {
      site {
        siteMetadata {
          title
        }
      }
-     allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
+     allMarkdownRemark(
+       sort: {
+         fields: [frontmatter___date],
+         order: DESC
+       }
+       filter: {
+         frontmatter: {
+           template: {
+             eq: "post"
+           }
+         }
+       }
+     ) {
      ...
トップページ

個別ページ

できました!ヤッター

6. 参考 URL

7. おわりに

同じ感じでカテゴリーとかタグを追加できそうです!

にほんブログ村 IT技術ブログ IT技術メモへ