Next.js と Bogo プラグインを用いた静的な Headless WordPress サイトの作り方

WordPress には PolylangWPMLBogo といった多言語でコンテンツを管理するためのプラグインが数多く存在します。これらのプラグインと、その拡張プラグインを組み合わせることで、数多くの Web サイトが多言語で配信されるようになりました。

Headless / Jamstack で Web サイトを公開する場合にも、多言語に対応するという要件を避けることはできません。しかしできるならば、これまでの Web サイト構築や運用で築かれたノウハウや資産を活用していきたいところです。

そこで今回は、Bogo プラグインで多言語化された Web サイトを Next.js で静的化した Web サイトで利用する方法について簡単に紹介します。

WP API から言語別にデータを取得する

Bogo で作成された記事は、WP API から取得することができます。

ただし filter クエリを利用する必要がありますので、以下のようなコードを追加する必要があります。

上記のプラグインのコードは以下のような内容です。filter クエリで WP Query の検索ができるように作られています。

<?php
/**
 * Plugin Name: WP REST API filter parameter
 * Description: This plugin adds a "filter" query parameter to API post collections to filter returned results based on public WP_Query parameters, adding back the "filter" parameter that was removed from the API when it was merged into WordPress core.
 * Author: WP REST API Team
 * Author URI: http://v2.wp-api.org
 * Version: 0.1
 * License: GPL2+
 **/
add_action( 'rest_api_init', 'rest_api_filter_add_filters' );
 /**
  * Add the necessary filter to each post type
  **/
function rest_api_filter_add_filters() {
	foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
		add_filter( 'rest_' . $post_type->name . '_query', 'rest_api_filter_add_filter_param', 10, 2 );
	}
}
/**
 * Add the filter parameter
 *
 * @param  array           $args    The query arguments.
 * @param  WP_REST_Request $request Full details about the request.
 * @return array $args.
 **/
function rest_api_filter_add_filter_param( $args, $request ) {
	// Bail out if no filter parameter is set.
	if ( empty( $request['filter'] ) || ! is_array( $request['filter'] ) ) {
		return $args;
	}
	$filter = $request['filter'];
	if ( isset( $filter['posts_per_page'] ) && ( (int) $filter['posts_per_page'] >= 1 && (int) $filter['posts_per_page'] <= 100 ) ) {
		$args['posts_per_page'] = $filter['posts_per_page'];
	}
	global $wp;
	$vars = apply_filters( 'rest_query_vars', $wp->public_query_vars );
	// Allow valid meta query vars.
	$vars = array_unique( array_merge( $vars, array( 'meta_query', 'meta_key', 'meta_value', 'meta_compare' ) ) );
	foreach ( $vars as $var ) {
		if ( isset( $filter[ $var ] ) ) {
			$args[ $var ] = $filter[ $var ];
		}
	}
	return $args;
}

このコードを動くようにすることで、以下のような API コールで言語別の記事を取得できるようになります。

日本語のみ: wp-json/wp/v2/posts?filter[lang]=ja
英語のみ: wp-json/wp/v2/posts?filter[lang]=en
言語コードベース: wp-json/wp/v2/posts?filter[lang]=en_US

Next.js で言語別にページを静的生成する

REST API からデータを取得できるようになったことで、あとは Next.js からその API を呼び出してページを生成するだけとなりました。

Next.js の i18n モジュールについて

Next.js には Version10 から多言語用のモジュールが用意されています。しかしこちらの機能は next export で静的化した場合には利用することができません。

そのため今回は、getStaticPath および getStaticProps でページを生成していく方法を紹介します。

/pages/ に言語別でファイルを作成する

Next.js では /pages に配置されたファイルを元にURLが生成されます。

そこで英語のコンテンツを追加する場合は /pages/en/index.tsx、英語のサイトに日本語を追加する場合は、/pages/ja_JP/index.tsx のようにページを追加しましょう。

