このサイト(miniTools)に新しく追加された /article/ のブログシステムは、WordPressも、Hugoも、外部ライブラリも一切使わずに作られています。PHPのMarkdownパーサーをゼロから書き、フロントマターのYAMLパーサーも自前で実装しました。

この記事は、そのシステムをAIと一緒に設計・構築した過程の記録です。

なぜWordPressを使わなかったのか

miniToolsはすでにサーバーで動いています。WordPressを追加しようとすれば、データベース・プラグイン・テーマ・セキュリティ更新の管理が必要になります。一方でやりたいことは「MarkdownのテキストファイルをHTMLとして配信する」というシンプルなものでした。

要件を整理すると、必要なのは以下だけでした。

これならPHPだけで十分です。依存ライブラリはゼロ、データベースも不要。.md ファイルを置くだけで記事が増える仕組みです。

AIとの設計対話

今回の開発で特徴的だったのは、実装の前に「どう作るか」をAI(Claude Code)と対話しながら決めたことです。

最初に方針を確認しました。

「シンプルな正規表現ベースのミニパーサーをゼロから書くでも作れそうですか?」

AIの返答は「作れます」。そこから具体的な設計に入りました。

ファイル構成についてはこちらから制約を出しました。

「ディレクトリ構成はシンプルにしたいので上位階層以外にassetsをまとめるのが良さそうです。」

URLの設計についてはAIから提案がありました。Claudeのドキュメントサイトのパターンを参考に、.md サフィックスでMarkdownの生ソースをプレーンテキストとして配信する設計です。

/article/htaccess-redirect     → HTMLレンダリング
/article/htaccess-redirect.md  → 生Markdownをプレーンテキストで配信
この設計には理由があります。AIがWebページを読む際、HTMLよりMarkdownのほうが構造を理解しやすい。記事をAIに参照させたいとき、.md URLを渡すだけで済みます。

Markdownパーサーの実装

外部ライブラリを使わないということは、Markdownのパーサーを自分で書くということです。

コードブロックの保護問題

Markdownパーサーを自作するとき、最初につまずくのがコードブロックの扱いです。

````

RewriteEngine On
````

このようなコードブロック内のテキストを、他のMarkdown記法として誤って解釈してしまう問題があります。

解決策は「プレースホルダー置換」です。まずコードブロックを抽出して \x02CB0\x03 のような制御文字のプレースホルダーに置換し、残りのMarkdownを処理してから最後に復元します。

$cb = [];
$text = preg_replace_callback(
    '/^```(\w*)\n(.*?)^```\s*$/ms',
    function ($m) use (&$cb) {
        $ph   = "\x02CB" . count($cb) . "\x03";
        $cb[] = "<pre><code>{$m[2]}</code></pre>";
        return $ph;
    },
    $text
);
// ... Markdownの残りを処理 ...
// 最後に復元
foreach ($cb as $i => $block) {
    $html = str_replace("\x02CB{$i}\x03", $block, $html);
}
制御文字(\x02 \x03)をプレースホルダーに使うのは、通常のMarkdownテキストに混入しないためです。

テーブルのパース

テーブルは特に厄介でした。コードブロックのプレースホルダーが同じブロックに混入すると、テーブルのヘッダー行として誤認識されます。

function parse_md_table(string $block): string {
    $lines = array_values(array_filter(
        explode("\n", $block),
        // プレースホルダー行を除外
        fn($l) => trim($l) !== '' && !preg_match('/^\x02CB\d+\x03$/', trim($l))
    ));
この !preg_match によるフィルタリングは、開発途中で発生した CB0 という謎の文字が表に混入するバグを修正するために追加されました。

CSSの文字化けとの戦い

開発中に発生した予想外のバグの一つがCSSの文字化けです。

「約4分で読めます」の前に区切り文字(中黒 )を表示するため、最初はCSSの content プロパティに直接日本語を書いていました。

/* ❌ これが文字化け「・」になる */
.article-reading-time::before { content: "・"; }
UTF-8の日本語文字( = U+30FB)をCSSに直書きすると、サーバーやブラウザのcharset処理次第で ・ という文字化けが発生します。

CSSのユニコードエスケープで書き直すことで一度は回避しましたが、根本的な解決のため最終的にはCSS ::before を廃止し、PHPのテンプレートで直接HTMLに出力する方式にしました。

<span class="article-reading-time">
  <span class="article-reading-time-sep">・</span>約<?= $read_min ?>分で読めます
</span>
PHPファイルは Content-Type: text/html; charset=utf-8 が明示されるため、charset の取り違えが起きません。

AIフレンドリーな設計思想

このブログシステムを設計する際に意識したのは「AIに読まれることを想定する」という視点です。

Googleの検索にはAI Modeが登場し、検索結果にAIによる要約が表示されるようになっています。AIがWebページを参照する際、HTMLのノイズを除去してテキストを抽出するより、最初からMarkdownで配信されていたほうが効率的です。

このブログは以下を意識して設計しています。

AI検索が普及するにつれ、「人間のブラウザ向けに最適化されたページ」だけでなく「AIが参照しやすいページ」を意識することが重要になってきています。

OGP画像の自動生成

SNSシェア時に表示されるOGP画像は、記事ごとに自動生成されます。

https://tools.adregion.co.jp/ogp.php?title=記事タイトル
このサイトにはOGP画像をサーバーサイドで動的生成するPHPスクリプトがあります。GDライブラリまたはImageMagickを使い、タイトルテキストを1200×630pxの画像にレンダリングします。個別に画像を用意しなくても、記事を書くだけでSNSシェアに対応できます。

完成したシステムの構成

article/
├── .htaccess         # ルーティング・.mdプレーンテキスト配信
├── index.php         # 記事一覧・ページネーション
├── post.php          # Markdownレンダリング・TOC・OGP・JSON-LD
├── feed.php          # Atomフィード
├── sitemap.php       # XMLサイトマップ
├── assets/
│   ├── article.css   # 記事専用スタイル
│   └── images/       # 記事内ローカル画像
└── *.md              # 記事ファイル(スラッグ.md)
記事ファイルはMarkdownで書いてこのディレクトリに置くだけで公開されます。フロントマターにタグや関連ツールを指定すると、記事末尾に関連ツールへのリンクが自動で表示されます。

AIと開発することの実感

今回の開発を通じて感じたのは、AIは「制約を与えるほど良い仕事をする」ということです。

「ブログを作って」という指示より「外部ライブラリなし・1ディレクトリ・Markdownパーサーは自前で・AIフレンドリーなURL設計で」という制約を積み重ねることで、方向性が明確になり、実装が具体的になりました。

AIは提案しますが、設計の判断は人間がする。その役割分担が機能したとき、想像以上のものが短時間で完成します。このブログシステム自体、Claude Codeとの対話を通じて1セッションで設計・実装まで完成しました。

次のフェーズは、このシステムに記事を積み重ねていくことです。ツールの使い方・SEOの実践・Web制作の知識を、実際に役立つ形でまとめていきます。