WordPress -> microCMS + Next.js + Vercel への移行メモ
2022年12月31日時点では microCMS は他ブログサービスからのインポート機能や、WP側のプラグインとして移行機能などを提供しておらず、自力で記事と画像の移行作業を行う必要がある。基本的には以下の記事を参考にする。
https://zenn.dev/kandai/articles/f6a034d166e4c977a78e
しかし画像URLの統一性がなくなっていたり、若干のつまづきポイントなどがあったのでまとめておく。
上記の記事から変更した点
上記の記事時点では画像URLのうち、ファイル名直前のパス(/xxx/yyy/sample.pnp
のうちの yyy )が全画像で同じであったが、2022年12月時点では画像ごとに異なるようになっていた。
そこで Management API を使用してアップロードした画像一覧を取得しつつ、ファイル名でマッチ判定、画像URLを置換した。
// GET Media list
$ch0 = curl_init();
curl_setopt($ch0, CURLOPT_URL, 'https://your-domain.microcms-management.io/api/v1/media?limit=999'); // limit 数は適宜変更
curl_setopt($ch0, CURLOPT_HTTPGET, TRUE);
curl_setopt($ch0, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch0, CURLOPT_RETURNTRANSFER, TRUE);
$json = curl_exec($ch0);
$media = json_decode($json)->media;
$media_urls = [];
foreach ($media as $m) {
array_push($media_urls, $m->url);
}
// 中略
// 画像を使っている部分を正規表現で抽出
preg_match_all('/https:\/\/your-blog\.com\/wp-content\/uploads\/([^ ]*)(png|jpg|jpeg|gif)/i', $contents, $matches); // 画像URLのみマッチ対象に
// 置き換え用の配列を作成
$pattern = [];
$replace = [];
foreach ($matches[0] as $key => $match) {
$pattern[] = '/' . preg_replace(['/\//', '/\./'], ['\/', '\.'], $match) . '/'; // スラッシュとピリオドの前にバックスラッシュ(エスケープ記号)を付与する
$pathArray = explode('/', $match);
$paths = array_reverse($pathArray);
$target_imgs = array_filter($media_urls, function ($url) use (&$paths) { // コールバック関数で $paths を使いたかったので use を使いつつ、&を付けて参照型にしています
return str_contains($url, $paths[0]);
});
if (count($target_imgs) > 0) {
$replace[] = array_merge($target_imgs)[0]; // array_filter では key を連番に詰めてくれないので array_merge で 0, 1, 2.. となるようにする
}
}
注意点
- API キーのヘッダー名は
X-MICROCMS-API-KEY
になりました - id は50文字までなので WP の slug を入れたい方は別途 slug フィールドを作ったほうがよさそう
- POST時に createdAt, publishedAt の設定は出来ないので記事公開日が移行日になってしまう
メモ
- Zennの記事では本文のフィールド名を
contents
にしていたが、microCMS API が返すデータもdata.contents
であるためbody
などの別名がよさそう - Management API の
/media
エンドポイントでは1800件の画像でも一回で全件取得できた
ハマったポイント
getStaticPaths() では limit: 9999 で get しておく
公式の Next.js チュートリアルをベースに進めていたが、microCMS SDK では get()
でデフォルト10件しか記事を取得しないため、それ以上記事がある場合は詳細ページが404になる。
しかし limit: -1
などの上限なし指定ができないため、getStaticPaths()
内の get()
では limit: 9999
などの超えそうにない値を入れておくしかない模様。
// 静的生成のためのパスを指定します
export const getStaticPaths = async () => {
const data = await client.get({ endpoint: "blog", queries: { limit: 9999 } });
const paths = data.contents.map((content) => `/blog/${content.id}`);
return { paths, fallback: false };
};
悩みどころ
URL に id を使うか slug を使うか問題
公式チュートリアルにある [id].js
の getStaticProps
// データをテンプレートに受け渡す部分の処理を記述します
export const getStaticProps = async (context) => {
const id = context.params.id;
const data = await client.get({ endpoint: "blog", filter: { fields: '' } });
return {
props: {
blog: data,
},
};
};
URL に slug を使う場合([slug].js
)
export const getStaticProps = async (context) => {
const slug = context.params.slug;
const data = await client.get({ endpoint: "blog", queries: { filters: `slug[equals]${slug}` } }); // APIスキーマに slug を追加している場合
return {
props: {
blog: data.contents[0],
},
};
};
もともと WordPress では slug(URL) にはタイトルの英訳を入れたりしてSEO効果を狙っていたが、昨今では Zenn を始め URL に長い英文を入れるのはスマートでないように思う。何より microCMS の id は50文字しか入らないのでタイトルの英訳では結構オーバーしがち。
ただ元の記事の id 版URLへのリダイレクトとか面倒だし、かと言って「 id をキーにしてデータが存在しなければ slug で再度API GET」なんて棲み分けするなんてナンセンスだし。結局 slug を今後も書いていくしかなさそう。