🏡まったのブログ

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].jsgetStaticProps

// データをテンプレートに受け渡す部分の処理を記述します
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 を今後も書いていくしかなさそう。