GatsbyjsでWordPress風の簡易ブログを作って勉強してみた

[Gatsbyjsのチュートリアル](https://www.gatsbyjs.org/tutorial/)がとてもわかりやすくて勉強になったので、
自分で内容を忘れないためにWordPressのデフォルトのテンプレートに近いものを作ってみました

また、私はGatsbyjsやWordPress初心者です、よろしくお願いいたします :bow:

---

作ったのはGitHub Pagesで見れます↓

https://okumurakengo.github.io/gatsby-blog-example/

![Screen Shot 2020-01-04 at 21.45.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/331c036f-5334-30f8-ac0a-3ebf656677a9.png)

---

私が作成したコードは以下です

<a href="https://github.com/okumurakengo/gatsby-blog-example"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/okumurakengo/gatsby-blog-example.png" width="460px"></a>


# 0. gatsbyの雛形を作成

```bash:bash
npx -p gatsby-cli gatsby new myblog https://github.com/gatsbyjs/gatsby-starter-hello-world
cd myblog
yarn develop
```

`http://localhost:8000/` で表示される

<img width="612" alt="Screen Shot 2020-01-04 at 22.38.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/eafbac6a-4f01-f4aa-f142-cb491b4db426.png">

# 1. 全体のスタイルを設定

```src/styles/global.css
body {
    font-family: Verdana, sans-serif;
    margin: 0 auto;
    width: 900px;
}

a {
    text-decoration: none;
}
```

`gatsby-browser.js`で読み込む 

```gatsby-browser.js
import "./src/styles/global.css"
```

※`gatsby-browser.js`などの設定関連のファイルは、変更するたびに<kbd>Ctrl+C</kbd>で開発サーバーを終了し、再度`yarn develop`で起動し直さないと反映されないようでした

# 2. ヘッダー、フッター、サイドバーを作成して共通化

`src/index.js`の内容を以下のように変更、(※拡張子も`jsx`に変更しました)

```src/pages/index.jsx
import React from "react"
import Layout from "../components/Layout"

export default () => (
  <Layout page={"top"}>
    <div>Hello world!</div>
  </Layout>
)
```

共通化したのがわかりやすいように、もう一つページを作成する

```src/pages/sample-page.jsx
import React from "react"
import Layout from "../components/Layout"

export default () => (
  <Layout>
    <h1>サンプルページ</h1>
    <p>
      これはサンプルページです。同じ位置に固定され、(多くのテーマでは)
      サイトナビゲーションメニューに含まれる点がブログ投稿とは異なります。まずは、サイト訪問者に対して自分のことを説明する自己紹介ページを作成するのが一般的です。たとえば以下のようなものです。
    </p>
    <blockquote class="wp-block-quote">
      <p>
        はじめまして。昼間はバイク便のメッセンジャーとして働いていますが、俳優志望でもあります。これは僕のサイトです。ロサンゼルスに住み、ジャックという名前のかわいい犬を飼っています。好きなものはピニャコラーダ、そして通り雨に濡れること。
      </p>
    </blockquote>
  </Layout>
)
```

ヘッダーで読み込む用にサイトのメタデータを設定しておく

```gatsby-config.js
module.exports = {
  siteMetadata: {
    title: "ホゲホゲブログ",
    description: "ホゲホゲによるブログです",
  },
}
```

全体のパーツを読み込むファイル読み込むファイル

```src/components/Layout.jsx
import React from "react"
import Header from "./Header"
import Main from "./Main"
import Sidebar from "./Sidebar"
import Footer from "./Footer"

export default ({ page = "default", children }) => (
  <>
    <Header page={page} />
    <Main sidebar={<Sidebar />}>{children}</Main>
    <Footer />
  </>
)
```

ヘッダーを表示するファイル。
GraphQLを使ってサイトのメタデータを取得します。

```src/components/Header.jsx
import React from "react"
import { useStaticQuery, Link, graphql } from "gatsby"
import styles from "../styles/header.module.css"

export default ({ page }) => {
  const {
    site: {
      siteMetadata: { title, description },
    },
  } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
          }
        }
      }
    `
  )

  return (
    <header>
      {page === "top" && <div className={styles.headerImg}>&nbsp;</div>}
      <div className={styles.headerBox}>
        <h1>
          <Link to="/">{title}</Link>
        </h1>
        <p>{description}</p>
      </div>
    </header>
  )
}
```

サイドバーとメインコンテンツを表示するファイル

```src/components/Main.jsx
import React from "react"
import styles from "../styles/main.module.css"

export default ({ children, sidebar }) => (
  <div className={styles.flex}>
    <div className={styles.mainLeft}>{children}</div>
    <div className={styles.mainRight}>{sidebar}</div>
  </div>
)
```

サイドバー表示、「最近の投稿」と「カテゴリー」の表示はあとで行います

```src/components/Sidebar.jsx
import React from "react"
import { Link } from "gatsby"

