プライベートでやっているバンドのホームページをレンタルサーバー+WordPressで運営していたのですが、最近Gatsby+microCMS+NetlifyのいわゆるJAMstack構成のホームページにリプレースしました。
ホームページ:男子校 Official Web Site
概要
今までかれこれ5年くらいレンタルサーバー+WordPressで運営してきていたのですが、
- サーバー代が毎年かかる
 - 表示が重い
 - 「固定ページやフォームをちょっとだけ動的にしたい」みたいなことが難しい
 - プラグイン・バージョンのアップデートといった管理が面倒
 
といった点が課題でした。
これらの課題を解消したく、また最近気になっていたGatsbyを使ってみたかったのもあってホームページをリプレースしてみました。
構成
TypeScript+React+Gatsbyでコードを作成、microCMSでデータを管理してNetlifyでホスティングするという構成にしました。いわゆるJAMstackと呼ばれている構成で、動的なWebサーバーを必要とせず静的コンテンツを高速配信することができます。
Gatsby
React製のSSG(静的サイトジェネレーター)。MarkdownファイルやHeadless CMSと組み合わせてブログ等のサイトを簡単に作成でき、かつ爆速なサイトが簡単に作れると評判です。
「静的」とは言っていますがReactの機能はフルに使えるので、フロントエンドをフルに活用したサイトを作ることができます。
microCMS
日本製のHeadless CMS。
個人的には管理画面がシンプルで一番使いやすそうと感じたので採用しました。特にバンドのホームページは自分以外も更新するので、直感的に記事を作成できるのは大きなメリットです。もちろん全て日本語なのもGood。
Gatsbyには対応プラグインがあるので、これを使用することでGraphQLクエリで簡単にデータを取得できます。
無料でも10個までAPIを作成できるので、ブログ記事以外にも曲情報やライブ情報といったAPIを作成してホームページ内で利用しています。
Netlify
デプロイ先の静的ホスティングサービス。静的サイト・サーバーレスに特化したサービスとなっていて、以下のような機能を提供してくれます。
- GitHubやHeadless CMSと連携した自動ビルド(プライベートリポジトリでも可)
 - フォーム機能
 - AWS Lambdaベースのfunctions機能
 
microCMSにはNetlifyとWebhook連携できる機能があるので、microCMSで記事の作成・更新時にNetlifyに通知し、更新後のコンテンツで自動ビルドすることができます。
効果
リプレースによって上にあげた課題がかなり改善しました。
- サーバー代が毎年かかる
- → Netlify / microCMSともにに無料枠で十分なのでサーバー代は0になり、ドメイン代だけで済むようになった
 
 - 表示が重い
- → GatsbyやNetlify CDNのおかげでかなり爆速に
 
 - 「固定ページやフォームをちょっとだけ動的にしたい」みたいなことが難しい
- → Reactをフルに利用できるので動的処理も自由に入れられる
- 例えば「ライブ情報から、未来の日付のライブだけ予約できるフォームを自動で作る」のようなこともWordPressでは難しかったのが簡単にできた
 
 
 - → Reactをフルに利用できるので動的処理も自由に入れられる
 - プラグイン・バージョンのアップデートといった管理が面倒
- → GitHub / npmで一括管理1
- セキュリティアップデートなんかはGitHubが自動でプルリクを生成してくれたりする
 
 
 - → GitHub / npmで一括管理1
 
