GatsbyでGoogle Lighthouseで満点を取るブログを一から作る

## 概要

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/375274/8dbd14b7-869f-dec6-d9b0-7edea08a4445.png)

Gatsbyは、React.jsを使ったStatic Site Generator(SSG、静的サイトジェネレーター)で、とても早いウェブサイトを作れる技術です。静的なサイトだけでなく、CSRやSSRサイトも作れますが、この記事では静的サイトに集中しておきたいと思います。Gatsbyが使用するGraphQLについても扱います。

### Google Lighthouse Score
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/375274/9435ae8b-21af-2a4f-2f35-4b5727f57fe7.png)
`学生時代には取れなかったオール100点`

完成品は<strong><a href="https://gatsby-starter-noodles.netlify.com/">こちら</a></strong>
コードは<strong><a href="https://github.com/so99ynoodles/gatsby-starter-noodles/">こちら</a></strong>


### 対象読者
- Gatsbyがどう動くか知りたい方
- Reactの基本的知識がある方
- ブログをScratchで作りたい方

### 目標
- Gatsbyの構造を知ることによって、色んなライブラリーを応用できるようになる

### 注意事項
- 完成品の基本的構造にふれるだけなので、上記の例と全く同じものが作られる記事ではありません。
- Gatsbyのことがよくわかってて、開発時間を下げたい方は<a href="https://www.gatsbyjs.org/starters/">Gatsby Starter</a>を使用することをおすすめします。


```
npm i -g gatsby-cli
gatsby new [project-name] [starter]
```

## 目次
1. インストールと環境設定
2. レイアウトを作る
3. GraphQLでQueryを使う
4. Markdownファイルを扱う
5. Postページを生成する
6. タグを生成する
7. Paginationを行う
8. イメージを使う
9. スタイリング
10. SEO、PWA設定
11. デプロイする
12. 最後に

## インストールと環境設定
GatsbyをScratchから作るので、空のfolderからスタートします。
Gatsbyには大きく３種類のpluginが存在します。

- <strong>Transformer Plugin</strong>
    - JSONやMarkdownといった、そのままでは使えないデータを、GraphQLでqueryできる形に変換してくれます。ここでは、Markdownをhtmlに変換してくれる`gatsby-transformer-remark`と、MDXをhtmlに変換してくれる`gatsby-plugin-mdx`を使います。
- <strong>Functional Plugin</strong>
    - TypeScriptサポートやPWA設定など、機能的な要素を追加してくれます。ここではリンクをリロードなしに開いてくれることでSPAの感覚を与えてくれる`gatsby-plugin-catch-links`等を使います。
- <strong>Source Plugin</strong>
    - Gatsbyのデータシステムの核心である`nodes`を作ってくれます。例えば`gatsby-source-filesystem`はディスクからファイルをロードして、Transformer pluginがデータを変換できるようにします。ここでは使用しませんが、wordpressやcontentfulなどのCMSから`nodes`を作成することもできます(`gatsby-source-wordpress`, `gatsby-source-contentful`)

まず、npm(またはyarn)を使ってdependenciesをインストールしていきます。

```console
mkdir [project-name]
cd [project-name]
npm init -y
npm i react react-dom gatsby gatsby-source-filesystem gatsby-transformer-remark gatsby-plugin-catch-links
```

そうしたら、`gatsby-config.js`を作成し、
自分が作りたいサイトのmeta情報を書き込みましょう。

``` gatsby-config.js
module.exports = {
  siteMetadata: {
    title:'My Wonderful Website',
    description: 'Welcome to your brilliant website.',
    author: 'Super Cool Developer'
  },
}
```

## レイアウトを作る

レイアウトは私達が作るblogの土台になる部分です。
例えばすべてのページに共通して現れるHeader, Footer, Sidebar等が該当します。

```src/layouts/index.jsx
import React from 'react'

export default ({ children }) => (
  <div>
    <h3>My Wonderful Website Layout</h3>
    {children}
  </div>
)
```

そしてホームの画面とAboutの画面を作っていきます。
Gatsbyのsrc/pagesの中のファイルは、各々一つのページとして作成されます。
例えば、`src/pages/about.jsx`というファイルがあると、`/about`が作られます。

```src/pages/index.jsx
import React from 'react'
import Layout from '../layouts'

export default () => (
  <Layout>
    <h1>My Wonderful Website Home Page</h1>
    <p>This is the home page</p>
  </Layout>
)
```

```src/pages/about.jsx
import React from 'react'
import Layout from '../layouts'

export default () => (
  <Layout>
    <h1>My Wonderful Website About</h1>
    <p>This is the about page.</p>
  </Layout>
)
```

ここで、`gatsby develop`コマンドを実行してみましょう。
`http://localhost:8000/`
これだと寂しいので、先程作ったLayoutにリンクを足してみましょう。

```src/layouts/index.jsx
import React from 'react'
import { Link } from 'gatsby'

export default ({ children }) => (
  <div>
    <Link to="/">
      <h3>My Layout</h3>
    </Link>
    <Link to="/about">
      About
    </Link>
    {children}
  </div>
)
```

