【Gatsby.js】「ここだけ押さえれば普通に使える」って知識をまとめてみた

Gatsby.jsでシンプルな静的サイトを作る際に必要な知識だけをまとめました。CMSとの連携等については今回は扱いません。

## Gatsby.jsとは
React製の静的サイトジェネレーターです。webサイトを作る時に使います。

## Gatsby.jsのメリット
従来通りHTML/CSS(+Gulp, Webpack)でコーディングする方法と比較した場合の最大のメリットは**表示速度がめっちゃ速い**ことです。びっくりするくらい速いです。

その他のメリットとしては

- JSXを使うので、HTMLの記法等にミスがあったときにすぐに気が付くことができる
- プラグインが豊富(Markdownファイルを読み込む、PWA対応、etc)
- ヘッドレスCMSとの相性が良い
- Gatsby Themeを使って、既存のデザインを流用することができる
    - カスタマイズが前提ならthemeは使わない方が良さそう

などがあります。

## セットアップ
### 前提
- node.jsがインストールされている
- yarnがインストールされている
    - なければnpmコマンドで代用可

### Gatsby CLIをインストール
```zsh
$ npm install -g gatsby-cli
$ gatsby -v
```
### プロジェクト開始
```zsh
$ gatsby new
✔ What is your project called? … my_project #プロジェクト名
✔ What starter would you like to use? › gatsby-starter-hello-world # 一番シンプルなスターターを選択
$ cd my_project
$ gatsby develop
```
http://localhost:8000/ にアクセスして、初期表示を確認。

## ヘッダー、フッター、レイアウトファイルを作成
複数のページで共通して使うパーツは`components`ディレクトリに配置します。

```zsh
$ mkdir src/components
$ touch src/components/header.js
$ touch src/components/footer.js
$ touch src/components/layout.js
```
```jsx:src/components/header.js
import React from "react"

export default () => (
  <header>
    <h1>サンプルサイトのヘッダー!</h1>
  </header>
)
```
```jsx:src/components/footer.js
import React from "react"

export default () => (
  <footer>
    <small>サンプルサイトのフッター!</small>
  </footer>
)
```
```jsx:src/components/layout.js
import React from "react"
import Header from "../components/header"
import Footer from "../components/footer"

export default ({ children }) => (
  <> {/* <React.Fragment>の省略形 */}
    <Header />
    {children}
    <Footer />
  </>
)
```

## トップページを作成
`pages`ディレクトリに、1ページ1ファイルで配置します。

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

export default () => (
  <Layout>
    <div>
      <p>トップページのメインコンテンツ！</p>
    </div>
  </Layout>
)
```

## リンクを追加
サイト内のリンクは従来の`<a href="hoge">`の代わりに`<Link to={"hoge"}>`を使うことで、ページ遷移のスピードを格段に上げることができます。

まずは遷移先のページを作成。

```zsh
$ touch src/pages/second.js
```
```jsx:src/pages/second.js
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"

export default () => (
  <Layout>
    <div>
      <p>2つ目のページのメインコンテンツ！</p>
      <Link to={"/"}>トップページへのリンク</Link>
    </div>
  </Layout>
)
```
トップページにもリンクを追加

```jsx:src/pages/index.js
import React from "react"
import { Link } from "gatsby" //追加
import Layout from "../components/layout"

