このポートフォリオサイトは、Next.jsの静的エクスポート(SSG)で作り、記事はヘッドレスWordPressのREST APIからビルド時に取得していました。個人のサイトなのでWordPressはなくてもよいのでは、と考え、記事をリポジトリ内のMarkdownで管理する形に移行しました。
移行前の構成と課題
- ビルド時にWP REST APIから記事・カテゴリ・タグ・画像情報を取得
- GitHub Actionsで週1回ビルドし、Xserverにアップロード
困っていた点は次のとおりです。
- 記事を書いても、次のビルドまで本番に反映されない
- ビルドがWordPressの稼働状況に左右される(WAF対策のUser-Agentや認証情報の管理も必要)
- 画像1枚ごとにメディアAPIを呼んでいて、ビルドが遅い
静的サイトでビルド時にしかデータを取得していないので、データの取得元をファイルに置き換えるだけで済みそうでした。
移行スクリプト
WP REST APIから全記事を取得し、本文のHTMLをMarkdownに変換して書き出す、一回限りのスクリプトを作りました。HTMLからMarkdownへの変換には turndown を使っています。
import TurndownService from "turndown";
const turndown = new TurndownService({
headingStyle: "atx",
codeBlockStyle: "fenced",
bulletListMarker: "-",
});
// per_page=100 で、返ってくる件数が100未満になるまでページングする
const fetchAll = async (type) => {
let all = [];
for (let page = 1; ; page++) {
const data = await getJson(`/${type}?per_page=100&page=${page}`);
all = [...all, ...data];
if (data.length < 100) return all;
}
};
ポイントは次のとおりです。
- URLを変えない:WPのスラッグ(
blog-123)をそのままファイル名にし、URLは/blog/blog-123/のまま - 画像もローカルに保存:本文中の
<img>をダウンロードしてpublic/images/blog/<ID>/に置き、パスを書き換え - コードブロックの言語を引き継ぐ:
<code class="language-php">はturndownのcodeBlockStyle: "fenced"で```phpになります。Markdownから戻すと同じクラスになるので、シンタックスハイライトの処理はそのまま使えました - frontmatterはJSON形式で書く:値を
JSON.stringifyで書き出すと、JSONはYAMLとしても有効なので、エスケープを気にしなくて済みます
読み込み側:zodでfrontmatterを検証
content/blog/*.md を gray-matter で読み、frontmatterを zod で検証してから、本文を marked でHTMLにしています。
const blogSchema = z.object({
title: z.string(),
date: dateSchema,
category: z.string(),
tags: z.array(z.string()).default([]),
});
const { data, content } = matter(fs.readFileSync(file, "utf8"));
const parsed = blogSchema.safeParse(data);
if (!parsed.success) {
throw new Error(`${file} の frontmatter が不正です: ${parsed.error.message}`);
}
カテゴリとタグは別のJSONファイルで管理し、存在しないslugを書いたらビルドを止めるようにしました。WordPressの管理画面のような入力補助は無くなりますが、書き間違いはビルド時に気づけます。
カテゴリ・タグ別ページは廃止
WP時代は /blog/category/7/ のようにタクソノミーのIDでページを作っていましたが、記事数も多くないので、ブログ一覧ページの中で絞り込む形にしました。記事詳細のカテゴリをクリックしたときは、URLを変えずに絞り込んだ状態で一覧を表示しています。
デプロイ
- 週1回の定期ビルドをやめ、mainにpushしたらGitHub Actionsでデプロイ
- WP REST API用のSecretsや認証情報が不要に
移行してみて
- 記事を書いてpushすればすぐ反映される
- 記事がGitで履歴管理される
- ビルドが外部に依存しないので速く、失敗しにくい
一方で、スマホから記事を書くのは難しくなりました。管理画面から書きたくなったら、Markdownを編集できるGitベースのCMS(Keystatic、Decap CMSなど)を足すことも検討しています。