Aboutページにも移動できました。
試しに`http://localhost:8000/not-found`など、存在しないページに移動すると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/375274/c8413539-6bca-f289-f7cc-4311cdb2d5ad.png)

このようにdevelopモードでは存在するページの一覧を表示してくれます。


## GraphQLでqueryを使う

Gatsbyはデータをfetchする際にGraphQLを使用します。
GraphQLはデータにアクセスするためのQuery言語です。他のQuery言語の例としてはSQLがあります。
GraphQLは自分がほしいデータのみをもらうことができます。

### GraphiQLを使う

まずはGraphiQLというツールを使ってqueryを実行してみましょう。

`http://localhost:8000/__graphql`に移動してみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/375274/cd727682-b27b-aa14-37ad-f93fa07e37c4.png)

GraphiQLのplaygroundが出てきます。
`Explorer`をクリックしたら、fetchできるqueryの情報が出てきます。
`Docs`をクリックしたら、queryに必要なInputの情報や返ってくるデータの情報が見れます。

ここで私達が先程設定したサイトのデータをfetchしてみましょう。


```graphql
{
  site {
    siteMetadata {
      title
      description
      author
    }
  }
}
```

そうしたら、下の結果が見れるはずです。

```JSON
{
  "data": {
    "site": {
      "siteMetadata": {
        "title": "My Wonderful Website",
        "description": "Welcome to your brilliant website.",
        "author": "Super Cool Developer"
      }
    }
  }
}
```

### コンポーネント内でGraphQLを使う

現在homeとaboutには各々サイトのタイトル、`My Wonderful Website`が入ってますが、
この書き方だとタイトルを修正したい時、毎回両方のページを修正する必要があります。
なので、`gatsby-config.js`に書かれている情報をそのまま持ってきて使えるようにしましょう。

#### Page Query
`src/pages`内のコンポーネントでは、以下のようにqueryを実行します。

```js

graphql`
{クエリ}
`
```

違和感を感じるかもしれませんが、ちゃんとしたJavaScriptです。

```src/pages/about.jsx
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../layouts'

export default ({ data }) => (
  <Layout>
    <h1>{data.site.siteMetadata.title} About</h1>
    <p>This is the about page.</p>
  </Layout>
)

export const query = graphql`
  query AboutQuery {
    site {
      siteMetadata {
        title
      }
    }
  }
`
```

`/about`ページに行くと、titleがしっかり入ってるのがわかります。


#### StaticQuery
ページではないコンポーネントでは、StaticQueryを使用します。
先程作成したLayoutで使ってみましょう。
まず`graphql`と`StaticQuery`を`gatsby`からインポートします。

`StaticQuery`は`query`と`render`プロパティを受け取ります。
`render`はpropとしてqueryを実行した結果の`data`を返します。

```src/layouts/index.jsx
import React from 'react'
import { Link, graphql, StaticQuery } from 'gatsby'

export default ({ children }) => (
  <StaticQuery
    query={graphql`
      query {
        site {
          siteMetadata {
            title
          }
        }
      }
    `}
    render={data => (
      <div>
        <Link to="/">
          <h3>{data.site.siteMetadata.title} Layout</h3>
        </Link>
        <Link to="/about">
          About
        </Link>
        {children}
      </div>
    )}
  />
)
```

## Markdownファイルを扱う
形はできてきたので、コンテンツを作ってみましょう。
ルートフォルダーに`posts`フォルダーを作成し、その中に`[ポスト名]`のフォルダーを作成してください。
その中に`index.md`ファイルを作ります。
私はポスト名を`post-one`にしました。

```markdown:posts/post-one/index.md
---
path: '/post-one'
date: '2019-08-18'
title: 'My First Post'
tags: ['gatsby', 'qiita']
---

This is my beautiful first post.

```


### ファイルを読み込む(pluginの設定)
マークダウンファイルをGatsbyが読み込むためにはプラグインが必要です。
一番最初にインストールしたプラグインを`gatsby-config.js`に書き込み、設定をしていきます。

```gatsby-config.js
module.exports = {
  siteMetadata: {
    title: 'My Wonderful Website',
    description: 'Welcome to your brilliant website.',
    author: 'Cool Developer'
  },
  plugins: [
    'gatsby-plugin-catch-links',
    'gatsby-transformer-remark',
      {
        resolve: 'gatsby-source-filesystem',
        options: {
          name: 'posts',
          path: `${__dirname}/posts`
        }
      }
    ]
}
```

pathは自分の`*.md`ファイルが存在するところである必要があります。
準備はできたのでGraphiQLに戻ります。

### GraphiQLで確認する
`GraphiQL-Explorer`から、`allMarkdownRemark`を選び、すべてのフィールドを確認してみてください。
`edges`はファイルパスを意味し、その中にある`node`は先程作成したmdファイルを意味します。

以下のqueryを実行してみましょう。

```graphql
{
  allMarkdownRemark {
    edges {
      node {
        frontmatter
      }
    }
  }
}

```

実行結果

```JSON
{
  "data": {
    "allMarkdownRemark": {
      "edges": [
        {
          "node": {
            "frontmatter": {
              "title": "My First Post",
              "path": "/post-one",
              "date": "2019-08-18"
            }
          }
        }
      ]
    }
  }
}
```