export default () => (
  <Layout>
    <div>
      <p>トップページのメインコンテンツ！</p>
      <Link to={"/second/"}>Secondページへのリンク</Link> {/* 追加 */}
    </div>
  </Layout>
)
```


## 画像を最適化して表示
「画像の最適化こそがGatsby.jsを使う最大の理由」と言っても過言では無いくらいの目玉機能です。

- 劣化が目立たない範囲で圧縮
- webp対応のブラウザではjpgやpngの画像もwebpで表示
- デバイスのサイズや解像度に応じて適切なサイズで表示
- 遅延読み込み

といった画像の最適化を、比較的少ない設定で実現することができます。
ここでは簡単さを重視して細かいな説明は省略します。
より深く知りたい方は、`gatsby 画像 static query`というキーワードで調べて見てください。

### 画像を追加
```zsh
$ mkdir src/images
```
作成した`images`ディレクトリに適当な画像(`sample1.jpg`)を追加します。

### 画像最適化のためのプラグインを追加
```zsh
$ yarn add gatsby-image gatsby-transformer-sharp gatsby-plugin-sharp gatsby-source-filesystem 
```
```gatsby-config.js
module.exports = {
  /* Your site config here */
  plugins: [
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`,
      },
    },
  ],
}
```

### 画像を表示するためのコンポーネントを作成
```zsh
$ touch src/components/image.js
```
```jsx:src/components/image.js
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"

export default props => {
  const { allImageSharp } = useStaticQuery(graphql`
    query {
      allImageSharp {
        nodes {
          fluid(maxWidth: 1600) {
            originalName
            ...GatsbyImageSharpFluid_withWebp
          }
        }
      }
    }
  `)
  return (
    <figure className={props.className} style={props.style}>
      <Img
        fluid={
          allImageSharp.nodes.find(n => n.fluid.originalName === props.filename)
            .fluid
        }
        alt={props.alt}
      />
    </figure>
  )
}
```
### トップページで画像を表示
```jsx:src/pages/index.js
import React from "react"
import { Link } from "gatsby"
import Layout from "../components/layout"
import Image from "../components/image" //追加

export default () => (
  <Layout>
    <div>
      <p>トップページのメインコンテンツ！</p>
      <Link to={"/second/"}>Secondページへのリンク</Link>
      <Image filename="sample.jpg" alt="サンプル画像" /> {/* 追加 */}
    </div>
  </Layout>
)
```

## メタデータを設定
### プラグインを追加
```zsh
$ yarn add gatsby-plugin-react-helmet react-helmet
```
```gatsby-config.js
module.exports = {
  /* Your site config here */
  plugins: [
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images/`,
      },
    },
    `gatsby-plugin-react-helmet` //追加
  ],
}
```

### メタデータ管理用のコンポーネントを作成
```zsh
$ touch src/components/meta.js
```
```jsx:src/components/meta.js
import React from "react"
import { Helmet } from "react-helmet"

export default () => (
  <Helmet>
    <html lang="ja" />
    <title>サンプルサイト</title>
    <meta name="description" content="説明文" />
  </Helmet>
)
```

### ページに埋め込む
```jsx:src/pages/index.js
//...
import Meta from "../components/meta" //追加

export default () => (
  <Layout>
    <Meta /> {/* 追加 */}
    {/* ... */}
  </Layout>
)
```

### ページごとに出力する値を変える
ページの中でtitleが設定されていれば`タイトル | サンプルサイト`、設定がなければ`サンプルサイト`を出力するように編集します。

```jsx:src/components/meta.js
//...

