Headless WordPress with Next.js Template> wp-headless-template

WordPressが持つ機能はそのままにヘッドレスCMSとして使い、Next.jsの開発体験も維持する。ハイパフォーマンスかつ拡張性のあるWebサイトを制作するためのテンプレート。

Organization

Coppie Packages

Packages in Coppie

GitHub

Nao Komura

UI Designer, PdM, Web Developer @ Emitgnos ?? Freelance

  • v0.1.0/
  • Apache License 2.0
  • 0 issues
  • 1 discussions

目次目次を閉める
  1. Headless WordPress with Next.js Template
    1. Features
      1. WordPressの機能はそのまま
      2. Docker DesktopとNode.jsだけで完了する環境構築
      3. Next.js App Routerの採用
      4. より良い開発体験の追求
    2. Getting Started
      1. 事前準備が必要なツール
      2. テンプレートの利用方法
    3. Development Notes
      1. Node.js/npmのバージョン管理
      2. GitHub Actionsのローカル実行
      3. wp:initでインポートされるWordPressデータの変更
    4. Production Deployment
      1. Next.jsのホスティング
      2. WordPressのホスティング
      3. 環境変数と定数の設定
    5. FAQ & Troubleshooting
      1. wp-block-template / wp-headless-templateのどちらを使えば良い?
      2. `npm run wp:init`でWordPressが起動しない
      3. WordPressから投稿のプレビューができない
      4. WordPressのREST APIエンドポイントが機能しない
      5. その他
    6. License
Headless WordPress with Next.js Templateのアイキャッチ画像

Headless WordPress with Next.js Template

WordPressが持つ機能はそのままにヘッドレスCMSとして使い、Next.jsの開発体験も維持する。ハイパフォーマンスかつ拡張性のあるWebサイトを制作するためのテンプレート。

🔗 Demo Site: wp-headless-templateHeadless WordPress with Next.js Templatehttps://wp-headless-template.coppie.app

Features

WordPressの機能はそのまま

WordPressをヘッドレスCMSとして利用する際、実装工数などからWordPressが搭載している機能が失われがちですが、本テンプレートではWordPressの機能を維持することを意識して制作しています。

  • ✅ 投稿のプレビュー
  • ✅ パスワード付きの投稿
  • ✅ カスタム投稿タイプ
  • ✅ カスタムフィールド
  • ✅ タグとカテゴリーによる絞り込み検索
  • 🏃 テキストによる検索(WIP)
  • 🏃 予約投稿(WIP)
  • 💭 リクエストに応じて他にも

このリスト内の一部機能に対応するために独自のWordPressプラグインを同梱しています。

📦 add-custom-post-type

カスタム投稿タイプおよび、カスタムフィールドをコード上で簡単に追加するためプラグイン

このユースケースに対して一般的に用いられるプラグインは、管理画面上からGUIでフィールド等を定義し、その定義はWordPressのデータベースに保存されますが、データベースでの管理ではなく、コードベースでの管理がしたかったため作成したプラグインです。

以下のフィールドタイプをサポートしています。

'text'|'number'|'select'|'checkbox'|'image'|'url'|'loop'

📦 change-post-preview

投稿のプレビューリンクを変更して、フロントエンドのテーマで安全にプレビューするためのプラグイン

WordPressのプレビューを特定のURLにリダイレクトすることで、プレビュー機能をWordPress以外のフロントエンドで利用できるようにします。Next.js側ではDraft Modeを用いてプレビューの体験を向上します。

また、アプリケーションパスワードの万が一の漏洩に備えて、このプラグインを用いて必要最小限の権限を持つロールを作り、そのロールが指定されたユーザーのアプリケーションパスワードをプレビュー用に使用することでリスクを低減させます。

Docker DesktopとNode.jsだけで完了する環境構築

wp-envを最大限活用することで、柔軟なWordPressローカル環境を構築しました。

wp-envを動作させるのに必要なDocker DesktopとNode.jsの2つがインストール済みなら、わずかなコマンドを実行するだけですぐにWordPressとNext.jsの開発環境を構築できます。

Next.js App Routerの採用