`frontmatter`の中に先程作成したmdファイルの中身が入ってくるのがわかります。
`gatsby-transformer-remark`プラグインが働いてくれました。
このデータを自由に使ってポストページを作っていきます。

### ポストリストをHomeに表示する
余裕のある方は、このセクションは自分の力でやってみてください！
page-queryを使用してhomeにデータが渡ってきたら、それをReact方式に表示するだけです。

`node`の中に`excerpt`は、コンテンツの内容を前の部分だけ切り取って表示してくれます。
私は`pruneLength`を使って、先頭100文字だけを表示するようにします。(defaultは140)

```src/pages/index.jsx
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../layouts'

export default ({ data }) => {
  const { edges } = data.allMarkdownRemark
  return (
  <Layout>
    <h1>Gatsby Tutorial Home Page</h1>
      {edges.map(({ node }) => (
        <div key={node.id}>
          <h3>{node.frontmatter.title}</h3>
          <p>{node.frontmatter.date}</p>
          <p>{node.excerpt}</p>
        </div>
    ))}
  </Layout>
)}

export const query = graphql`
  query {
    allMarkdownRemark {
      edges {
        node {
          id
          excerpt(pruneLength: 100)
          frontmatter {
            title
            date
          }
        }
      }
    }
  }
`
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/375274/f517f914-0899-c9db-8596-79b2141f01a3.png)

ポストがちゃんと表示されました!


### ソート
`allMarkdownRemark`は`sort`・`filter`・`skip`・`limit`の４つのプロパティを受け取れます。
`skip`と`limit`はPaginationの時に有用な項目ですが、ここでは`sort`と`filter`を扱います。

`sort`では`order`と`fields`を設定できます。
新しいポストが上に来てほしいので、`order`は`DESC`にし、
fieldsは`formatmatter___date`に設定します。

また、dateも日本語表記にしたいので、`formatString`を`YYYY年MM月DD日`に指定しましょう。

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

```

### フィルタリング
ブログを書くと下書き状態のポストもあるかと思いますが、
今のままではすべてのポストが公開されてしまいます。

なので作成したポストに`status`を追加しましょう。

```markdown:posts/post-one/index.md
---
path: '/post-one'
date: '2019-08-18'
title: 'My First Post'
tags: ['gatsby', 'qiita']
status: 'published'
---

This is my beautiful first post.
```

```markdown:posts/post-two/index.md
---
path: '/post-two'
date: '2019-08-19'
title: 'My Second Post'
tags: ['gatsby', 'qiita']
status: 'draft'
---

This is my magnificent second post.
```

では、`status`が`published`のポストのみをfetchするqueryを書きましょう。

```graphql
{
  allMarkdownRemark(
    filter: { 
      frontmatter: { 
        status: { eq: "published" } 
      } 
    }) {
    edges {
      node {
        id
        excerpt
        frontmatter {
          title
          date
        }
      }
    }
  }
}

```
これで`published`状態のポストしか見えないようになりました。

## Postページを生成する
ポストのリストが表示されたので、中身が見れるように各々のページを生成していきます。
まず、入り口を作るために、`Link`を設定しましょう。

```src/pages/index.jsx
import { graphql, Link } from 'gatsby'

...
    <Link to={node.frontmatter.path}>
      <h3>{node.frontmatter.title}</h3>
    </Link>
...
```

queryにも`path`を追加します。

```
...
    frontmatter {
      title
      date(formatString: "YYYY年MM月DD日")
      path
    }
...

```

ポストページを生成する処理を書く`gatsby-node.js`をルートディレクトリに作成します。
また、ポストの詳細を表示するテンプレート、`src/templates/post.jsx`を作成します。

```src/templates/post.jsx
import React from 'react'

const Post = (props) => {
  console.log('後で消す', props)
  return (
    <div>
      This is a post
    </div>
  )
}

export default Post
```

次に`gatsby-node.js`ではGatsbyのAPIの一つである`createPages`を使用します。
まずは、Pageを作成する関数を書いていきましょう。

`path.resolve`を使い、先程作成したpostのテンプレートを取得、
queryを実行してGatsbyが用意してくれるaction関数の`createPage`を実行していきます。

```gatsby-node.js
const path = require('path')

exports.createPages = (({graphql, actions}) => {
  const { createPage } = actions
  return new Promise((resolve, reject) => {
    const postTemplate = path.resolve('src/templates/post.jsx')

    resolve(
      graphql(
        `
          query {
            allMarkdownRemark {
              edges {
                node {
                  frontmatter {
                    path
                    title
                  }
                }
              }
            }
          }
        `
      ).then(result => {
        if (result.errors) {
          return Promise.reject(result.errors)
        }

        const posts = result.data.allMarkdownRemark.edges;

        posts.forEach(({node}) => {
          const path = node.frontmatter.path

          createPage({
            path,
            component: postTemplate,
            context: {
              pathSlug: path
            }
          })
        })
      })
    )
  })
})
```

長い関数ですね。お疲れさまです！
では、サーバーを再起動して、postページに行ってみてください。
template通りのページが作成されたと思います。

