📔 ブログをGitHub Pagesに移管する話

投稿 🕒: December 29, 2022 / 更新 🕒: December 30, 2022
Tags: 📔 diary🏷️ blog構築🏷️ Gatsby

ブログを Hatena から Gatsby の GitHub Pages ホスティングに移行しました。

以下は以降にあたり書き残した雑な技術メモです。


要件

  • 記事を GitHub 上で管理できる
  • GitHub Pages でホストする
  • Blog 記事とホスト用のコードは別の GitHub リポジトリを分離する

技術選定

できる限り労力をかけずにブログを構築できる、かつ記事が多いというところ、また色々使っている人が多かったGatsbyを選択。(React を触ったことがあるので、 というのも一つではあるが。)

セットアップ

環境情報

node -v
# v18.12.1

インストール

まずは、gatsby-cliを導入し、gatsby を利用できるようにする。

npm install -g gatsby-cli

Blog テンプレートの取得と local での起動

gatsby new blog https://github.com/gatsbyjs/gatsby-starter-blog
cd blog
gatsby develop

gatsby developを実行後 http://localhost:8000で起動する。 画面上では元から用意されているサンプル記事が表示される。

Build と GitHub Pages へのホスト

Build と GitHub Pages へのホストは、Kaname Sasaki 氏のブログ記事を参考にする。

Google Analytics の導入

もともと Hatena でブログを書いており、アクセス解析等がデフォルトでついてることが普通だった。

ただ、自身でホストするとなると、常にそのような機能があるわけではなく、自身で用意する必要がある。そこで、gatsby-plugin-google-gtagを用いる。

インストールは下記のコマンドでできる。

npm install gatsby-plugin-google-gtag

次に Google Analytics のアカウントを作成し、gatsby-plugin-google-gtagの設定に追加する。

gatsby-config.js

module.exports = {
  plugins: [
    // ~~snip~~
    {
      resolve: `gatsby-plugin-google-gtag`,
      options: {
        trackingIds: ["G-XXXXXXXXXX"], // Google Analytics のIDを追加
        pluginConfig: {
          head: true, // headタグに追加する
        },
      },
    },
    // ~~snip~~
  ],
};

目次の追加

ブログを書くと大体長くなるので目次をいい感じに追加してくれるプラグインを追加している。

npm install gatsby-remark-table-of-contents
npm install gatsby-remark-autolink-headers

gatsby-config.jsに先に追加したプラグインの設定を追加する。

// ~~ snip ~~
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          `gatsby-remark-autolink-headers`,
          {
            resolve: `gatsby-remark-table-of-contents`,
            options: {
              exclude: "Table of Contents",
              tight: false,
              ordered: false,
              fromHeading: 1,
              toHeading: 6,
              className: "table-of-contents",
            },
          },
// ~~ snip ~~

表示用のコンポーネントを作成します。

import React from "react";

const TableOfContents = ({ html }) => {
  return <div class="toc" dangerouslySetInnerHTML={{ __html: html }} />;
};
export default TableOfContents;

最後にsrc/templates/blog-post.jsにテーブルを表示するコンポーネントを追加しておしまいです。

src/templates/blog-post.js

import TableOfContents from "../components/tableOfContents"

