GatsbyJsでgatsby-starter-blogを使ったサイトの作成

2020.11.25

💻
TECH

GatsbyJSとTypeScriptでブログを作成して公開する(2)

~ GatsbyJsでgatsby-starter-blogを使ったサイトの作成 ~


今回は実際にGatsby CLIを使って、サイトを生成してみたいと思います。

※環境構築については、前回の投稿をご覧ください。



Gatsby CLI で新規のサイトを作成する

今回はgatsby-starter-blogというスターター(後述)を使用します。

Macならターミナルを、WindowsならPower Shell(管理者権限) を開いて、


    $ gatsby new my-blog-starter https://github.com/gatsbyjs/gatsby-starter-blog 

※これ以降は、Windows・Macにかかわらず、上記の$マークでコマンドの開始を表現します。

※Windowsではおそらく管理者権限で起動が良いかと思います。 通常のPowerShellで実行した場合に、開発サーバー起動のコマンドが失敗することがありました。


コマンドの意味ですが、

gatsby new [任意のプロジェクト名] [StarterのURL]

という形式になっており

  • new・・・新しいプロジェクトを作成するコマンド

  • [任意のプロジェクト名]・・・好きな名前を指定します。この名前で新規フォルダーが作成されます。(my-blog-starter)

  • [StarterのURL]・・・スターターのURL、指定しなくても良い



スターターとは

公式では、

Starters are boilerplate Gatsby sites maintained officially, or by the community.

と紹介されています。

公式や、コミュニティーが開発した、テンプレートのようなもので、

これを利用することで、あらかじめ必要な設定や、ソースコードのテンプレートなどが含まれた状態で、プロジェクトを生成することができます。

一から自分で必要なライブラリや依存関係を調べてインストールしていくのは骨のを折れる作業です。


自分の目的に合ったスターターを見つけて指定すれば、これらを一発でやってくれるので相当な時間短縮になります。

また、それらのライブラリを使ってある程度のコードが書かれていますので、 個人的にはライブラリの使い方の勉強にもなりました。


スターターはGatsbyのサイトで探すことができます。

https://www.gatsbyjs.com/starters/?v=2

2020/11/22 時点で456あるようです。


解説が長くなりましたが、上で実行したコマンドで、my-blog-starterというフォルダが作成されているはずです。



Development Serverを立ち上げて、ローカルでブログを確認する

次に、先ほど作成したプロジェクトを起動して確認してみます。

まず、プロジェクトのルートディレクトリに移動します


   $ cd my-blog-starter

そして、サーバーを立ち上げます


    $ gatsby develop

ブラウザを開いて、http://localhost:8000にアクセスすると、

画像のようなサイトが立ち上がっているのが確認できます。

It's your first blog powered by GatsbyJS

初めてのGatsbyサイトができました!



記事を追加してみる

gatsby-blog-starterには、Markdownから記事を生成するためのプラグインがデフォルトで入っています。

デフォルトで表示されている記事もMarkdownから生成されています。

以下のディレクトリに入っているのがそれですね。


> content
    > assets
    > blog
        > hello-world
            index.md
            salty_egg.jpg
        > my-second-post
            index.md
        > new-begginings
            index.md

試しに、一つ追加してみます。

content/blog配下にtest-postというファイルを作って、

そこにindex.md というファイルを新規作成します。


> content
    > assets
    > blog
        ...
        > test-post
            index.md

index.mdは以下のようにします

---
title: Test Post
date: "2112-09-03"
description: "This is a test post."
---

# これはテストです!

テストテスト

ファイルを保存して、先ほど開いていたhttp://localhost:8000をみると、

記事の一覧に一つ記事が増えていることが確認できると思います。

追加した記事

追加された記事はリンクになっていますので、クリックしてみると内容が確認できます。

追加した画像2

記事を追加してからgatsby develoopコマンドを実行しなおす必要もありません。

Gatsby はホットリロードの機能がついており、記事だけでなく

React.jsで実装するその他のデザインなどもすぐに反映されます。

といっても調べると様々な理由で結局コマンド叩きなおすことが多いみたいですが。。。

ともあれGatsbyJS はこういった開発を効率よく行う機能が簡単に使えるのがいいところです。



Markdownから記事が生成される仕組み

GatsbyJSにおいて、動的にページを生成する場合に用いられるのが、

