Skip to content
sakura86.com
戻る

WordPressからAstroへ移行した話(3)331件の記事を移行スクリプトで変換

はじめに

前回はサイトの基本設定を済ませました。
今回はWordPressの記事をAstroに移行します。

WordPressからエクスポートしたMarkdown(331件)は、そのままではAstroPaper v5では使えません。
フロントマターの形式が異なるためです。変換スクリプトを書いて一括処理しました。

変換スクリプトを書いた、と偉そうに書いてますが、実際に作成したのはClaude Codeです。
AI使うと楽で良い。

移行前後の構成

移行元移行先
記事ファイルsrc/content/blog/[slug]/index.mdsrc/data/blog/[slug].md
画像src/content/blog/[slug]/images/public/posts/[slug]/

フロントマターの変換ルール

WordPressエクスポート形式とAstroPaper v5形式の対応関係は以下のとおりです。

WordPressエクスポート形式AstroPaper v5形式
pubDate: "Fri, 18 Jan 2019..."pubDatetime: 2019-01-18T00:00:00Z
category: ["PC"]tags: ["pc-windows"]
ogImage: "filename.png"ogImage: "/posts/[slug]/filename.png"
なしdescription: "本文冒頭100文字"

移行スクリプト(migrate.mjs)

以下のスクリプトを migrate.mjs として保存してプロジェクトルートに置きます。

import fs from "fs";
import path from "path";
import matter from "gray-matter";

const sourceDir = "./src/content/blog";
const targetDir = "./src/data/blog";
const publicPostsDir = "./public/posts";

const slugDirs = fs.readdirSync(sourceDir).filter(f =>
  fs.statSync(path.join(sourceDir, f)).isDirectory()
);

for (const slug of slugDirs) {
  const indexPath = path.join(sourceDir, slug, "index.md");
  if (!fs.existsSync(indexPath)) continue;

  const raw = fs.readFileSync(indexPath, "utf-8");
  const { data, content } = matter(raw);

  // pubDatetime変換
  const pubDatetime = data.pubDate
    ? new Date(data.pubDate).toISOString()
    : new Date().toISOString();

  // ogImage変換
  let ogImage = undefined;
  if (data.heroImage) {
    ogImage = `/posts/${slug}/${path.basename(data.heroImage)}`;
  }

  // description生成(本文冒頭100文字)
  const description = content.replace(/[#*`\[\]]/g, "").trim().slice(0, 100);

  // 画像パスの置換(2パターン対応)
  let body = content
    .replace(/\(\.\/images\//g, `(/posts/${slug}/`)
    .replace(/\(images\//g, `(/posts/${slug}/`);

  const newFrontmatter = {
    title: data.title,
    pubDatetime,
    tags: data.tags || data.category || [],
    ...(ogImage && { ogImage }),
    description,
    draft: false,
  };

  const output = matter.stringify(body, newFrontmatter);
  fs.writeFileSync(path.join(targetDir, `${slug}.md`), output);

  // 画像をコピー
  const imagesDir = path.join(sourceDir, slug, "images");
  if (fs.existsSync(imagesDir)) {
    const destDir = path.join(publicPostsDir, slug);
    fs.mkdirSync(destDir, { recursive: true });
    for (const img of fs.readdirSync(imagesDir)) {
      fs.copyFileSync(path.join(imagesDir, img), path.join(destDir, img));
    }
  }
}

console.log(`✅ Migrated: ${slugDirs.length} articles`);

実行します。

node migrate.mjs

gray-matter が未インストールの場合は先にインストールします。

npm install gray-matter

ハマりポイント

ogImageのスキーマエラー

AstroPaper v5の content.config.ts のデフォルトは ogImage: image() です。
これはAstroの画像最適化を使う型で、/posts/[slug]/filename.png のような文字列パスを渡すとスキーマエラーになります。

対処: z.string().optional() に変更します。

// src/content.config.ts(該当箇所のみ)
// 変更前
ogImage: image().or(z.string()).optional(),
// 変更後
ogImage: z.string().optional(),

スキーマ変更後は .astro/ キャッシュを削除して再起動しないと反映されません。

Remove-Item -Recurse -Force .astro
npm run dev

画像パスのパターンが2種類ある

WordPressからエクスポートしたMarkdownの画像パスには2パターンが混在していました。

  • (./images/filename.jpg) → 正規表現で置換
  • (images/filename.jpg) → こちらも別途置換が必要

スクリプト内で両方に対応しています。

まとめ

スクリプト1本で331件の記事を一括変換できました。ハマりポイントはogImageのスキーマと画像パスのパターンの2点です。

次回はGitHub ActionsでAstroをビルドしてコアサーバーへFTP自動デプロイする設定を紹介します。