• #nuxt
  • #nuxt-content
  • #wordpress
  • #migration
  • #cloudflare-pages
  • #python
  • #ssg
開発pon-blogメモ

WordPressブログをNuxt 3 + Cloudflare Pagesに移行した記録

ponponpopopoon.com を WordPress から Nuxt 3 + @nuxt/content v3 + Cloudflare Pages に移行した。17ページ・169記事・画像約300枚の個人ブログで、1日でデプロイまで完了した。

1. クローリングとMarkdown変換

Pythonスクリプトでサイトをクローリングし、HTML→Markdown変換と画像ダウンロードを行った。

やったこと:

  • BeautifulSoup + markdownify で記事本文をMarkdown化
  • 画像を約300枚ダウンロード
  • frontmatter(title, permalink, publishedAt等)を自動生成

日本語ファイル名のエンコーディングが問題になった。URLエンコードされた日本語パスをデコードして保存するようにした。

2. Nuxt 3プロジェクト構築

pon-blog プロジェクトを新規作成し、@nuxt/content v3 でコンテンツ管理する構成にした。

日本語ファイル名問題

Nuxt Content がパスから日本語を除去してしまう。たとえば レビュー記事.md が空のスラッグになる。

対処として、全記事を連番スラッグ(001.md ~ 168.md)にリネームした。URLはfrontmatterの permalink フィールドで制御する方式に切り替えた。

permalinkベースルーティング

[...slug].vue のcatch-allルートで、permalink フィールドをもとにコンテンツを引き当てるようにした。

// permalinkでクエリ(概要)
const { data: page } = await useAsyncData(`page-${permalink}`, () =>
  queryCollection("pages").where("permalink", "=", permalink).first()
);

これで 001.md でも元のWordPress URLと同じパスでアクセスできるようになった。

3. Cloudflare Pagesへのデプロイ

SSR vs SSG

最初 cloudflare-pages プリセットを使ったが、これはSSR(Workers)モード。静的サイトとしてデプロイしたかったので cloudflare-pages-static に変更した。

// nuxt.config.ts
nitro: {
  preset: "cloudflare-pages-static",
}

production branch の不一致

Cloudflare Pagesのプロダクションブランチが main になっていたが、リポジトリは master を使っていた。Cloudflare APIで修正。

curl -X PATCH "https://api.cloudflare.com/client/v4/accounts/{id}/pages/projects/{name}" \
  -H "Authorization: Bearer {token}" \
  -d '{"production_branch":"master"}'

4. 404問題の調査と解決

デプロイ後、直リンクで404になる問題が発生した。

原因はトレイリングスラッシュの不一致。Cloudflare Pagesが /152/152/ にリダイレクトするため、useAsyncData のキーがプレレンダリング時とクライアント時で食い違う。

  • プレレンダリング時: page-/152
  • クライアント時: page-/152/(リダイレクト後)

permalinkの末尾スラッシュを正規化することで解決。詳細は別記事(Nuxt Content v3 + Cloudflare Pagesで直リンク時に404になる問題の解決)にまとめた。

5. コンテンツの整備

Amazonアソシエイトリンクの修正

WordPress時代のAmazonウィジェット(iframe埋め込み)が軒並み壊れていた。テキストリンクに置き換えた。

favicon設定

元サイトのfaviconをWayback Machineから取得した。パスが 2016/07 ではなく 2017/01 配下だったので少し探した。

画像ディレクトリ構造

画像を public/images/migrated/ 配下に統一して配置。記事内の画像パスも一括で書き換えた。

その他の整備

  • description を本文冒頭から自動生成
  • 不要な source フィールドをfrontmatterから削除

振り返り

1日で移行完了できたのは、Claude Codeでの一括処理が効いた。手作業だと画像300枚のパス書き換えやfrontmatter整備だけで数時間かかる。

つまずきポイントは主に3つ:

  • 日本語ファイル名: Nuxt Contentのパス正規化で日本語が消える。連番リネーム+permalink方式で回避
  • SSR/SSGプリセット: cloudflare-pagescloudflare-pages-static の違いを把握する必要があった
  • トレイリングスラッシュ: Cloudflare Pagesの自動リダイレクトとuseAsyncDataキーの整合性。これが一番調査に時間がかかった