export default () => {
  return (
    <>
      <div>
        <h3>固定ページ</h3>
        <ul>
          <li>
            <Link to="/sample-page/">サンプルページ</Link>
          </li>
        </ul>
      </div>

      <div>
        <h3>最近の投稿</h3>
        <ul></ul>
      </div>

      <div>
        <h3>カテゴリー</h3>
        <ul></ul>
      </div>
    </>
  )
}
```

フッターを表示するファイル

```src/components/Footer.jsx
import React from "react"
import { Link } from "gatsby"

export default () => (
  <>
    <hr />
    <Link to="https://www.gatsbyjs.org/">Powered by Gatsbyjs</Link>
  </>
)
```

共通部分の作成までできました

![fcD99uOlTM.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/7446795c-dcea-a8ef-8df5-13849639bedc.gif)


# 3. トップページにブログ一覧表示

`blog`フォルダにマークダウンで内容を書いて、それを一覧表示します。
マークダウンの内容はこのような感じです。

```md:blog/hello-world.md
---
title: Hello World
date: "2017-08-21"
categories: []
---

Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。

Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。

Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。Hello World。
```

---

ローカルファイルのデータをGatsbyに渡せるプラグインと、
マークダウンを扱うプラグインをインストール

```bash
yarn add gatsby-source-filesystem gatsby-transformer-remark
```

プラグインを使えるように設定

```gatsby-config.js
module.exports = {
  siteMetadata: {
    title: "ホゲホゲブログ",
    description: "ホゲホゲによるブログです",
  },
  plugins: [
    {
      resolve: "gatsby-source-filesystem",
      options: {
        path: `${__dirname}/blog`,
        name: "blog",
      },
    },
    "gatsby-transformer-remark",
  ],
}
```

`gatsby-node.js` を作成して、`createNodeField` でslugを設定します、これでGraphQLからslugが取得できるようになります。

```gatsby-node.js
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,
    })
  }
}
```

↑詳しい説明はチュートリアルの、パート6、7あたりです。

- https://www.gatsbyjs.org/tutorial/part-six/
- https://www.gatsbyjs.org/tutorial/part-seven/

---

GraphQLで、日付でソートしつつ必要なデータを取得しました

```src/pages/index.jsx
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from "../components/Layout"
import styles from "../styles/index.module.css"

export default ({
  data: {
    allMarkdownRemark: { totalCount, edges: blogs },
  },
}) => (
  <Layout page={"top"}>
    <strong>投稿 ( {totalCount} ) </strong>
    {blogs.map(
      ({
        node: {
          id,
          frontmatter: { title, date },
          fields: { slug },
          excerpt,
        },
      }) => (
        <div key={id} className={styles.box}>
          <div className={styles.date}>{date}</div>
          <h2 className={styles.title}>
            <Link to={slug}>{title}</Link>
          </h2>
          <p>{excerpt}</p>
        </div>
      )
    )}
  </Layout>
)

export const query = graphql`
  query {
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "YYYY年MM月DD日")
          }
          fields {
            slug
          }
          excerpt
        }
      }
    }
  }
`
```

これによりトップページにブログの一覧表示はできましたが、まだ詳細表示するページを作成していないためアクセスしても404と表示されます。

![wcBbnWmC0G.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/22f7c64c-24bd-7057-dffb-8dd045093baa.gif)

# 4. ブログ詳細ページ作成

ブログページを表示するためのテンプレートを作成しておきます

```src/templates/blog-post.jsx
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default ({
  data: {
    markdownRemark: {
      html,
      frontmatter: { title, categories },
    },
  },
}) => {
  return (
    <Layout>
      <h1>{title}</h1>
      <>
        <strong>カテゴリー : </strong>
        {categories.length ? categories.join(", ") : "なし"}
      </>
      <div dangerouslySetInnerHTML={{ __html: html }} />
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
        categories
      }
    }
  }
`
```

`gatsby-node.js` を変更して、ブログページが作成されるようにします

```gatsby-node.js
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/blog-post.jsx"),
      context: {
        // GraphQLのクエリの $slug 変数に設定する値
        slug: node.fields.slug,
      },
    })
  })
}
```

ブログのページを確認できました

<img width="897" alt="Screen Shot 2020-01-05 at 0.10.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/99e1fae7-bc30-2e33-4a7e-d4f921583f91.png">


# 5. カテゴリーごとの一覧ページ作成

参考：https://www.gatsbyjs.org/docs/adding-tags-and-categories-to-blog-posts/

先ほどと同じようにテンプレートを作成します

```src/templates/categories.jsx
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from "../components/layout"

export default ({
  data: {
    allMarkdownRemark: { totalCount, edges: blogs },
  },
}) => {
  return (
    <Layout>
      {blogs.map(
        ({
          node: {
            id,
            html,
            frontmatter: { title },
            fields: { slug },
          },
        }) => (
          <div key={id}>
            <h2>
              <Link to={slug}>{title}</Link>
            </h2>
            <div dangerouslySetInnerHTML={{ __html: html }} />
            <hr />
          </div>
        )
      )}
    </Layout>
  )
}

export const pageQuery = graphql`
  query($category: String!) {
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { categories: { in: [$category] } } }
    ) {
      totalCount
      edges {
        node {
          id
          html
          frontmatter {
            title
          }
          fields {
            slug
          }
        }
      }
    }
  }