Gatsbyの使い方もReactが分かっていれば割ととっつきやすく2、Gatsbyを初めて触ってからだいたい1ヶ月くらいでリプレースできました。
ポイント
主にGatsbyまわりのちょっとしたポイントやハマった点をいくつか紹介しようと思います。
(具体的な環境構築の手順等は今回はあまり触れません)
デザインライブラリ
1からデザインを作成するのは結構大変だったので、Reactのマテリアルデザインライブラリmaterial-uiを使用しました。
Gatsbyで使用する場合は、gatsby-theme-material-uiプラグインを使用すればよいです。
src/gatsby-theme-material-ui-top-layout/theme.tsを作成することで以下のようにテーマをカスタマイズできます。
import { createMuiTheme } from '@material-ui/core'
import { indigo } from '@material-ui/core/colors'
const theme = createMuiTheme({
  palette: {
    primary: { main: '#fbfb99' },
    secondary: { main: indigo.A400 },
  },
  props: {
    MuiLink: { color: 'secondary' },
    MuiTextField: { variant: 'outlined', color: 'secondary' },
  },
})
export default theme
TypeScript対応
GatsbyはデフォルトでTypeScript対応していて、tsconfig.json等でカスタマイズすることもできます。
また、gatsby-plugin-graphql-codegenプラグインを使用することでGraphQLの取得データにも型補完が効いてかなり開発しやすくなります。
gatsby-plugin-graphql-codegenを使用する場合は以下の設定が個人的におすすめです。
Gatsby+TypeScriptを快適にするためのgatsby-plugin-graphql-codegenの設定
microCMSデータ取得
GatsbyとmicroCMSを連携するには先ほど述べた通り対応プラグインgatsby-source-microcmsを利用できます。
microCMSはコンテンツごとに複数のAPIを作成できますが、その場合は以下のようにgatsby-configでプラグインを複数回読み込むことでそれぞれデータ取得できます。
import { ITSConfigFn } from 'gatsby-plugin-ts-config'
const gatsbyConfig: ITSConfigFn<'config'> = ({ projectRoot }) => ({
  plugins: [
    // 中略
    {
      resolve: 'gatsby-source-microcms',
      options: {
        apiKey: process.env.GATSBY_MICROCMS_API_KEY, // APIキーは環境変数で管理するのが良いです
        serviceId: process.env.GATSBY_MICROCMS_ENDPOINT,
        endpoint: 'index', // APIのエンドポイント
        format: 'object', // オブジェクト形式の場合
      },
    },
    {
      resolve: 'gatsby-source-microcms',
      options: {
        apiKey: process.env.GATSBY_MICROCMS_API_KEY,
        serviceId: process.env.GATSBY_MICROCMS_ENDPOINT,
        endpoint: 'blogs', // APIのエンドポイント
        format: 'list', // リスト形式の場合
        readAll: true, // リスト形式の場合デフォルトでは最新10件のみ取得するので全件取得したい場合はこれをtrueにする
      },
    },
    // ...
  ]
})
export default gatsbyConfig
microCMSのリッチエディタ(HTML)→JSX変換
microCMSで使用できるリッチエディタはHTMLをGUIエディタで作成できるのですが、APIでは生のHTML文字列として取得されます。
HTML文字列をReactでレンダリングするには
const BlogPost: React.FC<{ body: string }> = ({ body }) => (
  <div dangerouslySetInnerHTML={{ __html: body }} />
)
のようにすればよいのですが、これだとHTMLそのままで出力されてしまうのでスタイルを適用するのがやや大変です。特にmaterial-uiのようなReactコンポーネントのライブラリの場合はこのままだと難しいです。
このHTMLにmaterial-uiのデザインを適用させるために、rehype-reactというライブラリでHTML→JSXに変換しました。
まず、以下のように各タグ(以下はh1を想定)に対応するコンポーネントを作成します。
import React from 'react'
import { Box, Typography, Divider } from '@material-ui/core'
type Props = { id?: string; className?: string }
export const Chapter: React.FC<Props> = ({ children, className, ...props }) => (
  <Box mt={2} mb={1} className={className}>
    <Typography {...props} variant="h4" component="h2">
      {children}
    </Typography>
    <Divider />
  </Box>
)
// ... 略
そして、rehype-parseでHTMLをパース・rehype-reactでHTMLタグをJSXコンポーネントに変換するrender関数を作成します。
import React from 'react'
import unified from 'unified'
import parser from 'rehype-parse'
import rehypeReact from 'rehype-react'
import { Chapter } from '../components/Typography'
const processor = unified()
  .use(parser, { fragment: true })
  .use(rehypeReact, {
    createElement: React.createElement,
    components: {
      h1: Chapter,
      // ...以下タグとコンポーネントの対応を並べていく
    },
  })