React Server Components(RSC)を基準としたApp Routerを採用し、ほとんどのコンポーネントをサーバーコンポーネントとして実装しました。

複数のレンダリングパターン(Static Rendering / Dynamic Rendering / Streaming / Client-side rendering)を適切に使い分け、ハイパフォーマンスかつ機能的なWebサイトを開発するための良い出発点となるように設計しています。

このテンプレートで、App Routerの初見では分かりづらいSuspense, 'use client', searchParams, cookiesなどについて実例をもとに理解を深めることができます。

より良い開発体験の追求

RSCの副次的な恩恵として、コンポーネント単位でサーバーサイドのfetchリクエストを行えるようになりました。
今まではWordPressをヘッドレスCMSとして使う際に、WordPress REST APIをGraphQLに変換するプラグインを導入する人も多かったと思いますが、RSCを使う事で標準のREST APIを効率的に使えるようになります。

また、TypeScriptからWordPress REST APIを利用する際に適切な型を扱えるようにwp-typesを導入し、より具体的な型を受け取れるように拡張しました。

さらに、よく使うWordPress REST APIのエンドポイントへのfetchをラップした以下のような関数を用意しており、これらの関数を使うことで何もコードを書かずに型の恩恵を受けることができます。もちろん、fetchWp()を使って新しいリクエストを作るのも簡単です。

typescript
// ~/helpers/api
import { fetchWp } from '~/helpers/client'
import type { FetchWpParams } from '~/helpers/client'
import type { Post } from '~/types/wordpress'

export const getTotalPagesOfPosts = async (params?: FetchWpParams) => {
  const { headers } = await fetchWp<Post[]>({
    path: '/posts',
    params: {
      context: 'embed',
      ...params,
    },
  })
  const totalPages = Number(headers.get('x-wp-totalpages'))

  if (Number.isNaN(totalPages)) return 0
  return totalPages
}

その他にもテストにVitest、コードフォーマットにPrettier、コード検証にESLint、スタイリングにTailwind CSS、CI/CDにGitHub Actionsを採用しており、初期設定を行う必要なくすぐにこれらのツールを活用できます。

Getting Started

事前準備が必要なツール

テンプレートの利用方法

  1. git cloneコマンド等でリポジトリをクローン
  2. npm installを実行して依存関係をインストール
  3. npm run wp:initを実行してWordPressを初期化
    • WordPress: http://localhost:8888
    • wp:initの実行前にDocker Desktopが起動している必要があります
    • wp:init後は、npx wp-env start/stopでDockerコンテナを起動/停止できます(その他のコマンドはwp-env Command Referenceを参照)
  4. .env.sampleをコピーして.envを作成
  5. npm run devを実行してNext.jsを起動
yaml
# 管理者ユーザーの情報
login: admin
password: password
email: [email protected]
(任意)WordPressの投稿プレビューをフロントエンドで行う
  1. .wp-env.override.example.jsonをコピーして.wp-env.override.jsonを作成
  2. 任意のトークンジェネレーターでトークンを生成して.env -> DRAFT_MODE_TOKEN.wp-env.override.json -> WP_DRAFT_MODE_TOKENに追記
    • 開発環境に限ってはどんな文字列でも構わないので、お好きな文字列を設定しても問題ありません
    • ここで設定したトークンは投稿のプレビューを行う際にリダイレクトするNext.jsのRoute Handlerで、トークンが同じ値であるか検証するために使用されます
  3. npm run wp:previewを実行して、出力されるアプリケーションパスワード.envWORDPRESS_APP_PASSWORDに追記
    • wp:previewはpreviewユーザーの作成/設定を実行するコマンドです。コマンドの本体はscripts/wp-prepare-preview.shに記述されています
yaml
# プレビューユーザーの情報
login: preview
password: password
email: [email protected]
(任意)お問い合わせフォーム等でのメール送信を行う
  1. Resendに登録してAPIキーを取得
    • 本テンプレートではResendを使用しましたが、SendGridなど他のメール配信サービスや、Tallyなどのフォームサービスを利用しても良いでしょう
  2. .envRESEND_API_KEYを設定
    • 必要に応じてRESEND_FROM_EMAILにメールアドレスを設定すると、そのメールアドレスでのメール送信が行えます
    • Resendの管理画面で送信内容が確認できますが、Resendでのドメイン設定等が完了していないと実際には届きません
