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-pagesとcloudflare-pages-staticの違いを把握する必要があった - トレイリングスラッシュ: Cloudflare Pagesの自動リダイレクトと
useAsyncDataキーの整合性。これが一番調査に時間がかかった