はじめに
前回はサイトの基本設定を済ませました。
今回はWordPressの記事をAstroに移行します。
WordPressからエクスポートしたMarkdown(331件)は、そのままではAstroPaper v5では使えません。
フロントマターの形式が異なるためです。変換スクリプトを書いて一括処理しました。
変換スクリプトを書いた、と偉そうに書いてますが、実際に作成したのはClaude Codeです。
AI使うと楽で良い。
移行前後の構成
| 移行元 | 移行先 | |
|---|---|---|
| 記事ファイル | src/content/blog/[slug]/index.md | src/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自動デプロイする設定を紹介します。