// HTML -> JSX に変換する関数
export const render = (html: string) => processor.processSync(html).result
これをReactコンポーネント内で
const BlogPost: React.FC<{ body: string }> = ({ body }) => (
  <div>{render(body)}</div>
)
といった感じで使用することで、microCMSから取得したHTMLにmaterial-uiのスタイルを適用してレンダリングできます。
フォーム
Netlify Formsを利用すると静的サイトでもフォーム機能を利用することができます。
基本は<form>タグのアトリビュートにnetlifyかdata-netlify="true"と記載するだけで使用できるのですが、Gatsbyのようにビルド時にHTMLを生成する場合はNetlifyがビルド時に上記アトリビュートを読み取れないので、以下のようにname="form-name", value=(formのnameの値)と置いたinputタグを挿入する必要があります。
const Form: React.FC = () => (
  <form name="contact" method="POST" data-netlify="true">
    <input type="hidden" name="form-name" value="contact" /> {/* この行を追加 */}
    <p>
      <label>Your Name: <input type="text" name="name" /></label>   
    </p>
    <p>
      <label>Your Email: <input type="email" name="email" /></label>
    </p>
    <p>
      <label>Your Role: <select name="role[]" multiple>
        <option value="leader">Leader</option>
        <option value="follower">Follower</option>
      </select></label>
    </p>
    <p>
      <label>Message: <textarea name="message"></textarea></label>
    </p>
    <p>
      <button type="submit">Send</button>
    </p>
  </form>
)
また、ReCAPTCHAを利用する場合もformタグにdata-netlify-recaptcha="true"を記述したうえで<div data-netlify-recaptcha="true"></div>という空のdivタグを置けばNetlify側でReCAPTCHAを作成してくれるのですが、これもGatsbyでは読み取ってくれないので、react-google-recaptcha等のライブラリで実装する必要があります。
この辺は以下のようにラップしたフォームコンポーネントを作成しておくのが便利です。
import React from 'react'
import { makeStyles, Button } from '@material-ui/core'
import ReCAPTCHA from 'react-google-recaptcha'
const useStyles = makeStyles(theme => ({
  button: { marginTop: theme.spacing(2) },
}))
type Props = {
  name: string
  action: string
  className?: string
}
const Form: React.FC<Props> = ({ children, name, action, className }) => {
  const classes = useStyles()
  return (
    <form
      name={name}
      action={action}
      method="POST"
      data-netlify="true"
      data-netlify-recaptcha="true"
      className={className}
    >
      <input type="hidden" name="form-name" value={name} />
      {children}
      <ReCAPTCHA sitekey={process.env.GATSBY_SITE_RECAPTCHA_KEY ?? ''} />
      <Button type="submit" variant="outlined" className={classes.button}>
        送信
      </Button>
    </form>
  )
}
export default Form
ビルド時・フォーム受信時等に通知する
Netlify Functionsにはビルドやフォーム受信等のイベントに反応して処理をトリガーする機能があります。
Trigger serverless functions on events
特定のファイル名(デプロイ成功時ならdeploy-succeeded.js等)でfunctionを作成することで、ビルド完了時にSlackに通知するといった連携機能を簡単に作成できます。
// 例:デプロイ成功時にSlackに投稿する
// Slack App の作成方法等は省略
require('dotenv').config()
const { WebClient } = require('@slack/web-api')
const slack = new WebClient(process.env.SLACK_TOKEN)
exports.handler = async (event, context) => {
  const { deploy_time } = JSON.parse(event.body).payload
  try {
    await slack.chat.postMessage({
      text: `ホームページが更新されました! (ビルド時間 : ${deploy_time} 秒)`,
      channel: process.env.SLACK_CHANNEL_INFO,
    })
    return { statusCode: 200, body: 'ok' }
  } catch (error) {
    console.error(error)
    return { statusCode: 500, body: 'Internal server error' }
  }
}
まとめ
ホームページをWordPressからGatsbyに移行して幸せになったお話と、ちょっとしたポイント集でした(まとまりがない…)。
具体的な構築方法は今回はあまり触れなかったのですが、Gatsbyは最近話題のフレームワークということもあって結構ドキュメント・記事が充実している印象で、調べながら割と簡単にホームページを作れました(例:gatsby タグの記事一覧 - Qiita)。
公式ドキュメントは今のところ英語だけですが、日本語訳も現在作成中(2020/08/05現在)のようなので楽しみです。
gatsbyjs/gatsby-ja: Japanese translation of Gatsbyjs.org