WordPressでのHeadlessCMS化についての考察

この記事は [Ateam Lifestyle Inc. Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ateam-lifestyle) 23日目の記事です。

## はじめに
皆さんこんにちは！
CMS使ってますか？ 特に、WordPressを使っている人は多いと思います。
Googleが[ページエクスペリエンス](https://developers.google.com/search/blog/2020/11/timing-for-page-experience)をSEOの指標に入れるとなって、WordPress重いなあ、なんとかしたいなあと思った人もいるかと思います。

そんなときにちょっと考えてみてほしいのがWordPressをHeadlessCMS化してみるということです。
今回はメディアサイトを作る機会もあったので、その中で考えたことを記事にしました。

この記事の対象者は以下の方を想定しています。

* WordPressを現在触っている人
* HeadlessCMS興味あるけどよくわからないという人

## 概要
HeadlessCMSってそもそもなんぞや？という方もいらっしゃるかもしれないので、簡単に説明をします。

そもそも、CMS（Content Manegement System）とは、Webサイトのコンテンツを構成するテキストや画像、デザイン・レイアウト情報（テンプレート）などを一元的に保存・管理するシステムのことを指します。

そのCMSにHeadlessというのがつくとどうなるか？　図を作成しました。

|CMS | HeadlessCMS |
| --- | ---------- |
| ![スクリーンショット 2021-12-22 8.17.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/2dd1e442-5cfb-6806-d347-c2e2cf46ee31.png)|  ![スクリーンショット 2021-12-22 8.27.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/b282fa06-7bcf-8cb5-44d0-87c347a911d4.png) |

HeadlessCMSの一番の特徴はフロントエンドとバックエンドを分けて管理できることにあると思います。
図を見れば、CMSとしてどこまで責務を持っているかが一目瞭然かと思います。
具体的にどんな時に役に立つのか、逆にどんな場面は避けたほうが良いのかを次の項で説明します。

### HeadlessCMSのメリット
分離することで、下記のメリットが得られます。

* フロントエンド側の自由度を高められる
* 配信対象の拡大
* フロントとバックエンドの開発分離
* アップデートの追従がしやすい。
* セキュリティの障害点を少なくできる

また、CMSのバックエンド起因で表示が遅くなってしまうこともなくなります。
自由度の高いサイトにしたい、ページを可能な限り早くする必要があるといった場合は選ぶ理由になると思います。

### HeadlessCMSのデメリット
逆にデメリットになってしまうものを紹介します。

* 開発が専門的になり、開発工数が増加する
* APIの品質への要求が高い
    * 速度低下や高負荷対策(cache, scale)
    * 不正アクセスや攻撃への対応

特性上、技術的なコストがかかりやすいため、
明日からにでも、ブログを始めたいというような急ぎの場合や基本的にあまりカスタマイズすることはないという場合は普通のCMS
を選ぶほうが良いかもしれません。

## CMSの本番運用に求められる要件
* 料金
* 保守性
* セキュリティ
* 管理画面の使いやすさ (記事投稿だけでなく, SEO観点でのカスタマイズ性等も含む)
* [Core Web Vitals](https://developers-jp.googleblog.com/2020/05/web-vitals.html)を始めとしたUX

料金、セキュリティ、保守性は言わずもがなですね。
管理画面の使いやすさについては、前提として開発者と運用者が異なるという前提があります。
例えば、下記二つがあります。(WordPressを現在使っているCMSと仮定します。)

* WordPressの管理画面に慣れている人が別のシステムの管理画面を使ったときにどれくらい学習コストがかかるか。
* WordPressの管理画面でできること(満たしてほしいこと)が、別のサービスの管理画面でできるか。

記事の執筆効率やSEO面での開発効率にも関わってきますので、重要だと思います。

UXについては、サイト上で表現したいデザインなど、ユーザーがサイトを見たときにどう感じてほしいかと定義しています。
基本的に見てもらってなんぼのものだと思うので、SEO観点も重要になるはずです。
そのため、Googleが提唱し、SEO評価にも組み込んでいるCore Web Vitalsを例に挙げました。


### 他サービスとの比較

HeadlessCMSは[色々](https://jamstack.org/headless-cms/)あるんですが、代表的なものを取り上げます。
今回は下記観点で選びました。

* GitHubのスター数がなるべく多いもの ≒ 一般的に使われているもの

strapiとWordPressの保守性を下げている理由は、セルフホスティングがメインになるからです。
wordpressのセキュリティが低いのは、元々Headless化前提で作られていないので、デフォルトの設定だと穴が多くなってしまうためです。
その他のものについては、正直甲乙つけがたい部分が多いため、◯としました。

| サービス名 | 料金 | 保守性 | セキュリティ | 管理画面の使いやすさ | CWV |
| ---- | --| --| --|--|--|--|
|[strapi](https://strapi.io/)|◯|△|◯|◯|◯|
|[Ghost](https://ghost.org/) |◯|◯|◯|◯| ◯ |
|[microCMS](https://microcms.io/) |△|◯|◯|◯| ◯ |
|[WordPress](https://wordpress.com/ja/)のヘッドレス化 | ◯ | △ | △ | ◎ | ◯ |

今回WordPressのHeadlessCMS化に取り組む一番の理由は、管理画面の使いやすさ(カスタマイズ性含む)が大きいです。
慣れているものを使うことで学習コストを抑え、通常業務の効率を下げずに運用ができることを想定しています。
Headless化するにはややオーバースペックな部分もありますが、上記理由からWordPressを選択しています。

とはいえ、他のCMSにも良いところはたくさんあります。
各CMSについて、簡単に触ってみた所感をお伝えします。

#### strapi
オープンソースのため、[料金](https://strapi.io/pricing-self-hosted)はホスティング代だけで済ませることができます。
まだ、ホスティングしてくれるプランはありませんが、今後でるみたいです。

データ構造の設定などを割と直感的に行うことができ、自分は特に苦戦することなくすぐに操作に慣れることができました。
プラグインも今後充実していきそうなので、今一番個人的に注目しているHeadlessCMSです。

UIは下記のような感じになっています。(出典:[Strapi公式](https://strapi.io/))
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/341b3115-6ab1-c04f-aa8d-a3c48e5d7001.png" width="500" />


#### Ghost
strapi同様オープンソースのため、料金はホスティング代だけで済ませることができます。
ただ、ホスティングしてくれる[プラン](https://ghost.org/pricing/)もあります。

一番の特徴は[Stripe](https://stripe.com/jp)を使用したサブスクリプション課金の実装・メンバー管理がやりやすいことです。
会員制のメディアを運営するのであれば、おすすめできると思います。

UIは下記のような感じになっています。（出典：[Ghost公式](https://ghost.org/)）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/47de8004-7bfe-66e1-900c-faaf108273a9.png" width="500" />


#### microCMS
日本製のヘッドレスCMSです！
日本語でのサポートがあるというのが、決め手になりやすい理由ではないかと思います。
ドキュメントもやはり日本語だと読みやすいので、開発がしやすいです。
オープンソースではないため、自らホスティングすることはできませんが、サポートがこれだけ充実していれば困ることは少ないのではと思います。

[料金](https://microcms.io/pricing)は他のものと比べると若干高い気もしますが、
個人で使う分には無料で使えるプランもあるので、良いのではないかと思います。

UIは下記のような感じになっています。(出典: [MicroCMS公式](https://microcms.io/))
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/46b38890-5432-c927-84a1-963abffe8290.png" width="500" />



#### WordPress
皆さんご存知のサービスかと思います。CMSを知っている人でWordPressを知らない人はいないのでは？と思うくらい有名なサービスです。
Headless化する場合は、プラグインなどを用いて行います。
通常のWordPressでのCMS運用の場合は、一体型となっているため、セキュリティリスクが大きいですが、
今回のようにHeadless化することで、セキュリティリスクを回避しやすくなります。

例えばよくあるのは、サイトのURLのパスに/wp-adminをつけると、wordpressのログイン画面が表示されるサイトをよく見ます。
ログインIDやパスワードが漏れてしまえば、たちまち自由にサイトの更新ができてしまうというリスクを抱えていたりします。

Headless化することで、一般のユーザーからは居場所を隠蔽することができるので、セキュリティ面でのリスクを回避しやすいです。

## 設計
WordPressをHeadless化する際の設計に絞ってお伝えします。
今回は、セキュリティ面を担保するために静的サイトジェネレータを前提に考えています。
外部からAPIサーバーを秘匿するためです。

また、画像の配信についても、AWSの[S3](https://aws.amazon.com/jp/s3/)でホスティングします。

料金を抑えるために、必要のないときはサーバーを落とします。
方法としては自動監視, サーバーレス化などがあります。
今回は[Cloud Watch Event](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html)で自動起動/自動停止を行います。

そして、Headless化するためにAPIモードでWordPressを使えるように設定をします。
やり方をいくつか紹介しますが、今回はプラグインを用いて実装します。

* [Shifter](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/WhatIsCloudWatchEvents.html)
    * WordPress環境をサーバーレスで立ち上げられるサービス
* [WP REST API](https://ja.wp-api.org/)
    * WPのプラグインです。今回はこちらを使います。(ver4.7からはデフォルトで組み込まれているため、プラグインを入れなくても動きます。)

フロントエンド側は、[Next.js](https://nextjs.org/)や[Gatsby](https://www.gatsbyjs.com/)など選択肢は多いですが、今回は[Next.js](https://nextjs.org/)でSSGしたものを想定し、[CloudFront](https://aws.amazon.com/jp/cloudfront/)などのCDNでのホスティングをします。

以上を踏まえて、設計を図に落とすと、以下になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/655202fb-a5e7-af3f-9d3b-8110a7c3390c.png" width="400" />




## 実際の構築
WordPressのバックエンド部分の構築を紹介します。
[dockerでWordPressを立ち上げ](https://docs.docker.jp/compose/wordpress.html)ます。

必要事項を記載してWordPressの初期設定をします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/9232506c-0c79-b5de-67dc-4a38c01b3453.png" width="300px" />

メニューのプラグインを選択します。
[WordPress REST API (Version 2)](https://wordpress.org/plugins/rest-api/)をダウンロードします。

ダウンロードしたzipをアップロードしてください。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/e20dae44-ddea-bdc5-9a12-bc65c3441f9a.png" width="300px" />

アップロードすると以下のような形で進みます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/8c1533db-2921-2dcc-ab25-74e14b9b3dc1.png" width="300px" />

パーマリンク設定を投稿名にします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/ca723b54-5a41-f6d5-e066-a4ed218b5307.png" width="300px" />

`/wp-json/`にアクセスします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/19d6435e-cff7-b073-8c65-44db8e1243f2.png" width="300px" />

上記のような形で表示されるのがわかるかと思います。

APIの仕様については下記に載っているため、後はフロント側を構築するだけです！
[WP REST API リファレンス](https://ja.wp-api.org/reference/)

### 追記 GraphQLの場合

GraphQLで行う場合の設定方法を忘れていました！
今はHeadlessとGraphQLの相性は良いと思うので、ぜひ使ってみてもらえればと思います。

記事が長くなるので折りたたみます！
見たい方はクリックしてください！

<details>
<summary>GraphQLの場合</summary>
<div>

#### 使い方

+ [WP-GraphQL](https://www.wpgraphql.com/)のプラグインがおすすめです。
    + プラグインの設定から下記を検索して有効化しましょう！
    + ![スクリーンショット 2021-12-23 22.09.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/37aa3cc0-1f3a-d122-cfd0-8a189d4d0c0e.png)

プラグインを有効化するだけで、/graphql のパスで叩けるようになります。

動作テストはcurlで下記のように叩いてみるとわかると思います！  

`curl -X POST -H "Content-Type: application/json" --data '{ "query": "{generalSettings{ url }}" }' http://localhost:8000/graphql`

また、管理画面から直接触ることもできます。
サイドメニューにGraphQLが表示されていると思うので、そちらをクリックします。

こんな感じで見ることができます！↓

![スクリーンショット 2021-12-23 22.23.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224825/080906df-b506-36a2-987c-40a62d094e3d.png)

ドキュメントは[こちら](https://www.wpgraphql.com/docs/posts-and-pages/)から見ることができますが、IDEを使って、クエリを色々書いて試してみるのが一番早いと思います！

#### やっておいたほうが良いこと

+ GraphQLを使う場合はRESTAPIが必要なくなるので無効化しましょう。
+ 設定からRestrict Endpoint to Authenticated UsersをONにしましょう。
  + 認証については[こちら](https://www.wpgraphql.com/docs/authentication-and-authorization/)を参照してください。

</div>
</details>

### その他やっておいたほうがいいこと

* 使わないエンドポイントを削除する
    * functions.phpに対して下記のように追加することで対応できます。
        * rest_endpointsというフィルターフックを用いて削除できます。

``` php
    add_filter( 'rest_endpoints', 'wcl_filter_rest_endpoints');
function wcl_filter_rest_endpoints( $endpoints ){
    if ( isset( $endpoints['/wp/v2/posts'] ) ) {
        unset( $endpoints['/wp/v2/posts'] );
    }
    return $endpoints;
}
```

* 記事を表示させない設定
    * APIだけしか使わないのでフロント部分にはアクセスできない状態にします。
    * 必要最低限のファイルのみ残します。
        * functions.php
        * index.php
        * style.css
* 認証の設定
    * [JWT Authentication for WP REST API](https://ja.wordpress.org/plugins/jwt-authentication-for-wp-rest-api/)
        * APIに対して認証を必須とすることができます。
            * .htaccessとwp-config.phpの書き換えが必要になるので、公式ドキュメントを見て対応する必要があります。
            * /wp-json/jwt-auth/v1/tokenに対してusernameとパスワードを含めてPOSTするとトークンが記載されているので、そちらのトークンを使って認証を行います。
            * 対応する際は全て、公式ドキュメントに記載されているので、そちらを参照ください。
        * また、この対応に限らず、IP制御をかけるなどの対策を行うとよりよいです。

* カスタムAPIを駆使してAPIを軽量化する
    * 必要なものだけ取れるようにして、APIをなるべく軽量化することでパフォーマンスを向上させます。
        * こちらが参考になると思うので、リンクを貼っておきます。
            * [【WordPress】WP REST APIで独自エンドポイントの作り方](https://notes.sharesl.net/articles/751/)

## まとめ
* WordPressのHeadless化は考えることが多くて、辛い部分もそこそこある。
* とはいえ、プラグインが使えるので、意外と楽ができたりすることもある。
* 自分でAPIをカスタマイズするのが前提になりそう。

皆さんもぜひ一度Headless CMSを使ってみてください！

明日は@tsutormさんの記事です！お楽しみに〜！

## 参考文献
+ [4種類のHeadlessCMSを試してみた感想（wordpress/strapi/contentful/microCMS）](https://qiita.com/to4-yanagi/items/4e431b99b78401ef65ca)
+ [WordPressをヘッドレス化する時に気になるセキュリティ問題](https://zenn.dev/quocca/articles/d3f00b87020da7)
+ [バズっても安心『落ちないWordPressサイト』を実現する方法](https://blog.mmmcorp.co.jp/blog/2020/08/20/How_to_make_a_High-Availability_WordPress_on_AWS/#outline__4_2_1)
+ [WordPress REST APIでイマドキHeadless CMS](https://doocts.com/3373)
+ [WordPress REST APIについてまとめてみた](https://webcraftlog.net/about-wordpress-rest-api/)
+ [WordPress をヘッドレス化するメモ](https://zenn.dev/ysmtegsr/scraps/81dff7c542d694)
+ [Headless CMSとWordPressの囚人
](https://qiita.com/teradonburi/items/fd2c34a52c0c4cfd0d22)