---
title: "WordPressからAstroへブログを移行した記録 | C#もぐもぐ"
description: "WordPressで運営していた技術ブログをAstro 5 + MDXの静的サイトに移行し、Cloudflare Pagesにデプロイした作業の記録です。"
url: "https://blog.223n.tech/posts/233/"
---

# WordPressからAstroへブログを移行した記録

2026年4月13日

[Astro](https://blog.223n.tech/tags/Astro/) [WordPress](https://blog.223n.tech/tags/WordPress/) [Cloudflare](https://blog.223n.tech/tags/Cloudflare/) [TypeScript](https://blog.223n.tech/tags/TypeScript/) [Program](https://blog.223n.tech/tags/Program/)

## はじめに

このブログ「C#もぐもぐ」は、もともとWordPressで運営していました。

今回、Astro 5 + MDXによる静的サイトへ全面移行し、ホスティングもCloudflare Pagesに切り替えました。

この記事では、移行の動機から技術選定、変換スクリプトの実装、デプロイまでの一連の作業を記録として残します。

## 移行の動機

WordPressを使い続ける中で、いくつかの課題を感じていました。

- PHPやデータベースの管理コスト（アップデート対応、セキュリティパッチなど）
- 表示速度の改善が難しい
- Markdownで記事を書きたい
- 静的ページが多いのに動的CMSを使う疑問

静的サイトジェネレーターに移行すれば、サーバー管理から解放され、CDN配信による高速な表示が実現できます。

記事もMarkdownで管理でき、Gitでバージョン管理できるようになります。

そのため、今回、Claudeと相談しながら静的CMSに移行することにしました。

## 移行元技術スタック

移行元の技術スタックは、下表のとおりです。

| 項目 | 技術 |
| - | - |
| CMS | WordPress |
| コンテンツ形式 | HTML / Gutenbergブロック |
| ホスティング | さくらのレンタルサーバー |

## 技術スタック

移行先の技術スタックは、下表のとおりです。

| 項目 | 技術 |
| - | - |
| フレームワーク | Astro 5（`output: "static"`） |
| コンテンツ形式 | MDX / Markdown |
| UIフレームワーク | AdminLTE 4（rc4）+ Bootstrap 5.3 |
| スタイル | SCSS（sass） |
| パッケージマネージャー | pnpm 10 |
| ホスティング | Cloudflare Pages |
| デプロイ | wrangler / GitHub連携 |
| 画像最適化 | sharp |
| 型チェック | TypeScript + astro check |

Astroを選んだ理由は、次の3点です。

1. コンテンツ主体のサイトに最適化されていること
2. MDXによる柔軟な記述が可能なこと
3. ビルド時に静的HTMLを出力するためパフォーマンスが高いこと

今回、UIにはAdminLTE 4を採用しました。

Bootstrap 5ベースでレスポンシブ対応が容易であり、ナビゲーションやカードレイアウトなどの既成コンポーネントが充実しています。

## プロジェクト構成

移行後のディレクトリ構成は、次のようになっています。

```
src/
├── content/
│   ├── config.ts          # コンテンツコレクションのスキーマ定義
│   └── posts/             # 記事ファイル（Markdown/MDX）
│       ├── 6.md
│       ├── 7.md
│       └── ...
├── layouts/
│   └── AdminLayout.astro  # 共通レイアウト（OGP・メタ情報含む）
├── pages/
│   ├── index.astro        # トップページ
│   ├── about.astro        # Aboutページ
│   ├── posts/
│   │   ├── index.astro    # 記事一覧
│   │   └── [...slug].astro # 記事詳細（動的ルーティング）
│   ├── tags/
│   │   ├── index.astro    # タグ一覧
│   │   └── [...tag].astro    # タグ別記事一覧
│   └── rss.xml.js         # RSSフィード
├── styles/
│   ├── global.scss        # グローバルスタイル
│   └── post-content.scss  # 記事本文のスタイル
├── types.ts
└── utils/
    └── date.ts
```

記事はAstroのコンテンツコレクションで管理しています。

スキーマはZodで定義しており、型安全にfrontmatterを扱えます。

```typescript
// src/content/config.ts
import { defineCollection, z } from "astro:content";

export const postsSchema = z.object({
  title: z.string(),
  date: z.date(),
  description: z.string().optional(),
  excerpt: z.string().optional(),
  image: z.string().optional(),
  tags: z.array(z.string()).optional().default([]),
  draft: z.boolean().optional().default(false),
});

const posts = defineCollection({
  type: "content",
  schema: postsSchema,
});

export const collections = { posts };
```

## WordPress記事の変換

### 変換スクリプト

WordPressからのコンテンツ移行には、Python製の変換スクリプトを自作しました。

処理の流れは、次のとおりです。

1. WordPressのエクスポートXML（WXR形式）をパース
2. 各記事のタイトル、本文、日付、カテゴリ、excerptを抽出
3. HTML/Gutenbergブロックを`markdownify`ライブラリでMarkdownに変換
4. Astro互換のfrontmatter付きMarkdownファイルとして出力
5. 本文中の画像URLを抽出し、ダウンロード対象リストをJSONで生成

ファイル名にはWordPressの`post_id`をそのまま使用することにしました。

これにより、旧URLからのリダイレクトを簡潔に設定できます。

```bash
# 変換実行
python wp2md_convert.py wordpress_export.xml ./output/posts
```

### 画像の処理

画像のダウンロードとWebP変換は、別のスクリプトで並列処理しています。

```bash
python download-images.py ./output/images_to_download.json \
  ./public/images/posts ./output/posts
```

主な処理内容は、次のとおりです。

- 最大5並列でダウンロード（リトライ3回）
- 横幅1200pxを超える画像はリサイズ
- WebP形式に変換（quality 85）
- Markdownファイル内の画像パスを自動更新
- 失敗した画像はJSONファイルに記録

### 変換結果

| 項目 | 値 |
| - | - |
| 変換記事数 | 61件 |
| 画像URL数 | 184件 |
| 出力形式 | Astro互換frontmatter |

## WordPress互換URLのリダイレクト

WordPressでは`/{post_id}/`というURL構造を使っていました。
Astro移行後は`/posts/{post_id}/`になるため、旧URLからの301リダイレクトを設定しています。

Cloudflare Pagesの`_redirects`ファイルで対応しました。

```
# public/_redirects
/6/ /posts/6/ 301
/7/ /posts/7/ 301
# ... 61件のリダイレクトルール
```

Cloudflare Pagesの`_redirects`は正規表現に対応していないため、個別のルールを記述する必要があります。

## ビルド最適化

本番ビルドでは、次の最適化を`astro.config.mjs`で設定しています。

### チャンク分割

```javascript
manualChunks: (id) => {
  if (id.includes("node_modules")) {
    if (id.includes("admin-lte")) return "vendor-adminlte";
    if (id.includes("bootstrap")) return "vendor-bootstrap";
    return "vendor";
  }
},
```

AdminLTEとBootstrapを別チャンクに分離することで、キャッシュ効率を高めています。

### 圧縮

gzipとBrotliの事前圧縮を行っています。

閾値は10KBに設定し、小さなファイルは圧縮対象外としています。

```javascript
plugins: [
  compress({ algorithm: "gzip", ext: ".gz", threshold: 10240 }),
  compress({ algorithm: "brotliCompress", ext: ".br", threshold: 10240 }),
],
```

### その他の最適化

- terserによる`console.log`と`debugger`の自動削除
- ソースマップの無効化
- CSSコード分割の有効化
- アセットファイルの種類別ディレクトリ配置（`assets/css/`、`assets/js/`、`assets/images/`、`assets/fonts/`）

## デプロイ

Cloudflare Pagesへのデプロイは、GitHub連携による自動デプロイと手動デプロイの2つの方法を用意しています。

### wrangler.toml

```toml
name = "blog-223n-tech"
compatibility_date = "2024-01-01"
pages_build_output_dir = "dist"

[vars]
PUBLIC_SITE_URL = "https://blog.223n.tech/"
```

### 手動デプロイ

```bash
pnpm build
npx wrangler pages deploy dist --project-name=blog-223n-tech
```

最終的なビルド結果は85ページになりました。

## 開発環境

開発は、DevContainerで行っています。

主要なコマンドは、次のとおりです。

```bash
pnpm dev          # 開発サーバー（port 4321）
pnpm build        # 本番ビルド
pnpm check        # 型チェック（astro check + tsc --noEmit）
pnpm format       # Prettierフォーマット
```

## 移行してよかったこと

実際に移行を終えてみて、次の点でメリットを感じています。

- **表示速度の向上**: 静的HTML + CDN配信により、体感速度が大幅に改善
- **管理コストの削減**: データベースやPHPランタイムの管理が不要に
- **記事の書きやすさ**: Markdown + VS Codeで快適に執筆できる
- **バージョン管理**: 記事も設定もGitで一元管理
- **デプロイの簡単さ**: `git push`するだけで自動デプロイ

## おわりに

WordPressから61記事、184枚の画像を移行し、URL互換性も維持した状態でAstro + Cloudflare Pagesへの移行が完了しました。

変換スクリプトを自作したことで、frontmatterの形式やファイル名の規則を自由にコントロールでき、結果的にスムーズな移行ができました。

同様の移行を検討している方の参考になれば幸いです。

## 参考サイト

- [Astro公式ドキュメント](https://docs.astro.build/)
- [Astro コンテンツコレクション](https://docs.astro.build/ja/guides/content-collections/)
- [Astro MDXインテグレーション](https://docs.astro.build/ja/guides/integrations-guide/mdx/)
- [Astro サイトマップインテグレーション](https://docs.astro.build/ja/guides/integrations-guide/sitemap/)
- [Cloudflare Pages ドキュメント](https://developers.cloudflare.com/pages/)
- [Cloudflare Pages リダイレクト設定](https://developers.cloudflare.com/pages/configuration/redirects/)
- [AdminLTE 4](https://github.com/ColorlibHQ/AdminLTE)
- [Bootstrap 5.3](https://getbootstrap.com/docs/5.3/)
- [markdownify（Python HTML→Markdown変換ライブラリ）](https://github.com/matthewwithanm/python-markdownify)
- [Vite ビルド設定](https://vite.dev/config/build-options)
- [sharp（Node.js画像処理ライブラリ）](https://sharp.pixelplumbing.com/)
- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/)

[記事一覧に戻る](https://blog.223n.tech/posts/)