export const getStaticProps = async () => {
  const posts = await fetch('https://example.com/wp-json/wp/v2/posts?filter[lang]=ja')
  return {
    props: {
      posts
    }
  }
}
export default function Home({posts}) => {
  return (
   <ul>
      {posts.map(post => (
        <li key={post.ID}><Link href={`/ja_JP/{post.slug}`}>{post.title.rendered}</Link></li>
      )}
   </ul>
  )
}

続いて index.tsx と同じディレクトリに [slug].tsx を配置し、そこで各投稿ページを作成します。

export const getStatisPaths = async () => {
  const posts = await fetch('https://example.com/wp-json/wp/v2/posts?filter[lang]=ja')
  return {
    fallback: false,
    paths: posts.map(post => ({
      params: {
         slug: decodeURI(post.slug)
      }
    })
  }
}
export const getStaticProps = async ({params}}) => {
    const slug = typeof params.slug === 'string' ? params.slug : params.slug[0]
  const posts = await fetch('https://example.com/wp-json/wp/v2/posts?filter[lang]=ja&slug={slug}')
  return {
    props: {
      post: posts[0]
    }
  }
}
export default function Page({post}) => {
  return (
   <div>
   <h1>{post.title.rendered}</h1>
   </div>
  )
}

実運用時には、getStatisPaths で全てのページを取得するように処理を記述する必要があることに注意してください。fallback を利用することで動的に生成することも Next.js では可能ですが、こちらを利用する場合には next export を利用した SSG はできなくなります。

1言語分のページセットが揃えば、あとは /pages/配下にfilter[lang] の値を変更したものをそれぞれ配置していけば OK です。

これで WordPress で作成した多言語コンテンツのデータを元に、Next.js の SSG モードでページを生成することができました。

Appendix: Algolia を用いた検索について

Bogo で多言語化したコンテンツを検索したい場合、Algolia に検索インデックスを用意することをお勧めします。WP Search with Algolia プラグインを利用することで、記事更新時に検索インデックスの作成や更新が自動で行われます。

しかし、上記のプラグインを使うだけですと、どの言語の投稿かを判別する属性が Algolia 側に送られないために全ての言語の記事が Algolia Instantsearch などの検索結果に表示されます。

Bogo を使っている場合、カスタムフィールドにその記事の言語属性が保存されていますので、その値を Algolia へのインデックスに追加することで、言語別の検索を可能とします。

Search with Algolia Bogo extensionプラグインを利用することで、記事更新時に Bogo の言語設定を Algolia のインデックスに自動で反映させることができます。

検索する際は、以下のような検索処理を Instantsearch.js で実行しましょう。

index.search("about next", {
 "getRankingInfo": true,
 "analytics": false,
 "enableABTest": false,
 "hitsPerPage": 10,
 "attributesToRetrieve": "*",
 "attributesToSnippet": "*:20",
 "snippetEllipsisText": "…",
 "responseFields": "*",
 "maxValuesPerFacet": 100,
 "page": 0,
 "facets": [
  "*",
  "locale",
 ],
 "facetFilters": [
  [
   "locale:en_US"
  ]
 ]
});

Bogo + Next.js でシンプルな多言語 Jamstack サイトを作ろう

Bogo を利用することで、最小限のカスタマイズで WP API からも利用できる多言語コンテンツ管理機能を手に入れることができます。また、今回は Netlify などでも利用できる例として SSG で紹介しましたが、Next.js はコアに多言語化機能も実装されているなど、より多言語サイトのフロントエンドを構築しやすいフレームワークです。

Jamstack 化することで、WordPress テーマにどのように多言語機能を組み込むかという部分を考慮する必要が少なくなり、また Next.js などであれば多言語化についても比較的低コストで実現できます。

「ページを生成する getStaticProps でデータを取得する際に、DeepL や Google / AWS などの翻訳 API を経由させて、WordPress 側で未翻訳の記事は自動翻訳で対応する」などのアイディアも活用することができますので、ぜひ一度ご自身でお試しください。