title | emoji | type | topics | published | publication_name | ||
---|---|---|---|---|---|---|---|
2万超のコンテンツを抱える大規模サイトをWordPressからmicroCMSに移行した記録 |
📝 |
tech |
|
true |
psi |
プラスクラス・スポーツ・インキュベーション株式会社 エンジニアキャプテンの GORAN です。
本記事は、実務案件(某プロスポーツクラブ様の公式サイトフルリニューアル)で実施した大規模なコンテンツ移行の記録です。
:::message 本記事の目的は実際にやったことの共有&記録です。ベストプラクティスを紹介する記事ではございません。「こんなやり方あるよ!」のコメントいただけたら嬉しいです。 :::
このサイトリニューアルは microCMS 様の事例紹介コンテンツにも掲載いただきましたので、そちらもぜひご覧ください。
https://blog.microcms.io/usecase-cerezo/
- 移行データは WordPress のエクスポート機能で生成された XML
- XML→JSON の変換は PHP
- JSON→microCMS の POST は POST API を Node.js から使用
- サンプルコードはすべてニュース記事コンテンツについてのもの
- コンテンツデータの管理:microCMS
- 過去のメディアデータのホスティング:Firebase Hosting
- サイト自体のホスティング:Vercel
- フロントエンド:Next.js (Pages Router)
:::message
サイト設計の話は別記事にする予定です。
Next.js x microCMS でページパフォーマンスも運用パフォーマンスも改善した記録を公開しました。
:::
まずは以下の記事に目を通しましょう。大筋の流れは一緒です。
https://zenn.dev/kandai/articles/f6a034d166e4c977a78e
WordPress の管理画面ツール → エクスポートから簡単に生成できます。 ただし、ファイルを分割してエクスポートできないのが難点です。 100MB を超える投稿タイプも少なくなかったので、そこそこ手元の環境にメモリがないとファイルをエディタで開くのも大変だったろうと思います。
https://wordpress.com/ja/support/export/
microCMS 自体の立ち上げの HOW TO はドキュメントがたくさんあるので割愛します。(公式のチュートリアルなど)本記事ではスキーマ定義の方針について記述します。
microCMS には非常に多くのスキーマが用意されています。 特に
- コンテンツ参照
- カスタムフィールド
- 繰り返しフィールド
は強力で、これらの組み合わせでコンテンツサイトに必要なリレーションはほぼ表現できると思います。
また、microCMS の API スキーマは JSON ファイルをインポートすることでも定義できます。 対象のサイトは WordPress に ACF を導入していたので、Export As JSON を使って JSON ファイルを出力することが容易でした。
- リニューアル前後で変わらないフィールドについてはそのまま
- リニューアルの前後で型が変わるフィールドについては、スキーマ自体は引き継がず、GUI から再定義する
- フロントで例外処理するために、移行コンテンツが存在する場合はフラグ(
is_migrated
)を立てる
リニューアル前後で変わらないフィールドについては、ACF からエクスポートした JSON を ChatGPT 経由で microCMS のスキーマ形式に変換することで、かなり時短になったと思います。
リニューアルの前後で型が変わるフィールドについては、スキーマ自体は引き継がず、GUI から再定義する方法を取りました。
また、移行コンテンツ(WordPress のデータ)が存在し、かつ、今後は新環境(microCMS)でコンテンツが増えていくデータについては、移行コンテンツにis_migrated
のフラグを立てることで、Next.js 側で表示を棲み分けられるようにしました。
:::message ニュースなどリッチエディタで管理したいコンテンツは、WordPress のリッチエディタで管理していたデータをそのまま microCMS のリッチエディタでも管理というやり方が不可能なので、「移行コンテンツを microCMS で編集することはない」という前提で、HTML 文字列としてデータ移行しています。 :::
スキーマが定義できれば、microCMS の API プレビュー(POST)からリクエストに必要な JSON 形式のデータを確認できます。この形式の JSON を用意できれば POST API で移行できるということです。
{
"title": "テキスト1",
"date": "[YYYY-MM-DDTHH:MM:SS.MS]Z",
"category": [
"参照先id1",
"参照先id2"
],
"contents": "複数行のテキストを入力\n複数行のテキストを入力",
"is_html": true,
"html_contents": "複数行のテキストを入力\n複数行のテキストを入力",
"is_migrated": true,
}
WordPress のメディアアップロード機能(/wp-content/uploads/
配下)に大量の画像・ファイルデータを保持していました。
冒頭で紹介した記事ではコンテンツデータ内のパスを書き換える手法を取っていましたが、今回は違うやり方で対応しています。
というのも、画像ファイルの一括アップロードについて、microCMS のメディア機能の仕様変更があり、アップロード後の URL をファイル名から一致させることが困難になってしまったためで、本記事の案件では uploads
ディレクトリ配下の構造を保ったまま静的ファイル用のサーバー(Firebase Hosting)にアップし、Next.js のリダイレクト機能を利用することにしました。
public
フォルダの下を愚直にデプロイ
{
"hosting": {
"public": "public",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"]
}
}
静的ファイルをホストするだけの環境なので、公式ドキュメントにある以上のことはしていません。カスタムドメインも接続せず、デフォルトのドメイン(****.web.app
)のまま運用しています。
:::message alert このやり方はプロジェクトの容量が大きくなりすぎるため、メディアファイルのバージョン管理が困難です。実際の現場では外付け SSD を複数個使用する力技で運用しています。定常的なバックアップが保持できて、バージョン管理が容易な(お金がかかりすぎない)手法がないか模索中です。 :::
next/image
で外部ドメインを指定できるように(画像最適化が必要な場合)- Path Matching によるリダイレクト処理
const nextConfig = {
// 省略
images: {
domains: [
'images.microcms-assets.io', // microCMSのメディアファイルを読み込めるように
'****.web.app', // Firebase Hostingで公開されているファイルを読み込めるように
// 省略
],
},
async redirects() {
return [
{
source: '/upload/:slug*',
destination: 'https://****.web.app/upload/:slug*',
permanent: true,
},
// 省略
]
}
// 省略
}
microCMS への画像の一括アップロードについては、API 経由で画像アップロードができるようになったみたいなので、もし今その手法を採用するなら API で対応する気がします。
:::message alert
本記事の内容とは関係ありませんが、WordPress 環境を移行する際、日本語をファイル名に含むメディアファイルが存在するuploads
配下の移行はほとんどの場合で失敗します。少なくともWP Multibyte Patchはインストールしておきましょう。
:::
旧サーバーからメディアファイルをダウンロード → Firebase Hosting へアップロード の過程で、ファイル名の文字エンコードに関する問題にも直面したので、別記事で紹介できればと思います。
エクスポートした XML は大体以下のような形をしているはずです。
※<item>
のみ抜粋しています
<item>
<title><![CDATA[ITEM_TITLE]]></title>
<link>[ITEM_URL]</link>
<pubDate>D, d M Y H:i:s +0900</pubDate>
...省略
<content:encoded><![CDATA[]]></content:encoded>
<wp:post_id>[ITEM_ID]</wp:post_id>
...省略
カスタムフィールドがある場合は以下のような形になる
<wp:postmeta>
<wp:meta_key><![CDATA[ITEM_META_KEY]]></wp:meta_key>
<wp:meta_value><![CDATA[ITEM_META_VALUE]]></wp:meta_value>
</wp:postmeta>
...省略
</item>
この XML を JSON へ変換するのに本記事の案件では PHP を使用しました。PHP を採用した理由は特になく(XML を処理させたい PC に PHP がインストールされていたから程度)、xml2js
・fs
などを用いれば Node.js でも同様に実装できます。
ここでひとつ注意があって、
:::message alert
オブジェクトのメンバ変数を参照する際、PHP では $obj->element
のように指定しますが、element
に:
(本記事とは関係ありませんが-
も)が含まれているhoge:element
のような場合、通常の参照方法を使用することがきません。
:::
解決方法としては
$obj->{'hoge:element'}
として参照する- メンバ変数の
:
を_
に置き換える(_
で繋がっているメンバ変数はアロー演算子でアクセスできる)
のふたつが考えられます。今回は前者で対応しました。
以上を踏まえれば、
`XMLの<item>をコピペ`
をもとに
`スキーマ定義後のJSONをコピペ`
に変換する処理をPHPで実装
のようなプロンプトで ChatGPT から生成されたプログラムをほとんどそのまま使用できます。 以下は、上述の注意点も加味しながら修正したもので、3-4 回のやり取りでほぼ仕様を満たすものを書き上げてくれました。
<?php
$xml = "./[FILE_NAME].xml";
// Load the XML file and create a SimpleXML object
$xmlData = simplexml_load_file($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
// Initialize an empty array to store the JSON data
$jsonData = array();
// Loop through the <item> elements
foreach ($xmlData->channel->item as $item) {
$wp = $item->wppostmeta;
// Create an empty array to store the item data
$itemData = array();
// Add the item data to the array
$itemData['id'] = (int) $item->{'wp:post_id'};
$itemData['title'] = (string) $item->title;
$itemData['link'] = (string) $item->link;
$itemData['pubDate'] = (string) $item->pubDate;
$itemData['html_contents'] = (string) $item->{'content:encoded'};
// Initialize an empty array to store the categories
$itemData['categories'] = array();
// Loop through the <category> elements and add them to the array
foreach ($item->category as $category) {
$itemData['categories'][] = (string) $category;
}
// Initialize an empty array to store the postmeta data
$itemData['postmeta'] = array();
// Loop through the wp:postmeta elements and add them to the array
foreach ($item->{'wp:postmeta'} as $postmeta) {
$metaData = array();
$metaData['metaKey'] = (string) $postmeta->{'wp:meta_key'};
$metaData['metaValue'] = (string) $postmeta->{'wp:meta_value'};
$itemData['postmeta'][] = $metaData;
}
// Add the item data to the JSON data array
$jsonData[] = $itemData;
}
// Convert the JSON data array to a JSON string
$jsonString = json_encode($jsonData);
// Output the JSON string
echo $jsonString;
JSON ファイルを作成できれば、POST 自体はごく単純で、ファイルを読み込んでオブジェクトの配列を生成し、ループ処理で POST し続けるというものになります。
- POST する Body に
id
フィールドを指定することで、指定したコンテンツ ID を持つデータを作成できる :::message alert すでに microCMS 側に存在するコンテンツ ID を指定するとエラーになります ::: status=draft
パラメータを指定することで下書き状態で作成することも可能
const axios = require('axios')
const fs = require('fs')
const dataArray = JSON.parse(fs.readFileSync('./[FILE_NAME].json', 'utf-8')) // JSONファイルのパスを指定
const postRequests = async () => {
for (let data of dataArray) {
try {
const response = await axios({
method: 'post',
url: 'https://[SERVICE_ID].microcms.io/api/v1/**', // エンドポイント
headers: { 'X-MICROCMS-API-KEY': '[API_KEY]' }, // APIキー
data,
})
console.log(response)
} catch (error) {
console.error(error)
}
}
console.log('All POST requests have been completed.')
}
postRequests()
- microCMS から GET したデータをもとに
is_migrated
を処理 is_migrated
に応じて、表示するコンテンツデータと適用するスタイルを棲み分け
const contents = is_migrated ? html_contents : rich_editor
...
return (
...
<div
className={`${styles.article} ${is_migrated ? styles.migrated : ''}`}
dangerouslySetInnerHTML={{
__html: contents,
}}
/>
...
)
...
:::message サイト表示側での工夫については別記事にします :::
- microCMS はとても良くできたサービス
- ChatGPT と Copilot のおかげで WordPress → microCMS のようなリニューアル前後でデータベース基盤が変わる移行もかなりラクになってるので、別案件でも積極的に移行を推奨したい
- 特に WordPress のバージョン管理を徹底できていないサイト(5 系以前からアップグレードできていないようなサイト)は microCMS x ウェブフレームワーク に置き換えたい
あまり類を見ない移行方法だと思っていますが、自分が取り組む過程ではフィットする前例をなかなか見つけられず苦労したので、この記録がどこかでニッチなことに取り組もうとしている誰かの何かの役に立つことを願っています。