export default props => { // html部分の前に処理が入ると、( から { に変わることに注意
  const baseTitle = "サンプルサイト"
  const title = props.title ? `${props.title} | ${baseTitle}` : baseTitle
  return (
    <Helmet>
      <html lang="ja" />
      <title>{title}</title>
      <meta name="description" content={props.desc} />
    </Helmet>
  )
}
```
```jsx:src/pages/index.js
//...
export default () => (
  <Layout>
    <Meta
      title="トップページ"
      desc="Gatsby.jsの使い方を説明するためのサンプルページです。"
    />
    {/* ... */}
  </Layout>
)
```
```jsx:src/pages/second.js
import Meta from "../components/meta"
//...
export default () => (
  <Layout>
    <Meta
      title="2つ目のページ"
      desc="2ページ目です"
    />
    {/* ... */}
  </Layout>
)
```

## CSSの選択肢
Gatsby.jsにおけるCSSの選択肢は大きく4つあります。
### グローバルCSS
1つのCSS(or SCSS)ファイルに全てのスタイルを記述する方法です。
従来のやり方とほぼ同じなので、学習コストがかからないことが最大のメリットです。
小規模なサイトであれば選択肢の1つでしょう。

### CSS Modules
コンポーネントごとにCSSファイルを作り、それを適用したいコンポーネントからインポートしてスタイルを当てていく方法です。scopeができるので、クラス名が重複する心配を減らすことができます。

```src/components/header.module.css
.title {
  color: green;
}
```
```src/components/header.js
import Styles from "./header.module.css"
.
.
return (
  <h1 className={Styles.title}>サンプルサイト</h1>
)
```

### CSS in JS
[styled-components](https://styled-components.com/)、[emotion](https://emotion.sh/docs/introduction)などのCSS in JSライブラリを使う方法です。まだ使ったことないです。使い方はライブラリごとに違いますが、どれも色々大変そうです。

### Tailwind CSS
個人的にイチオシの方法です。
Tailwind CSSはユーティリティークラス(bootstrapでいうところの`mb-4`、`text-center`など)を大量に集めたCSSフレームワークです。
HTMLにクラスを大量につけて、極力CSSを書かないようにしよう、というアプローチをとっています。

このアプローチには

- クラス名を決める必要がなくなる
- クラス名の衝突がなくなる
    - BEM、CSS設計からの開放
- デザインの修正により不要になったCSSが残ってしまうことがなくなる
- どの要素にどんなスタイルが当たっているかがすぐにわかる
- カラーコードやフォントサイズ、ブレイクポイント等の統一性を保ちやすい
    - GitLabでは402のテキスト色、239の背景色、59のフォントサイズが使われています。
- ネット上に転がっているサンプルコードを気軽に取り入れやすい
    - 通常この手のサンプルはカスタマイズがしんどいけど、Tailwindならその心配なし
    - [tailwindcomponents.com](https://tailwindcomponents.com/)に公式のサンプルが大量に公開されている

など様々なメリットがあります。
またメディアクエリや繰り返し使うコンポーネントのスタイリングに関してもうまいこと解決してます。

以下、Tailwind CSSの開発者、Adam Wathanのブログの翻訳記事です。
[翻訳：CSSユーティリティクラスと「関心の分離」（いかにしてユーティリティファーストにたどり着いたか） - yuhei blog](https://yuheiy.hatenablog.com/entry/2020/05/25/021342)

## Tailwind CSSの導入
### TailwindCSS、PostCSSをインストール
```zsh
$ yarn add gatsby-plugin-postcss # postcssをインストール
```
```gatsby-config.js
module.exports = {
  plugins: [
    //省略
    `gatsby-plugin-postcss`, //追加
  ],
}
```
```zsh
$ yarn add tailwindcss --dev # tailwind cssをインストール
$ yarn tailwindcss init # tailwind cssの設定ファイルを生成
$ touch postcss.config.js # postcssの設定ファイルを生成
```
```postcss.config.js
const tailwind = require('tailwindcss')
 
module.exports = () => ({
  plugins: [tailwind('./tailwind.config.js')],
})
```
```zsh
$ mkdir src/utils
$ touch src/utils/global.css
```
```src/utils/global.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### ページに反映させる
```src/components/layout.js
import React from "react"
import '../utils/global.css' //追加
//省略
```
```jsx:src/pages/index.js
//省略
export default () => (
  <Layout>
    {/* ... */}
    <div>
      <p>トップページのメインコンテンツ！</p>
      <button class="block uppercase mx-auto shadow bg-indigo-800 hover:bg-indigo-700 focus:shadow-outline focus:outline-none text-white text-xs py-3 px-10 rounded">
        Tailwind test
      </button>
      {/* ... */}
  </Layout>
)
```

### Purge CSS
ビルド時に、実際に使われているクラスをチェックし、そのクラスのスタイルだけをCSSファイルに含めるようにして、ファイルサイズを小さくする設定を行います。

```tailwind.config.js
module.exports = {
  purge: ["./src/**/*.js"], //編集
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
}
```
この修正を行う前後で`gatsby build`コマンドを叩くと、修正前に表示されていた
`Tailwind is not purging unused styles because no template paths have been provided.`
のエラーが、修正後には消えていることが確認できます。

### その他
VSCodeで開発する場合、[Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)という拡張機能を使うと快適にコーディングができます。

## デプロイ
NetlifyやVercel等にデプロイすると

- 無料で公開できる
- 高速
- Githubへのプッシュと連動して自動でビルド→デプロイされる

のでおすすめです。
Netlifyの設定は[こちらの記事](https://rightcode.co.jp/blog/information-technology/netlify-github-up)で紹介されています。

## 参考
- [Webサイト高速化のための 静的サイトジェネレーター活用入門](https://amzn.to/30mrmps)
- [【GatsbyJS】yarnで、Tailwindcssを使用する方法【シンプル構成】](https://0371.blog/programming/gatsbyjs-tailwindcss-yarn)
- [最小手数で始めるTailwind CSS - メドピア開発者ブログ](https://tech.medpeer.co.jp/entry/better-tailwind-css)