(任意)WordPressテーマ/プラグインの開発を行う
  1. Composerをインストール
  2. cdコマンドでwordpressディレクトリに移動
  3. composer installを実行して依存関係をインストール
Note

Note

[!NOTE] 現状、このプロセスはPHP CodeSnifferでPHPのリンティングおよびフォーマッティングを行うためだけに用意しているため、WordPressテーマ/プラグインの開発自体はこのプロセスを実施しなくても可能です。

また、.phpcs.xml に記載のPHP CodeSnifferの設定はWordPressのコーディング規約に準拠しています。

この規約では、グローバルな関数や変数には独自の接頭辞を付けることが求められています。
デフォルトではcustomという接頭辞を設定していますが、必要に応じてWordPress.NamingConventions.PrefixAllGlobalsのprefixesに追加してください。

Development Notes

Node.js/npmのバージョン管理

.node-versionに記載のバージョンのNode.jsを使用します。

これはnodenvを使用する前提で配置されたファイルです。そのため、nvmvoltaなどを使用する場合は、記載のバージョンを別途.nvmrcpackage.jsonに追加してください。

また、npmのバージョンはCorepackで管理します。CorepackはNode.js v14.19.0以降に同梱されています。

Corepackでnpmを管理下に置くにはcorepack enable npmを実行します。

ただ、Corepackはまだ実験的なツールであり、npmのバージョン差異による影響も限定的であるため、特に本プロジェクトにおいて導入は必須ではありません。

GitHub Actionsのローカル実行

./github/workflowsにGitHub Actionsの設定ファイルを配置しています。

ローカルでのジョブの実行確認にはactを使用できます。 また、ジョブ内で使用する環境変数は.secretsに記載することでactから参照できるようになります。

wp:initでインポートされるWordPressデータの変更

wp:initでインポートされるデータはtheme-test-data-jaを利用しています。

このデータはWP-CLIのwp db exportコマンドでwordpress/migrations/seed.sqlにエクスポートされています。

wp:initはこのSQLファイルをインポートして初期データを読み込んでいます。そのため、各々で都合の良いWordPressデータをwp db exportコマンドでエクスポートすることで、wp:initでのインポートデータを変更することができます。

また、WP-CLIのwp db import/exportコマンドをこの用途に使い易いように、npm scriptsにwp:import/wp:exportコマンドを用意しています。このラッパーコマンドではexportが不要と思われるテーブルは除外しているため、必要に応じて編集してください。

Production Deployment

wp-headless-templateを本番環境で使用する際は、以下の項目を確認してください。

Next.jsのホスティング

結論から言うと、Next.jsはVercelでホスティングするのが最も考えることも少なく、確実で、容易です。

Next.jsのセルフホスティングについては幾つかの方法がありますが、その際にはRevalidationについて考慮する必要があります。

Pages RouterではISR(Incremental Static Regeneration)と呼ばれているデータ再検証の仕組みは、Pages Routerにおいては静的なWebページ生成に留まっていましたが、App Routerではより広範にキャッシュされたデータを再検証する機能として存在しています。

そのため、App RouterにおけるRevalidationはWebページの静的レンダリングだけでなく、サーバーサイドでの動的レンダリングにも適用される仕組みとなっており、Next.jsのパフォーマンスを最大限発揮するためにはこの機能を適切に活用する必要があります。

現時点ではVercel以外でRevalidationを確実に動作させることは難しいと思いますが、SSR(Server Side Rendering)に限ってWebサイトを構築する場合は、Cloudflare PagesのEdgeランタイムで動かしてパフォーマンスをカバー出来るかもしれません。

Warning

Warning

[!WARNING] このテンプレートがCloudflare Pagesで動作するかは未検証です。

以下のコメントからはRevalidationは動作しないように読み取れます。
[⚡ Feature]: Why does Cloudflare Pages still not support OnDemandRevalidate, even after Next.js 13 introduced a new revalidate function that runs on the edge? · Issue #292 · cloudflare/next-on-pagesDescription Next.js 13 introduced revalidatePath, a feature that will run on the edge and revalidate the page on demand. import { NextRequest, NextResponse } from 'next/server'; import { revalidate...https://github.com/cloudflare/next-on-pages/issues/292#issuecomment-1577377894