`
```

`gatsby-node.js`もブログの詳細ページを作成した時と同じようにします

```diff:gatsby-node.js
  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
              }
            }
          }
        }
+       categoriesAllMarkdownRemark: allMarkdownRemark {
+         group(field: frontmatter___categories) {
+           category: fieldValue
+         }
+       }
      }
    `)
    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      createPage({
        path: node.fields.slug,
        component: path.resolve("./src/templates/blog-post.jsx"),
        context: {
          // GraphQLのクエリの $slug 変数に設定する値
          slug: node.fields.slug,
        },
      })
    })
+   result.data.categoriesAllMarkdownRemark.group.forEach(({ category }) => {
+     createPage({
+       path: `/category/${category}/`,
+       component: path.resolve("./src/templates/categories.jsx"),
+       context: {
+         // GraphQLのクエリの $category 変数に設定する値
+         category,
+       },
+     })
+   })
  }
```

まだページ内からのリンクはないのですが、`http://localhost:8000/category/ブログ/`とurlを直打ちすると、カテゴリーに対応する投稿のみを表示することができました

<img width="964" alt="Screen Shot 2020-01-05 at 0.18.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/6569d1af-5aa0-bce2-da92-8c7a2ca2c7df.png">

# 6. サイドバーの情報を取得して表示

`Sidebar.jsx`をGraphQLからデータを取ってくるように変更

```src/components/Sidebar.jsx
import React from "react"
import { useStaticQuery, Link, graphql } from "gatsby"

export default () => {
  const {
    recentlyAllMarkdownRemark: { edges: recentlyBlogs },
    categoriesAllMarkdownRemark: { group: categories },
  } = useStaticQuery(
    graphql`
      query {
        recentlyAllMarkdownRemark: allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 5
        ) {
          edges {
            node {
              id
              frontmatter {
                title
              }
              fields {
                slug
              }
            }
          }
        }
        categoriesAllMarkdownRemark: allMarkdownRemark {
          group(field: frontmatter___categories) {
            category: fieldValue
            totalCount
          }
        }
      }
    `
  )

  return (
    <>
      <div>
        <h3>固定ページ</h3>
        <ul>
          <li>
            <Link to="/sample-page/">サンプルページ</Link>
          </li>
        </ul>
      </div>

      <div>
        <h3>最近の投稿</h3>
        <ul>
          {recentlyBlogs.map(
            ({
              node: {
                id,
                frontmatter: { title },
                fields: { slug },
              },
            }) => (
              <li key={id}>
                <Link to={slug}>{title}</Link>
              </li>
            )
          )}
        </ul>
      </div>

      <div>
        <h3>カテゴリー</h3>
        <ul>
          {categories.map(({ category, totalCount }) => (
            <li key={category}>
              <Link to={`category/${category}`}>{category}</Link> ( {totalCount}{" "}
              )
            </li>
          ))}
        </ul>
      </div>
    </>
  )
}
```

サイドバーも表示することができました

<img width="965" alt="Screen Shot 2020-01-05 at 0.20.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/dd2b470e-dcf3-ea53-dd2a-0817a0864be6.png">


# 7. サイトメタデータ

チュートリアルのパート8参考
https://www.gatsbyjs.org/tutorial/part-eight/

サイトの`meta`タグや`title`、`description`を設定します

`react-helmet`を使うのでインストール

```bash
yarn add gatsby-plugin-react-helmet react-helmet
```

```diff:gatsby-config.js
  module.exports = {
    siteMetadata: {
      title: "ホゲホゲブログ",
      description: "ホゲホゲによるブログです",
    },
    plugins: [
      {
        resolve: "gatsby-source-filesystem",
        options: {
          path: `${__dirname}/blog`,
          name: "blog",
        },
      },
      "gatsby-transformer-remark",
+     "gatsby-plugin-react-helmet",
    ],
  }
```

```diff:src/components/Header.jsx
  import React from "react"
+ import Helmet from "react-helmet"
  import { useStaticQuery, Link, graphql } from "gatsby"
  import styles from "../styles/header.module.css"

  export default ({ page }) => {
    const {
      site: {
        siteMetadata: { title, description },
      },
    } = useStaticQuery(
      graphql`
        query {
          site {
            siteMetadata {
              title
              description
            }
          }
        }
      `
    )

    return (
+     <>
+       <Helmet
+         htmlAttributes={{ lang: "ja" }}
+         title={title}
+         meta={[
+           {
+             name: "description",
+             content: description,
+           },
+         ]}
+       />
        <header>
          {page === "top" && <div className={styles.headerImg}>&nbsp;</div>}
          <div className={styles.headerBox}>
            <h1>
              <Link to="/">{title}</Link>
            </h1>
            <p>{description}</p>
          </div>
        </header>
+     </>
    )
  }
```

タイトルタグや、metaタグが設定できました。

<img width="842" alt="Screen Shot 2020-01-05 at 0.32.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/d7d5da35-cd5b-079e-c654-9fe1cd138e05.png">

---

最後まで読んでいただいてありがとうございました。m(_ _)m