デベロッパーコンソールを開いて、`console.log`を確認してみてください。
`pageContext`が渡ってきてるのがわかります。
ここには`createPage`のcontextに設定したデータが入ってきます。
従って、`pathSlug`が入っているはずです。

このデータを活用してページを作成するために、template側を修正する必要があります。

```src/templates/post.jsx
import React from 'react'
import { graphql } from 'gatsby'
import Layout from '../layouts'

const Post = ({ data }) => {
  const post = data.markdownRemark
  const title = post.frontmatter.title
  const date = post.frontmatter.date
  const html = post.html

  return (
    <Layout>
        <h1>{title}</h1>
        <p>{date}</p>
        <div dangerouslySetInnerHTML={{ __html: html }} />
    </Layout>
  )
}
export const query = graphql`
  query($pathSlug: String!) {
    markdownRemark(frontmatter: { path: {eq: $pathSlug} }) {
      html
      frontmatter {
        date
        title
      }
    }
  }
`
export default Post

```

`markdownRemark`queryを使用し、そこに先程`gatsby-node.js`で渡した`pathSlug`を入れてます。
pageContextに設定したプロパティをそのままpage-queryに入れてます。
これでPostの詳細ページの完成です！


## タグを生成する
次に、タグの一覧を集めたページと、
タグをクリックした時に同じタグのポストのみ表示させるページを作りたいと思います。

```src/pages/tags.jsx
import React from 'react'

const Tags = props => {
  return (
    <div>
      Tags
    </div>
  )
}

export default Tags
```

次に、前のセクションと同様、タグのテンプレートを作成します。

```src/templates/tag.jsx
import React from 'react'

const Tag = props => {
  return (
    <div>
      Tag
    </div>
  )
}

export default Tag
```

`gatsby-node.js`に戻り、タグ別にページを作る処理を書きましょう。
すべてのポストのタグを集め、タグ別にポストを分類し、
`createPage`のcontextにタグの情報を渡して上げればOKです。

```gatsby-node.js
const path = require('path')

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

  return new Promise((resolve, reject) => {
    const postTemplate = path.resolve('src/templates/post.jsx')
    const tagPage = path.resolve('src/pages/tags.jsx');
    const tagPosts = path.resolve('src/templates/tag.jsx');

    resolve(
      graphql(
        `
          query {
            allMarkdownRemark {
              edges {
                node {
                  frontmatter {
                    path
                    title
                    tags
                  }
                }
              }
            }
          }
        `
      ).then(result => {
        if (result.errors) {
          return Promise.reject(result.errors)
        }

        const posts = result.data.allMarkdownRemark.edges;

        const postsByTag = {};

        posts.forEach(({ node }) => {
          if (node.frontmatter.tags) {
            node.frontmatter.tags.forEach(tag => {
              if (!postsByTag[tag]) {
                postsByTag[tag] = [];
              }
              postsByTag[tag].push(node);
            });
          }
        });

        const tags = Object.keys(postsByTag);

        tags.forEach(tagName => {
          const posts = postsByTag[tagName]
          createPage({
            path: `/tags/${tagName}`,
            component: tagPosts,
            context: {
              posts,
              tagName
            }
          })
        })

        createPage({
          path: '/tags',
          component: tagPage,
          context: {
            tags: tags.sort(),
          },
        });

        posts.forEach(({node}) => {
          const path = node.frontmatter.path

          createPage({
            path,
            component: postTemplate,
            context: {
              pathSlug: path
            }
          })
        })
      })
    )
  })
})
```

`gatsby-node.js`の修正が終わったので、サーバーを再起動して、
contextを受け取るコンポーネント側も修正しましょう。

```src/pages/tags.jsx
import React from 'react'
import { Link } from 'gatsby'

const Tags = ({pageContext}) => {
  const { tags } = pageContext
  return (
    <div>
      <ul>
        {tags.map((tagName, index) => {
          return (
            <li key={index}>
              <Link to={`/tags/${tagName}`}>
                {tagName}
              </Link>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default Tags
```

```src/templates/tag.jsx
import React from 'react'
import { Link } from 'gatsby'

const Tag = ({ pageContext }) => {
  const { posts, tagName } = pageContext
  return (
    <div>
      <div>
        Posts about {`${tagName}`}
      </div>
      <div>
        <ul>
          {posts.map((post, index) => {
            return (
              <li key={index}>
                <Link to={post.frontmatter.path}>
                  {post.frontmatter.title}
                </Link>
              </li>
            )
          })}
        </ul>
      </div>
    </div>
  )
}

export default Tag
```

せっかくだしポストのページにもタグを追加しましょう。
タグリストのコンポーネントを作成します。

```src/components/TagList.jsx
import React from 'react'
import { Link } from 'gatsby'

const TagList = ({ tags }) => {
  return (
    <div>
      {tags.map(tag =>
        <Link key={tag} to={`/tags/${tag}`}>
          {tag}
        </Link>
      )}
    </div>
  )
}

export default TagList

```

```src/pages/post.jsx
...
    <TagList tags={post.frontmatter.tags || []} />
...
```