ただ、Cloudflare側でレンダリング結果をキャッシュすることで、Time-based Revalidationのような仕組みは実現できるのではないかと考えています。 同様に、On-demand RevalidationのようなこともCMSのデータ更新をフックにしてCloudflareのキャッシュを削除することで実現できるかもしれませんが、こちらも未検証なので保証はできません。

また、このテンプレートは/news/[id]ページに関しては静的生成して最大のパフォーマンスを発揮できるようにしていますが、Cloudflare Pagesで動かす等の事情によりSSRに移行する場合は、Preloading Dataパターンを参考にすると高いパフォーマンスを維持できるかもしれません。

あるいは、Static Exportで静的にWebサイトを出力する場合であれば、HTML/CSS/JS等の静的アセットを提供できる任意のWebサーバーにホスティングできます。

Webサイトの性質によっては静的な出力で十分な場合もありそうですが、この場合はRevalidationもSSRも出来ません。

そのため、データの更新を反映させるには、CMSのデータ更新をトリガーに静的アセットを再生成する必要があります。 また、SSRが出来ないと記事のプレビューも出来ないため、プレビューについてはクライアントコンポーネントとして実装し直さないといけません。

詳しい情報は以下のドキュメントを参照してください。
Building Your Application: Deploying | Next.jsLearn how to deploy your Next.js app to production, either managed or self-hosted.https://nextjs.org/docs/app/building-your-application/deploying

WordPressのホスティング

WordPressのホスティングは多様な選択肢がありますが、デモサイトではさくらのレンタルサーバーを使用しています。
さくらのレンタルサーバーを使用している理由は、何より安価であることと、WP-CLIがデフォルトで使えるくらいです。

本テンプレートを動かすにあたってWordPressに対しての特別な要件はないので、お好きなホスティングサービスをご利用ください。

また、任意ではありますが、SSHでのファイル操作が可能な場合、定義済みのGitHub Actionsのジョブ(.github/workflows)によって特定のGitHubのアクションをトリガーに自動的にWordPress関連のファイルを更新することができます。

SSHの要件にくわえて、複数のWordPressのホスティングが可能な場合、ステージング環境用のWordPressを用意することで、GitHubでのプルリクエスト時にステージング環境にデプロイすることも可能です。

環境変数と定数の設定

開発環境では.env.wp-env.override.jsonにて環境変数と定数を設定していますが、本番環境ではデプロイする環境に合わせて適切な方法で設定してください。

.wp-env.override.jsonのconfigに指定する定数は、wp-config.phpにてdefine()で定義するのと同じ扱いとなっています。以下のようにwp-config.phpに追記することで定数を設定できます。

php
define( 'WP_FRONTEND_HOST', 'wp-headless-template.coppie.app' );
define( 'WP_DRAFT_MODE_TOKEN', 'hs32rTl5cpg3V4sPXSmQ6U8MEIssmB2V6YOszUhS' );

必須ではないですが、本テンプレートでCI/CDとして利用しているGitHub Actionsにもシークレットを設定すると、WordPress関連のファイルをSSHで自動的にデプロイできるようになります。

.github/workflows/deploy-production.yml.github/workflows/deploy-staging.ymlにて、以下のシークレットを使用しています。

yaml
SERVER_SSH_PRIVATE_KEY: # SSH秘密鍵
SERVER_HOST: # SSHホスト
SERVER_USER: # SSHユーザー
SERVER_PORT: # SSHポート
SERVER_PATH: # 本番環境のWordPressディレクトリまでのパス (例: `/home/coppie/www/headless-wp`)
SERVER_STAGING_PATH: # ステージング環境のWordPressディレクトリまでのパス (例: `/home/coppie/www/headless-wp-staging`)

FAQ & Troubleshooting

wp-block-template / wp-headless-templateのどちらを使えば良い?

それぞれのテンプレートの違いを把握することが選択の判断材料になると思います。
細かい違いは色々とありますが、明らかな違いとしては以下のような項目が挙げられそうです。

