静的サイトジェネレータを自作した|WordPressもフレームワークも使わない選択
このサイトはMarkdownを書くとHTMLになる仕組みを自作しています。既存のツールを使わなかった理由と、200行ほどのスクリプトで何ができるかを実装ごと公開します。
なぜ既存のツールを使わなかったのか
このサイトは、Markdownで記事を書くとHTMLが生成される仕組みで動いています。その仕組みを自作しました。
WordPressも、有名な静的サイトジェネレータも使っていません。理由は3つです。
1. やりたいことが特殊だった
このサイトには、普通のブログにない要件がありました。
- 広告リンクを1か所で管理し、記事にはキーだけ書く
- 広告表記(PR)が全記事に自動で入る
- 公開前に規約違反の検査が走り、通らないと公開できない
既存ツールでもプラグインで実現できるかもしれませんが、要件が特殊なほど、既存の仕組みに合わせる作業のほうが重くなります。
2. 数ヶ月後に触れなくなるのを避けたかった
個人で運用するものは、更新が止まった瞬間から朽ちていきます。
フレームワークを使うと、そのバージョンアップに追随する必要が出ます。放置すると、久しぶりに触ったときにビルドが通りません。
依存が少ないほど、久しぶりに開いても普通に動きます。
3. 全体を把握できる量だった
書いてみたら、中核部分は200行ほどでした。全部読んで理解できる量です。
問題が起きたとき、自分で原因を追えます。これは既存ツールにはない利点でした。
何をやっているか
処理はこれだけです。
content/articles/*.md 記事のMarkdown
↓ build_site.py
site/articles/*.html HTMLに変換、一覧ページも生成
↓ build_seo.py
構造化データ・llms.txt・sitemap.xml を注入・生成
記事の書き方
ファイルの先頭に情報を書き、その下に本文を書きます。
---
title: 記事タイトル
slug: url-name
category: web
date: 2026-08-25
verified: 2026-08-25
lede: 一覧と検索結果に出る要約
---
## 本文
verified(検証日)を必須にしているのは、料金や仕様がいつ時点のものかを必ず書くという運用ルールを、仕組みで強制するためです。
広告リンクの扱い
記事に生のURLを書きません。キーだけを書きます。
実際のURLは1つのファイルにまとめてあり、変換時に展開されます。このとき自動で付くものが3つあります。
rel="sponsored nofollow noopener"- PR表記
- 提携していない場合は、リンクではなく注意書き
貼り間違いも、表記の付け忘れも、構造的に起こらなくなりました。
日本語の見出しでも目次が壊れないようにする
一般的な変換ライブラリは、見出しからIDを自動生成します。日本語だと空になることがあります。
そこで、変換後のHTMLを走査して連番のIDを振り直す方式にしました。
def repl(m):
counter[0] += 1
hid = f"h{counter[0]}"
# ... 見出しにIDを付けて、目次用に収集
見出しの文言が変わってもIDが安定します。
テーブルを横スクロールさせる
記事に表を入れると、スマホで画面からはみ出します。ページ全体が横スクロールするのは避けたいので、表だけを箱に入れます。
re.sub(r"(<table>.*?</table>)",
r'<div class="table-wrap">\1</div>', body, flags=re.S)
CSSで overflow-x: auto を当てると、表の中だけがスクロールするようになります。
作ってよかった点
公開前の検査を組み込める
公開コマンドを叩くと、先に検査が走ります。
- 広告表記の漏れ
- 誇大表現
- 書きかけの箇所
- リンク切れ
- 生のURLの直書き
1つでも引っかかると公開されません。
実際、この記事を書いている最中にも止まりました。別の記事で「こう書いてはいけない例」として引用した文言が、禁止表現として検出されたためです。誤検知でしたが、検査を緩めずに記事側を直しました。
元Markdownを消すと、HTMLも消える
記事を削除したのに、生成済みのHTMLが残って公開され続ける——という事故を防ぐため、対応するMarkdownがないHTMLを自動で削除しています。
何度実行しても同じ結果になる
生成処理は冪等にしてあります。変更がなければ「変更なし」と出ます。
これが効くのは、意図しない変更に気づけることです。差分が出たときだけ、何かが変わったと分かります。
つまずきやすい点
生成順序を間違えると内容が消える
HTMLを生成する処理と、構造化データを注入する処理は順序が決まっています。 逆にすると、注入した内容が上書きで消えます。
対策として、両方を正しい順序で実行するスクリプトを1本用意し、個別に叩かない運用にしました。順序を覚えておく運用は必ず破綻します。
さらに、HTMLを作り直すときに既存の注入部分を保持するようにしました。これで何度実行しても差分が出ません。
公開フォルダに作業ファイルを置かない
生成結果を置くフォルダは、中身が全部そのまま配信されます。
原稿のMarkdown、ビルドスクリプト、バックアップファイルを同じ場所に置くと、URLを直接叩けば誰でも見られます。公開するフォルダと作業するフォルダを分けるのが対策です。
実際、別サイトの更新時にバックアップファイルが公開対象に混ざっていたのを、直前で見つけました。
自作は全員に勧められるものではない
普通のブログを作るなら、既存ツールのほうが速いです。 自作が合うのは、要件が特殊で、既存の仕組みに合わせるほうが重くなる場合だけです。
判断材料はこうだと思います。
| 自作が向く | 既存ツールが向く |
|---|---|
| 要件が特殊 | 一般的なブログ |
| 自分で保守できる | 保守に時間をかけたくない |
| 長期運用する | 短期で試す |
よくある質問
静的サイトジェネレータを自作するメリットは何ですか?
公開前の検査を仕組みに組み込めること、依存が少なく久しぶりに触っても動くこと、全体を把握できるため問題の原因を自分で追えることです。一方、一般的なブログを作るなら既存ツールのほうが速く済みます。
日本語の見出しで目次が正しく作られません。
多くの変換ライブラリは見出しの文字列からIDを生成するため、日本語だと空になることがあります。変換後のHTMLを走査して連番のIDを振り直す方式にすると、日本語でも安定します。
記事の表がスマホで画面からはみ出します。
表を個別のdivで囲み、そこにoverflow-x: autoを当ててください。ページ全体ではなく表の中だけがスクロールするようになり、本文の読みやすさを保てます。
まとめ
このサイトの生成の仕組みは自作です。既存ツールを使わなかったのは、要件が特殊で、合わせるほうが重かったからでした。
- 広告リンクを1か所で管理し、記事にはキーだけ書く
- PR表記が自動で入る
- 検査に通らないと公開できない
作ってよかったのは、運用ルールを仕組みで強制できる点でした。「気をつける」で運用していたものが、全部自動になっています。