これでタグができました！

## 前の記事、次の記事を実装する
前の記事、次の記事のリンクをポストのページにおきたいです。
そのために、`gastby-node.js`でpostページ生成のロジックをいじってみましょう。

```gatsby-node.js
...
  posts.forEach(({ node }, index) => {
    const path = node.frontmatter.path
    const prev = index === 0 ? null : posts[index - 1].node;
    const next = index === posts.length - 1 ? null : posts[index + 1].node;

    createPage({
      path,
      component: postTemplate,
      context: {
        pathSlug: path,
        prev,
        next,
      }
    });
  });
...

```

インデックスを使って前、次を判断してるので、まずはデータをソートする必要がありそうです。
前のセクションでやった内容の復習ですね。しかし今回は降順ではなく昇順でソートします。

```gatsby-node.js
...
          query {
            allMarkdownRemark (
              sort: {order: ASC, fields: [frontmatter___date]}
            ) {
              edges {
                node {
                  frontmatter {
                    path
                    title
                    date
                  }
                }
              }
            }
          }
...

```

これで`prev`と`next`の情報がpageContextで渡ってきますので、`src/pages/post.jsx`を修正しましょう。

```src/pages/post.jsx
import React from 'react'
import { graphql, Link } from 'gatsby'
import Layout from '../layouts'
import TagList from '../components/TagList';

const Post = ({ data, pageContext }) => {
  const post = data.markdownRemark
  const title = post.frontmatter.title
  const date = post.frontmatter.date
  const html = post.html
  const { prev, next } = pageContext
  return (
    <Layout>
        <h1>{title}</h1>
        <p>{date}</p>
      <div dangerouslySetInnerHTML={{ __html: html }} />
      <TagList tags={post.frontmatter.tags || []} />
      {next &&
        <Link to={next.frontmatter.path}>
          Next
        </Link>
      }
      {prev &&
        <Link to={prev.frontmatter.path}>
          Previous
        </Link>
      }
    </Layout>
  )
}
```

これでシンプルなページ設定は終わりです。

## イメージを使う
今回はイメージを読み込めるようにします。
いろんな方法がありますが、ここでは普通のJSインポートと、
gatsbyのpluginを使った２つの方法を紹介します。

### JSインポート
まずは適当なロゴファイルをプロジェクトフォルダーに入れましょう。
私は`src/images/logo.png`という名前にしました。

```src/layouts/index.jsx
...
import logo from '../images/logo.png'
...
    <img src={logo} alt="logo" />
...
```
`import`文を使ってインポートしたイメージをそのまま`img`タグの`src`に渡せばいいです。
Gatsbyのすごいところは、パフォーマンスを重視した設定を自動でやってくれることです。
10KB以下のファイルはuriで渡してくれますが、
10KBを超えるファイルはstaticフォルダーにバンドルしてくれます。

### Pluginを使う
とても高いパフォーマンスでイメージをさばいてくれるpluginを使ってみましょう。
まずは、プラグインと`gatsby-image`をインストールしましょう。

```console
npm i gatsby-transformer-sharp gatsby-plugin-sharp gatsby-remark-images gatsby-image
```

`gatsby-config.js`でプラグインを設定します。
今回はマークダウンの中のイメージをプロセスしてくれる`gatsby-remark-images`を、
`gatsby-transformer-remark`のoptionsに設定します。