gatsby-node.js ファイルです。

こちらはビルドプロセスで呼ばれるAPIになっており、

動的なページ生成、GraphQLへのノードの追加、ビルドライフサイクル中のイベントをキャッチして処理を行ったりといったことが可能になります。


新しいページを作成するには2つのステップがあります。

  1. ページのパス(slug)をつくる
  2. ページそのものを生成する

という工程を踏む必要があり、これを行っているのがgatsby-node.jsということです。


ちなみにslugというのはウェブのアドレスの一部で、例えば、

https://kohsuk.tech/posts/no-oneというアドレスがあった場合

/posts/no-oneの部分のことをさします。

ページそのものと、そこに到達するためのURLの生成が必要というイメージです。



onCreateNodecreatePage

gatsby-node.js内でこれを行っているのが、onCreateNodecreatePageです。

前者から見ていきます。

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    const value = createFilePath({ node, getNode })

    createNodeField({
      name: `slug`,
      node,
      value,
    })
  }
}

Gatsbyは追加された外部のデータをnodeとして扱います。

onCreateNodeはこのnodeが作られた際に呼ばれる関数です。

MarkdownRemarkという文字列が見えますね。

Markdownファイルは、gatsby-transformer-remarkというプラグインがパースし、

nodeにそのデータを追加してくれています。

MarkdownRemarkはこのプラグインで作られたデータだということです。

このnodeからファイル名を取得し、それを利用してslugを生成、nodeのfieldとして追加、

というのがここでの処理です(createNodeField)。


続いて、createPageです。

exports.createPages = async ({ graphql, actions, reporter }) => {
  const { createPage } = actions

  // Define a template for blog post
  const blogPost = path.resolve(`./src/templates/blog-post.js`)

  // Get all markdown blog posts sorted by date
  const result = await graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          nodes {
            id
            fields {
              slug
            }
          }
        }
      }
    `
  )

  if (result.errors) {
    reporter.panicOnBuild(
      `There was an error loading your blog posts`,
      result.errors
    )
    return
  }

  const posts = result.data.allMarkdownRemark.nodes

  // Create blog posts pages
  // But only if there's at least one markdown file found at "content/blog" (defined in gatsby-config.js)
  // `context` is available in the template as a prop and as a variable in GraphQL

  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,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })
    })
  }
}

ここでは主に2つのことが行われています。

  1. GraphQLでマークダウンの一覧を取得する
  2. 取得した一覧からページを生成する

ここで注目すべきは、先ほどnodeのfieldに追加したslugをGraphQLで取得していることです。

このようにnodeに追加したデータはGraphQLでアクセスすることができます。



ページ全体の生成

マークダウンを拾っている部分は何となくわかりましたが、

マークダウンは記事の中身であって、その他のヘッダーやフッターなどは別に定義しているはずです。

createPageする際にこれに使うコンポーネントのパスが指定されていますが、

これが、ページを作る際のテンプレートになっています。

createPagesの関数内で上部で指定されている./src/templates/blog-post.jsですね。


このblog-post.jsファイルには、Reactのコンポーネント、GraphQLが定義されています。

ファイル下部で定義されたGraphQLのクエリをよく見てみますと、

引数として、id, previousPostId, nextPostIdを受け取ることがわかります。


export const pageQuery = graphql`
  query BlogPostBySlug(
    $id: String!
    $previousPostId: String
    $nextPostId: String
  ) {
    ...

お気づきかもしれませんが、これは先ほどのgatsby-node.jsのcreatePageでcontextとして指定されています。

      createPage({
        path: post.fields.slug,
        component: blogPost,
        context: {
          id: post.id,
          previousPostId,
          nextPostId,
        },
      })

つまり、実際のページ全体の生成は、

createPageが、①ページのパス、ページに使用する②コンポーネントのパス、そのページで使う③GraphQLの引数を指定して呼び出され、

実際の記事データは、blog-postで引数として渡されたidからGraphQLで取得,

そのデータをコンポーネントに注入してレンダリング、というようになっているんですね!


今回はここまでです。

これからも精進して行きます!


KOHSUK

I'm a Web Developer👑 who shares tips on Tech, Productivity, Design and MORE!

Code snippets licensed under MIT, unless otherwise noted.

Content & Graphics © 2022, KOHSUK