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 で取得,

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

今回はここまでです。

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


Homeへ戻る
profile picture

Kosuke Kihara

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

Kohsuk11KOHSUKkohsuk.tech@gmail.com