const BlogPostTemplate = ({
  data: { previous, next, site, markdownRemark: post },
  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">{post.frontmatter.title}</h1>
          <p>投稿: {post.frontmatter.createDate}</p>
          <p>Tags: {post.frontmatter.tags.map(tag => {
            return (<Link class="taglink" to={`/tags/${tag}`}>{tag}</Link>)
          })}</p>
        </header>
        <TableOfContents html={post.tableOfContents} />
// ~~ snip ~~

Tag の追加

front matter によるタグと Gatsby のタグ

マークダウンのメタ情報を記述する front matter のタグを元にホストするブログのタグを用意する。

例えば下記の 1 のような情報を front matter に記載していたとして、下記の 2 のような graphql のクエリを Gatsby のページから発行することで、下記の 3 のようにタグを取得することができる。

1. front matter によるメタ情報の記述

---
title: 一年を振り返って
author: Azara / @a_zara_n
tags: ["blog"]
createDate: 2022-12-31 18:04
---

2. Gatsby のページ生成時に Graphql で情報の取得をする

{
  markdownRemark {
    id
    excerpt(pruneLength: 160)
    frontmatter {
      title
      tags
    }
  }
}

3. 取得される情報

{
  "data": {
    "markdownRemark": {
      "id": "419c83a0-b0e2-5d94-97d3-2d2d69e7491f",
      "excerpt": "年末の話 tags: #blog #振り返り はじめに 今年を振り返り 入社して1年 未熟さの実感 一念発起 ビールって美味しい 食わず嫌いをなくそうキャンペーン おわりに --–-",
      "frontmatter": {
        "title": "一年を振り返って",
        "tags": ["blog"]
      }
    }
  },
  "extensions": {}
}

タグを貼る

記事にタグを貼ることを考えた際に、次の三点を満たすことを前提に実装を進める。

  1. タグがつけられた記事の一覧を表示する
  2. トップページに Tag を表示する
  3. 最小限で実装したいので、多くなった場合のことはこの際考慮しない
  4. 記事か 1 のページに遷移できる

タグに紐づけられた記事一覧ページ

タグに紐づけられた記事一覧ページを実装するにあたり、2 つのファイルを編集する。1 つは既存のgatsby-node.js、もう 1 つは作成するsrc/templates/tag.jsのファイルになる。

src/templates/tag.jsは記事一覧の描画を担当するファイルで、下記のような実装をした。

import React from "react";
import PropTypes from "prop-types";

// Components
import { Link, graphql } from "gatsby";
import Layout from "../components/layout";

const Tags = ({ pageContext, data }) => {
  const siteTitle = data.site.siteMetadata?.title || `Title`;
  const { tag } = pageContext;
  const { edges, totalCount } = data.allMarkdownRemark;
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? "" : "s"
  } tagged with "${tag}"`;
  return (
    <Layout location title={siteTitle}>
      <article
        className="blog-post"
        itemScope
        itemType="http://schema.org/Article"
      >
        <header>
          <h1 itemProp="headline">{tagHeader}</h1>
        </header>
        <ol style={{ listStyle: `none` }}>
          {edges.map(({ node }) => {
            const { slug } = node.fields;
            const { title } = node.frontmatter;
            return (
              <li key={slug}>
                <article
                  className="post-list-item"
                  itemScope
                  itemType="http://schema.org/Article"
                >
                  <header>
                    <h2>
                      <Link to={slug} itemProp="url">
                        <span itemProp="headline">{title}</span>
                      </Link>
                    </h2>
                  </header>
                  <section>
                    <p
                      dangerouslySetInnerHTML={{
                        __html: node.excerpt || "aaaa",
                      }}
                      itemProp="description"
                    />
                  </section>
                </article>
              </li>
            );
          })}
          <Link to="/tags">All tags</Link>
        </ol>
      </article>
    </Layout>
  );
};

Tags.propTypes = {
  pageContext: PropTypes.shape({
    tag: PropTypes.string.isRequired,
  }),
  data: PropTypes.shape({
    allMarkdownRemark: PropTypes.shape({
      totalCount: PropTypes.number.isRequired,
      edges: PropTypes.arrayOf(
        PropTypes.shape({
          node: PropTypes.shape({
            frontmatter: PropTypes.shape({
              title: PropTypes.string.isRequired,
            }),
            fields: PropTypes.shape({
              slug: PropTypes.string.isRequired,
            }),
          }),
        }).isRequired
      ),
    }),
  }),
};

export default Tags;

export const pageQuery = graphql`
  query ($tag: String) {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(
      limit: 2000
      sort: { frontmatter: { date: ASC } }
      filter: { frontmatter: { tags: { in: [$tag] } } }
    ) {
      totalCount
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
          }
          excerpt
        }
      }
    }
  }
`;

gatsby-node.jsは、先に作ったテンプレートを展開し、専用のページを作成に関連するコードを書いていく。

// ~~snip~~
const tagPage = path.resolve(`./src/templates/tag.js`);

/**
 * @type {import('gatsby').GatsbyNode['createPages']}
 */
exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions;

  // Get all markdown blog posts sorted by date
  const result = await graphql(`
    {
      allMarkdownRemark(sort: { frontmatter: { date: ASC } }, limit: 1000) {
        nodes {
          id
          fields {
            slug
          }
          frontmatter {
            tags
          }
        }
      }
      tagsGroup: allMarkdownRemark(limit: 1000) {
        group(field: { frontmatter: { tags: SELECT } }) {
          fieldValue
        }
      }
    }
  `);
  // ~~snip~~
  const tags = result.data.tagsGroup.group;

  tags.forEach((tag) => {
    createPage({
      path: `/tags/${tag.fieldValue}/`,
      component: tagPage,
      context: {
        tag: tag.fieldValue,
      },
    });
  });
};

// ~~snip~~

記事に Tag を表示する

記事に付与されたタグを表示し、関連記事の一覧ページに移動できるようにする。 src/templates/blog-post.js

// ~~snip~~
<p>
  Tags:{" "}
  {post.frontmatter.tags.map((tag) => {
    return (
      <Link class="taglink" to={`/tags/${tag}`}>
        {tag}
      </Link>
    );
  })}
</p>
// ~~snip~~

GitHub Pages へのデプロイ

GitHub Pages へのデプロイは Blog 側のリポジトリで main ブランチへの push があった際に GitHub actions を動作させ、一連の動作ののちに、gh-pagesを用いて GitHub Pages へデプロイしています。

インストール

npm install gh-pages

参考


Obsidian Tags : #blog #Gatsby


Tags: 📔 diary🏷️ blog構築🏷️ Gatsby
Profile picture

Written by azara / Japanese Security Engineer