更に、`maxWidth`と`quality`、`linkImagesToOriginal`を書いていきます。
他の詳しいoptionは、
(https://www.gatsbyjs.org/packages/gatsby-remark-images/?=remark)
を参考にしてください。

```gatsby-config.js
plugins: [
    'gatsby-plugin-catch-links',
    'gatsby-transformer-sharp',
    'gatsby-plugin-sharp',
      {
        resolve: 'gatsby-source-filesystem',
        options: {
          name: 'posts',
          path: `${__dirname}/posts`
        }
    },
    {
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: [
          {
            resolve: 'gatsby-remark-images',
            options: {
              maxWidth: 690,
              quality: 90,
              linkImagesToOriginal: true,
            }
          }
        ]
      }
    }]
```

次はポストのカバーイメージを設定しましょう。
ポストの同じフォルダーにイメージを準備しておき、coverイメージを追加します。

```markdown:posts/post-one/index.md
...
    date: '2019-08-18',
    title: 'My First Post',
    cover: './image.jpg'
...
```

GraphiQLのページですべてのマークダウン内のイメージを読み込んでみましょう。

```graphql
{
    allMarkdownRemark {
      edges {
        node {
          id
          excerpt(pruneLength: 100)
          frontmatter {
            title
            date(formatString: "YYYY年MM月DD日")
            path
            cover {
              childImageSharp {
                fluid(maxWidth: 1000, quality: 90) {
                  src
                }
                fixed(width: 650) {
                  src
                }
              }
            }
          }
        }
      }
    }
  }
```

`childImageSharp`の中の`fixed`と`fluid`を見てください。
`fixed`は指定された`width`と`height`でイメージを返してくれ、
`fluid`はresponsiveにイメージを返してくれます。
Gatsbyはユーザーの使っている端末に合わせて最適なサイズのイメージを自動的に使ってくれます。
lazy-loadingなどもやってくれるので、設定無しですごくパフォーマンスの良いサイトを作れます。

残念ながらGraphiQLのエラーで`fragment`を使えません。
`fragment`は、GraphQLのqueryを使い回すために、予め設定しておいたものです。
`gatsby-image`には、予め定められた`fragment`がたくさんあるので、そこから選んで使いましょう。
https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-image#fragments

では実際のコンポーネントで使ってみます。

```src/pages/index.jsx
import React from 'react'
import { graphql, Link } from 'gatsby'
import Img from 'gatsby-image'
import Layout from '../layouts'

export default ({ data }) => {
  const { edges } = data.allMarkdownRemark
  return (
  <Layout>
    <h1>Gatsby Tutorial Home Page</h1>
      {edges.map(({ node }) => (
        <div key={node.id}>
          <Img 
            fluid={node.frontmatter.cover.childImageSharp.fluid} 
            alt={node.frontmatter.title}
          />
          <Link to={node.frontmatter.path}>
            <h3>{node.frontmatter.title}</h3>
          </Link>
          <p>{node.frontmatter.date}</p>
          <p>{node.excerpt}</p>
        </div>
    ))}
  </Layout>
)}

export const query = graphql`
  query {
    allMarkdownRemark {
      edges {
        node {
          id
          excerpt(pruneLength: 100)
          frontmatter {
            title
            date(formatString: "YYYY年MM月DD日")
            path
            cover {
              childImageSharp {
                fluid(maxWidth: 1000, quality: 90) {
                  ...GatsbyImageSharpFluid_withWebp_tracedSVG
                }
              }
            }
          }
        }
      }
    }
  }
`

```

homeのページで、すべてのマークダウンのカバーイメージを表示するようにしました。
`gatsby-image`を`Img`としてインポートして、その中の`fixed`や`fluid`プロパティに、イメージのデータを渡してあげます。

`src/templates/post.jsx`でも、同じくカバーイメージを追加しましょう。

```src/templates/post.jsx
import React from 'react'
import { graphql, Link } from 'gatsby'
import Img from 'gatsby-image'
import Layout from '../layouts'
import TagList from '../components/TagList';

const Post = ({ data, pageContext }) => {
  const post = data.markdownRemark
  const title = post.frontmatter.title
  const date = post.frontmatter.date
  const html = post.html
  const { prev, next } = pageContext
  return (
    <Layout>
        <Img 
          fluid={post.frontmatter.cover.childImageSharp.fluid} 
            alt={node.frontmatter.title}
        />
        <h1>{title}</h1>
        <p>{date}</p>
      <div dangerouslySetInnerHTML={{ __html: html }} />
      <TagList tags={post.frontmatter.tags || []} />
      {next &&
        <Link to={next.frontmatter.path}>
          Next
        </Link>
      }
      {prev &&
        <Link to={prev.frontmatter.path}>
          Previous
        </Link>
      }
    </Layout>
  )
}
export const query = graphql`
  query($pathSlug: String!) {
    markdownRemark(frontmatter: { path: {eq: $pathSlug} }) {
      html
      frontmatter {
        date
        title
        tags
        cover {
          childImageSharp {
            fluid(maxWidth: 1920, quality: 90) {
              ...GatsbyImageSharpFluid_withWebp
            }
          }
        }
      }
    }
  }
`

```

これで、イメージを表示できるようになりました！

## スタイリング
ここまでお疲れさまです！
ブログがちゃんと動くようになりましたね。
しかし、見栄えが全然よくないので、スタイリングを追加していきます。

Reactでのスタイリングはいろんな方法があります。
ここでは`react-emotion`というライブラリーを使っていきたいと思います。
`styled-components`と似てるので、入れ替えても問題ないかと思います。

まず、関連ライブラリーをインストールしましょう。

```console
npm i emotion @emotion/core @emotion/styled emotion-theming gatsby-plugin-emotion
```

`gatsby-config.js`に`gatsby-plugin-emotion`を追加しておきましょう。

### Global、ThemeProvider

`src`フォルダーの外に`config/theme.js`を作成します。
`theme.js`には、使いまわしたいcssを変数として保存しておきます。
それを`ThemeProvider`に渡すことで、いろんなコンポーネントで使い回すことができます。

```config/theme.js
const colors = {
  black: {
    base: '#333438',
    light: '#4b4e57',
    lighter: '#696d77',
    blue: '#2e3246',
  }
}
const transition = {
  easeInOutCubic: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
  easeOutBack: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
  duration: '0.4s',
}
const theme = {
  colors,
  transition,
}

export default theme
```
`ThemeProvider`はReactのContext APIを使ってますが、DarkModeなどの設定を書くのも便利です。
詳しい方法は下の記事に書いてあったので、気になる方は試してみてください。
https://morimo7.hatenablog.com/entry/2019/07/13/155418

`theme.js`を書いたので、実際に`ThemeProvider`を使ってみます。
すべてのページで共有する`layout`に変更を加えましょう。
中身は、`src/components/NavBar.jsx`に切り出します。

```src/layouts/index.jsx
import React from 'react'
import { ThemeProvider } from 'emotion-theming'
import { Global, css, } from '@emotion/core'
import theme from '../../config/theme'

const reset = css`
  *, *:before, *:after {
    box-sizing: inherit;
  }
  html, body {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
  }
`

export default ({ children }) => (
  <ThemeProvider theme={theme}>
    <>
    <Global styles={reset}/>
      {children}  
    </>
  </ThemeProvider>
)

```
`Global`では全体に適用したいスタイルを書きました。
ここではcssのリセットですね。

```src/components/NavBar.jsx
import React from 'react'
import { Link } from 'gatsby'
import styled from '@emotion/styled'
import logo from '../images/logo.png'

const StyledLink = styled(Link)`
  display: flex;
  font-weight: 700;
  align-items: center;
`

const Nav = styled.nav`
  display: flex;
  justify-content: flex-end;
  font-weight: 500;
  font-size: 1.25rem;
  align-items: center;

  a {
    color: ${props => props.theme.colors.black.base};
    margin-left: 2rem;
    transition: all ${props => props.theme.transitions};
    &:hover {
      color: ${props => props.theme.colors.black.lighter};
    }
  }
`

const NavBar = () => (
  <>
    <StyledLink to="/">
      <img src={logo} alt='Gatsby Logo' />
    </StyledLink>
    <Nav>
      <Link to='/'>Home</Link>
      <Link to='/about'>About</Link>
    </Nav>
  </>
)

export default NavBar
```

`styled`を使ってスタイリングしていきます。
`emotion`の`ThemeProvider`を使えば、`props`から`theme`にアクセスすることができます。

一例として`emotion`を使う方法を紹介しましたが、
他に自分の気に入ってる方法があればご自由に使用してください。
この記事はCSSの記事ではないので、スタイリングに関しては以上になります。

## SEO、PWA設定
ブログは、コンテンツをより多くの人に露出するために、SEO対策がとても大事です。
このセクションでは、Google LighthouseのSEOスコアを100にしていきます。

### manifest

まずはPWAサポートのためにmanifestを追加します。

```console
npm i react-helmet gatsby-plugin-react-helmet gatsby-plugin-manifest gatsby-plugin-sitemap gatsby-plugin-offline
```

今までは`gatsby-config.js`にサイトの情報を書いておきましたが、`config/site.js`に切り出します。

```config/site.js
module.exports = {
  pathPrefix: '/',
  title: 'My Wonderful Website', // タイトル
  titleAlt: 'My Wonderful Website', // JSONLDのためのタイトル
  description: 'Welcome to my brilliant website.',
  url: 'https://[].netlify.com', // スラッシュなしのサイトURL
  siteURL: 'https://[].netlify.com/', // スラッシュありのサイトURL
  siteLanguage: 'ja', // HTMLの言語（ここでは日本語）
  logo: 'src/images/logo.png',
  banner: 'src/images/banner.png',
  favicon: 'src/images/favicon.png', // ファビコン
  shortName: 'CoolSite', // サイトの略称、12文字以下
  author: 'so99ynoodles', // schemaORGJSONLDの作成者
  themeColor: '#3e7bf2',
  backgroundColor: '#d3e0ff',
  twitter: '@so996ynoodles', // TwitterのID
};
```

```gatsby-config.js
const config = require('./config/site');

module.exports = {
  siteMetadata: {
 ...config
  },
...

```

また、インストールしたプラグインの設定も書きましょう。

```gatsby-config.js
...
'gatsby-plugin-sitemap',
    {
      resolve: 'gatsby-plugin-manifest',
      options: {
        name: config.title,
        short_name: config.shortName,
        description: config.description,
        start_url: config.pathPrefix,
        background_color: config.backgroundColor,
        theme_color: config.themeColor,
        display: 'standalone',
        icon: config.favicon,
      },
    },
 'gatsby-plugin-offline'
]
...
```
`gatsby-plugin-sitemap`を書いたあとに、`gatsby-plugin-manifest`を書きます。
`gatsby-plugin-offline`をその後に書くことで、manifestファイルがcacheされます。

### SEO

`src/components/SEO.jsx`を作成します。
まずは先程作成したサイトの情報をとってきて、`react-helmet`を使って記述していきます。
`react-helmet`はdocumentのheadをいじれるライブラリーです。
Helmetの情報は自動的にstaticなHTMLとして変換されます。
JavaScriptのコンテンツは一般的にSEOに向かないので助かりますね。

```src/components/SEO.jsx
// src/components/SEO.jsx
import React, { Component } from 'react'
import Helmet from 'react-helmet'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'

const SEO = ({ title, desc, banner, pathname, article }) => (
  <StaticQuery
    query={query}
    render={({
      site: {
        buildTime,
        siteMetadata: {
          defaultTitle,
          titleAlt,
          shortName,
          author,
          siteLanguage,
          logo,
          siteUrl,
          pathPrefix,
          defaultDescription,
          defaultBanner,
          twitter,
        },
      },
    }) => {
      const seo = {
        title: title || defaultTitle,
        description: defaultDescription || desc,
        image: `${siteUrl}${banner || defaultBanner}`,
        url: `${siteUrl}${pathname || '/'}`,
      };
    }}
  />
);

export default SEO

SEO.propTypes = {
  title: PropTypes.string,
  desc: PropTypes.string,
  banner: PropTypes.string,
  pathname: PropTypes.string,
  article: PropTypes.bool,
};

SEO.defaultProps = {
  title: null,
  desc: null,
  banner: null,
  pathname: null,
  article: false,
};

const query = graphql`
  query SEO {
    site {
      buildTime(formatString: "YYYY年MM月DD日")
      siteMetadata {
        defaultTitle: title
        titleAlt
        shortName
        author
        siteLanguage
        logo
        siteUrl: url
        pathPrefix
        defaultDescription: description
        defaultBanner: banner
        twitter
      }
    }
  }
`;

```

次にschema.orgを使ってJSON-LDを書いていきます。
JSON-LDはGoogle推奨、リッチスニペットを表示させる構造的マークアップです。
詳しい内容はQiitaにいい記事があったので、参考にしてください。
https://qiita.com/narumana/items/b66969b80cce848b2ddf

```src/components/SEO.jsx
...
     const seo = {
        title: title || defaultTitle,
        description: defaultDescription || desc,
        image: `${siteUrl}${banner || defaultBanner}`,
        url: `${siteUrl}${pathname || '/'}`,
      };
      const realPrefix = pathPrefix === '/' ? '' : pathPrefix;
      let schemaOrgJSONLD = [
        {
          '@context': 'http://schema.org',
          '@type': 'WebSite',
          '@id': siteUrl,
          url: siteUrl,
          name: defaultTitle,
          alternateName: titleAlt || '',
        },
      ];
      if (article) {
        schemaOrgJSONLD = [
          {
            '@context': 'http://schema.org',
            '@type': 'BlogPosting',
            '@id': seo.url,
            url: seo.url,
            name: title,
            alternateName: titleAlt || '',
            headline: title,
            image: {
              '@type': 'ImageObject',
              url: seo.image,
            },
            description: seo.description,
            datePublished: buildTime,
            dateModified: buildTime,
            author: {
              '@type': 'Person',
              name: author,
            },
            publisher: {
              '@type': 'Organization',
              name: author,
              logo: {
                '@type': 'ImageObject',
                url: siteUrl + realPrefix + logo,
              },
            },
            isPartOf: siteUrl,
            mainEntityOfPage: {
              '@type': 'WebSite',
              '@id': siteUrl,
            },
          },
        ];
      }
    }}
  />
);
...

```

最後に、書いた情報を`Helmet`に埋め込みます。
FacebookのためのOpenGraphブロックと、twitterブロックも用意しました。

```src/components/SEO.jsx
...
              '@id': siteUrl,
            },
          },
        ];
      }
      return (
        <>
          <Helmet title={seo.title}>
            <html lang={siteLanguage} />
            <meta name="description" content={seo.description} />
            <meta name="image" content={seo.image} />
            <meta name="apple-mobile-web-app-title" content={shortName} />
            <meta name="application-name" content={shortName} />
            <script type="application/ld+json">{JSON.stringify(schemaOrgJSONLD)}</script>

            {/* OpenGraph  */}
            <meta property="og:url" content={seo.url} />
            <meta property="og:type" content={article ? 'article' : null} />
            <meta property="og:title" content={seo.title} />
            <meta property="og:description" content={seo.description} />
            <meta property="og:image" content={seo.image} />

            {/* Twitter Card */}
            <meta name="twitter:card" content="summary_large_image" />
            <meta name="twitter:creator" content={twitter} />
            <meta name="twitter:title" content={seo.title} />
            <meta name="twitter:description" content={seo.description} />
            <meta name="twitter:image" content={seo.image} />
            </Helmet>
        </>
      );
    }}
  />
);
...

```

完成です！
`SEO.jsx`は`layout`に置くこともできますし、
`posts`みたいなテンプレートに置くことで、デフォルトな情報ではなく、そのページに特化した情報を渡すこともできます。

```src/templates/post.jsx
...
  return (
    <Layout>
      <SEO
        title={title}
        description={post.frontmatter.description || post.excerpt || ' '}
        image={image}
        pathname={post.frontmatter.path}
        article
      />
...
```


## デプロイする
お疲れさまです！
デプロイにはいろんな方法があります。
githubページとか、Netlify, Surge, Herokuなど。
個人的にはNetlifyとgithubを連携して、ブログを更新するために自動deployする方法が好きです。
詳しい内容は下をご参考ください。
https://www.gatsbyjs.org/docs/hosting-on-netlify/


## 最後に
いかがだったでしょうか。
長文お読みいただきありがとうございます。
説明が下手な部分や、省略してしまった部分もあり、読みにくい文章になってたらごめんなさい。
この記事で少しでもGatsbyへの理解が深まったなら幸いです。

完成品は<strong><a href="https://gatsby-starter-noodles.netlify.com/">こちら</a></strong>
コードは<strong><a href="https://github.com/so99ynoodles/gatsby-starter-noodles/">こちら</a></strong>