wp-block-template

  • WordPress管理画面からノーコードツールのような感覚でWebサイトの内容を変更できる
  • ホスティングのために用意するサーバーが1つで済むため、小〜中規模であればランニングコストが抑えられる
  • WordPressテーマ開発者にとっては、テーマ開発の知識がそのまま活かせる
  • Webサイトの内容次第では、HTMLとCSSを書くだけでWebサイトを構築できる

wp-headless-template

  • 開発の柔軟性・拡張性が高い
  • 高速なページ遷移やインタラクティブなUIを実装しやすい
  • 大規模なアクセスを効率よく処理できるため、コスト面を含めスケーラビリティが高い
  • 実装方法次第では、セキュリティリスクを大きく低減できる

大雑把に言えば、小〜中規模のアクセスが見込まれるWebサイトで、ランニングコストもあまりかけられないような場合においては、wp-block-templateは手離れしやすいWebサイトを開発できる良い選択となりそうです。

Tip

Tip

[!TIP] 上記のリストには含めていませんが、テンプレート自体はサードパーティのプラグインをWP Multibyte Patch以外使用しておらず、不具合・脆弱性対応なども最小限に抑えられると思います。

反対に、wp-headless-templateは大規模なアクセスが見込まれるケースや、セキュリティが重視されるケース、Webサイトの拡張性を最大化したいケースなどに適した選択です。 前者2つのケースにおいてはwp-block-templateでも対応可能ですが、最後の「Webサイトの拡張性を最大化したいケース」への対応はwp-headless-templateでないと困難を極めます。

Tip

Tip

[!TIP] 具体的には、複数のデータソースやWebアプリケーション的な機能を持つWebサイトの開発が難しくなるはずです。

例えば、Shopify Storefront APIを使ってヘッドレスにECサイトを構築してその中にWordPressを使ったブログも含めたり、ユーザーアカウントのサブスクリプション状態に応じて記事の閲覧権を付与したり、そういった要件がこれに該当します。

また、個々の開発者のスキルセットに依存するとは思いますが、WordPressテーマ/プラグインの開発しか経験がない場合はwp-headless-templateを使いこなすまでの道のりは険しく、一方で、普段からNext.jsなどでWebアプリケーションを開発しているような方にとってはwp-block-templateは煩わしさを感じる部分も多いと思います。

とはいえ、それぞれのテンプレートで採られている手段を1から開発するのに比べれば、相当な時間短縮が図れるのは間違いないので、Webサイトの要件に応じて各テンプレートを選び、それぞれの開発手法にチャレンジしてみるのも良いのではないかと思います。困ったことがあれば気軽にご相談ください。


npm run wp:initでWordPressが起動しない

まず、Docker Desktopが起動しているか確認してみてください。

Docker Desktopが起動しているにも関わらずnpm run wp:initでWordPressが起動しない場合、wp-envを別のプロジェクトで使っている可能性があります。

別のプロジェクトで使っている場合は、そのプロジェクト内でnpx wp-env stopを実行してから、再度npm run wp:initを実行してください。

それでも解決しない場合は、npx wp-env clean allあるいはnpx wp-env destroyを実行してから再度お試しください。

WordPressから投稿のプレビューができない

まず、READMEの 「(任意)WordPressの投稿プレビューをフロントエンドで行う」 が正しく実施できているか確認してみてください。

実施済みであるにも関わらずWordPressから投稿のプレビューができない場合、一度ログアウトしてから再度ログインすると治ることを確認しています。

WordPressのREST APIエンドポイントが機能しない

まれに/wp-json/wp/v2/postsなどのREST APIエンドポイントが機能しないことがあります。

npm run wp:restartでWordPressの再起動を行なったり、管理画面の「設定->パーマリンク」の「変更を保存」ボタンを押すことで治ることを確認しています。

その他

その他、不具合や疑問点があれば、にて気軽にご相談ください。

License

本テンプレートのライセンスはApache License 2.0に基づきます。詳細はLICENSEを参照してください。

また、WordPressのGPL(GNU General Public License)によって認められる権利を除き、NOTICEに記載のように、本テンプレート内のソースコードを再配布することはできません。