Web サイトは、二層に分けて作る。
2-09: 会議とカレンダーを自分の側に ── Jitsi と CalDAV で、集まりと予定を自分の側に置いた。ここでは外に見せる Web を、自分の手元で組み立てる。中身は AsciiDoc と Mermaid、外枠は HTML と CSS と最小限の JavaScript、両者を Python が繋ぐ。中身の形式は 2-07 と同じで、Markdown でも同じことができる。焼き上がった HTML をどこに置くかは、次の章が受け持つ。
React の疲れは、道具の数から来る
過去 10 年、Web 開発はフレームワークの軍拡だった。
jQuery、Backbone、Angular、React、Vue、Svelte。ビルドツールは Grunt、Gulp、Webpack、 Rollup、Vite、Turbopack。CSS は Sass、Less、PostCSS、Tailwind、CSS-in-JS。サーバー側の描画は Next.js、Nuxt、Remix、SvelteKit、Astro。
どれも何かの問題を解いてきた。しかし、解いた問題と新しく生まれた問題を並べると、割に合わない場面が増えた。
- ビルドの設定を覚えるのに時間がかかる
- 依存パッケージの数が、人の目で追えない
- セキュリティ更新が絶えず来る
- 時間が経つと、ビルドが通らなくなる
- 小さなサイト一つにも、大きなフォルダが要る
これは技術そのものの複雑さではなく、自分たちで足した複雑さだ。素の HTML と CSS と JavaScript に戻ると、この五つが一度に片づく。ブラウザはこの三つを直接読むので、間に変換の層が要らない。そして AI が最も安定して書けるのも、この三つと、文字の原稿だ。
WordPress には四つの問題がある
「React は使っていない、うちは WordPress だ」── 多くの人がそう思っているはずだ。
それは事実に合っている。世界の Web サイトの 40.1% が WordPress で動いている(W3Techs、 2026-10-05)。日本でも企業の公式サイト、個人ブログ、ニュースメディア、自治体の Web ── あらゆる場所にある。
ただし WordPress には WordPress の問題がある。React の問題とは質が違い、深刻さは同じか、それ以上だ。
問題 1: 中身が MySQL に入る
WordPress の記事は、テキストファイルではなく、MySQL の中の HTML 混じりのレコードとして保存される。同じ記事を PDF にしたい、別のサイトに移したい、AI に渡して分析したい ── どの道もエクスポート作業から始まる。
しかもエクスポート形式は .xml(WordPress 独自の WXR)で、HTML タグや独自の短縮記法
([gallery] など)が混ざっている。中身が WordPress の側に残る。これは
2-03: 土台を据える ── SQLite・PostgreSQL・pgvector・DuckDB・Polars
で見た「Excel に入ったままのデータ」と同じ形だ。
問題 2: プラグインが攻撃面を増やす
WordPress の機能拡張は、世界中の有志が作ったプラグインに頼る。一つのサイトに何十ものプラグインが入っているのは珍しくない。それぞれが定期的に脆弱性を出す。サイト全体が攻撃面の集合になる。世界の Web の四割が同じ装置で動くということは、攻撃する側から見れば、四割の標的が同じ装置だということでもある。
問題 3: 更新が互いを止める
WordPress 本体、テーマ、プラグイン ── これらが互いに依存している。一つを更新すると、別のものが動かなくなる。「更新を当てたらサイトが落ちた」は、WordPress の運用でよく起きることだ。
問題 4: PHP より Python のほうが、AI の出力が安定する
WordPress のテーマとプラグインは PHP で書かれている。AI は PHP も書けるが、出力の安定度は Python のほうが高い。AI と組んで道具を作るなら、Python の側に寄せたほうが速い。
とくに急ぐのは、ネットショップだ。決済と顧客の個人情報を扱うため、価値の高い標的になる ── カード情報の抜き取り、個人情報の流出。被害が直接、お金と信用に響く。 WordPress のネットショップは、まっ先に静的にするとよい。決済は Stripe などの外部の決済ページに任せ、カード情報を自分で持たない。動く PHP・データベース・管理ログインを手放せば、攻撃面ごと消える。
WordPress からは、七つの手順で抜ける
WordPress を使っているなら、抜け道は決まっている。 2-12: API を作る ── FastAPI で基幹のロジックを出す で扱う 並行稼働 と同じ形で移る。
- 既存の WordPress を動かしたまま、中身を文字(Markdown か AsciiDoc)にエクスポートする(プラグインや CLI ツールで自動化できる。変換コードは AI が書く)
- 新しいサイトを文字の原稿 + 最小限の HTML/CSS + Python で組む
- 同じ URL 構造を保つ(検索の評価を引き継ぐ)
- ステージング環境で動きを確認し、検索エンジンへのインデックス申請を用意する
- DNS を切り替える日を決める
- 切り替えの後、WordPress を読み取り専用にして 1 か月動かす
- 問題が出なければ WordPress を止め、ホスティング契約を解く
費用も変わる。WordPress のホスティングには月額がかかるが、焼いた HTML は 2-02 の一台に置けば追加の費用が無い(置き場は 2-11)。
WordPress も並行稼働で抜ける。文字へエクスポートし、外枠だけ HTML と CSS で書き、 Python で生成して、静的に配る。これで WordPress を止められる。
作り直す時間が無ければ、もっと速い手もある ── 今あるサイトを丸ごと静的に写し取る。ヘッドレスブラウザ(Playwright)で各ページを開き、HTML と、読み込む全ファイル
(CSS・JS・画像・フォント)を保存し、参照を相対パスに書き換える。既定では JavaScript を実行せず、サーバーが返す素の HTML を保存する ── 広告や解析ウィジェットが描画時に足す膨大な DOM が入らない、軽い HTML になる。本文を JS で組み立てるサイトのときだけ、描画を有効にすればいい。どちらにせよブラウザはページが実際に読み込むファイルを残らず辿って落とすので、requests や wget のような取りこぼしがない。これを静的配信に載せれば、動く WordPress を攻撃面ごと止められる。Mac の SiteSucker(有料)と同じことが、
AI に頼めば Python で書ける(このサイトのリポジトリには tools/mirror_site.py がある)。
中身と外枠を、二層に分ける
Web サイトの中身は、二つに分けられる。
| 種類 | 何を書くか | 道具 |
|---|---|---|
| 中身 | 文章、表、引用、コード、図 | AsciiDoc + Mermaid(Markdown でも同じ) |
| 外枠 | ヘッダー、ナビ、フッター、レイアウト、配色 | HTML + CSS + 最小限の JavaScript |
| 接続 | 中身を外枠に流し込んで HTML を生成 | Python |
これを混ぜて書くのが、これまでの Web 制作の問題だった。React のコンポーネントには、文章と書式とロジックが一緒に入っている。WordPress の記事には、HTML タグと文章が一緒に入っている。後から書き換えるとき、この混在が効いてくる。
AI ネイティブな分け方は、中身と外枠を完全に離すことだ。中身は AsciiDoc と Mermaid だけで書き、HTML タグは置かない。外枠は HTML と CSS で書き、各記事の中身には触らない。Python が機械的に繋ぐ。
(AsciiDoc)"] MMD["図 *.mmd
(Mermaid)"] end subgraph Frame["外枠"] HTML["template.html"] CSS["style.css"] JS["main.js(最小)"] end Py(("Python
ビルド")) MD --> Py MMD --> Py HTML --> Py CSS --> Py JS --> Py Py --> Site["静的 HTML サイト"] MD -.->|そのまま| PDF["PDF / 印刷"] MD -.->|そのまま| AI[("AI 分析")] MD -.->|そのまま| Book["電子書籍"] classDef good fill:#e8f5e9,stroke:#7a9a6d,color:#3a4d34 classDef bad fill:#fef3e7,stroke:#c89559,color:#5a3f1a class MD,MMD good class HTML,CSS,JS bad
中身を文字で持つと、出口が増える
中身を AsciiDoc と Mermaid で書く最大の理由は、同じデータが Web 以外の出口にも回ることだ。
同じ原稿のファイルから、
- Web ページが作れる(Python が HTML 化する)
- PDF が作れる(
pandocでも AI でも変換できる) - 印刷用の原稿に変換できる
- 電子書籍(EPUB)が作れる
- AI に渡して要約・翻訳・質問ができる
- 別の媒体への貼り付けに回せる
- Git で差分を見て、履歴を持てる
- 他の人と一緒に編集できる
中身が Web の側に残らない。WordPress や Wix で書いた中身は、サービスが終わるとそこまでだ。 Notion で書いた中身は、Notion の形式の中にある。文字の原稿は、どこにも属さない。
これは 2-07: 文書を取り戻す ── 読む物は adoc、触る表は格子、刷る紙はテンプレート で見た「中身は素のテキストで持つ」考え方の、Web 版だ。入口・中身・出口を分ける。
- 入口 ── 画像、PDF、音声、Word から、AI がテキストに起こす
- 中身 ── AsciiDoc と Mermaid で持ち、Git で版を管理する
- 出口 ── Web、PDF、印刷、AI 分析、電子書籍。用途に応じて Python が変換する
外枠は最小限で書く
外枠 ── ヘッダー、ナビ、フッター、レイアウト、配色 ── は、HTML と CSS で直接書く。最小限で足りる。
- HTML テンプレートは 1〜数ファイル
- CSS は 1 ファイル
- JavaScript は本当に要る所(モバイルメニューの開閉など)だけ、数十行
これらは年に数回しか触らない。外枠に時間を掛けすぎない。骨格は骨格として置き、時間は中身に使う。
手元に置く物
- HTML(構造)── テンプレート 1 ファイル
- CSS(見た目)── 1 ファイル
- JavaScript(動く所だけ)── 数十行
- AsciiDoc + Mermaid(中身)── 記事ごとに 1 ファイル
要らなくなる物
- JavaScript フレームワーク(React、Vue、Angular、Svelte)
- ビルドツール(Webpack、Vite、Turbopack、Parcel)
- TypeScript(素の JS で足りる)
- CSS フレームワーク(Tailwind、Bootstrap)
- パッケージマネージャ(npm、yarn、pnpm)と
node_modules
残るのは、素の Web 標準と文字の原稿だけだ。HTML / CSS / JS には変換の層が無く、 AsciiDoc と Markdown は AI がよく扱う記法だ。どちらも、AI が出した物をそのまま置ける。
npm を離れるもう一つの理由は、サプライチェーンにある
ビルドが遅いこと、依存が重いことだけが理由ではない。npm を離れるもう一つの理由は、 レジストリを通して他人のコードが実行される という構造にある。
npm レジストリでは、サプライチェーンを狙う攻撃が繰り返し起きている。似た名前のパッケージで誤インストールを誘うタイポスクワッティング、依存パッケージ経由のコード注入、メンテナーのアカウント侵害によるパッケージの乗っ取り、ポストインストールスクリプトでの秘密情報の流出 ── 確かめられる代表例だけでも、こう並ぶ。
- 2018 年:
event-stream事件(暗号通貨ウォレット狙い) - 2021 年:
ua-parser-js事件(マイニング・情報窃取のマルウェア注入) - 2022 年:
colors、faker事件(作者自身による破壊コミット) - 2024 年:
xz-utilsのバックドア(npm ではないが、長期のメンテナー乗っ取りの典型例) - 2025 年 9 月:
chalk・debugなど 18 以上のパッケージの乗っ取り(メンテナーが偽の npm からのメールで認証情報を抜かれた)
ほぼ毎年、大きな事件が報告されている。個別の事件は、 3-02: 物語を確かめる ── ベンダーの語りを一次情報で検算する の手順で、各自が一次情報に当たって確かめてほしい。
この構造は、npm の設計から来ている
問題は、npm の生態系の設計そのものにある。
- 一つのプロジェクトが数百〜数千のパッケージを推移的に依存する
- それらのパッケージは、世界中の個人メンテナーが管理している
- 更新は自動で取られる(
npm installで最新が来る) - パッケージマネージャは依存先のコードを検査せずに実行する
- ビルド手順や postinstall フックで、任意のコードが走る
このうち一つが乗っ取られれば、それを取り込んだ全部のプロジェクトに届く。一つのパッケージの乗っ取りが、それを知らずに使っている全員に及ぶという形は、npm を使う限り付いてくる。この構造がどう効いてくるかは 3-05: ロックイン問題 の話と噛み合う。
これに対して、この章のスタックは依存が少ない。AsciiDoc + HTML + CSS + JS は外部依存ゼロ(ブラウザ標準だけ)、Python のビルドの依存は一桁だ。一桁なら、全てのソースを AI と一緒に読み通せる。怪しい挙動があれば気付ける。何百何千の依存は、人の目では追い切れない。この章の決めは、依存を一桁に保つことだ。侵害される表面積が、そのぶん小さい。
追い切れない依存に AI を入れるということ
AI 時代に、もう一つ重い点がある。
AI のエージェントは、エディタの裏でファイルを読み、コマンドを実行する。開発機の
node_modules のどこかに悪意のあるパッケージが潜んでいれば、*AI 経由でその実行が引き起こされる* 可能性がある。
追い切れない数の依存を抱えるプロジェクトに AI を入れることは、信頼できない他人をその数だけ、自分の作業空間に招き入れることと同じだ。
依存の無いスタックなら、この心配がない。HTML、CSS、文字の原稿は、ただのテキストだ。実行されるコードは、自分と AI が書いた物だけになる。「軽い」「速い」だけでなく、「侵害される入口が無い」── これが依存を減らすもう一つの理由だ。
Python が中身と外枠を繋ぐ
AsciiDoc を HTML に、Mermaid の図を SVG か画像に、そして外枠のテンプレートに流し込む ── これは Python スクリプトの仕事だ。
articles/foo.adoc ──→ Python ──→ html/foo/index.html
↑
tools/templates/article.html(外枠)
このスクリプトを自分で書く必要は無い。AI に頼んで書いてもらう。pyasciidoc で
AsciiDoc を HTML にし、Jinja2 でテンプレートに流し込む。短いスクリプトで済む。
依存は一桁で収まる。
pyasciidoc── AsciiDoc のパーサ(markdown-it-pyの上に作られていて、Markdown もそのまま読める)Jinja2── HTML テンプレートエンジンPillow── 画像処理(OG 画像の生成など、要るときだけ)- Mermaid の図は、ブラウザ側で描くか、
mermaid-cliで SVG にする
uv add pyasciidoc jinja2 pillow を一度打てば、あとは git clone && uv sync で他の機械でも動く。node_modules は出てこない。
このサイト ── aiseed.dev ── のビルドスクリプト(tools/build_article.py)も、ほとんど
AI が書いた。ビルドツールを自分の手元に持てる時代だ。フレームワークの規約に従う代わりに、自分の規約で書く。図と資料の作り方は
2-13: 図と資料を作る ── Mermaid・Marp・そのほかの道具
に続く。
動く部分は、FastAPI を一つ選ぶ
「動的なサイトは作れないのでは」と思うかもしれない。動く部分はサーバー側に置く。 Python の FastAPI、これ一つでいい。
Flask、Django、Go、Rust、Ruby ── 選択肢は山ほどある。しかし選択肢を増やすと、組織はそのぶん分かれる。「FastAPI で書こう」と決めれば、そこから先の議論が終わる。 選んだら、選んだことを忘れる。これが道具立ての考え方だ。
なぜ FastAPI か。
- Python なので、この連載のスタックと揃う
- 型ヒントと Pydantic が自然に使える
- async が標準に入っている
- OpenAPI のドキュメントが自動で出る
- AI が書きやすい(オープンソースで学習データが多い)
- 定型の記述が少ない
そして、その FastAPI も最小限に保つ。
- ORM を使わない。PostgreSQL のドライバ(asyncpg または psycopg)で SQL を直接書く
- 層を積まない。Repository 層、Service 層、Domain 層を置かず、リクエストを受け、SQL を実行し、HTML を返す
- 依存を増やさない。FastAPI、PostgreSQL ドライバ、Jinja2(要るとき)の三つで足りる
- 設定ファイルを増やさない。環境変数だけで動かす
- 一つのプロセスで動かす
一つの関数の形は、こうなる。
@app.get("/items", response_class=HTMLResponse)
async def items():
rows = await conn.fetch("SELECT name, price FROM items")
return render("items.html", rows=rows)
リクエストを受け、SQL を実行し、HTML を返す。ここから先は、これ以上の何が要るのかを問い続ける。AI が直接 SQL を書ける以上、ORM の抽象化を挟まなくても届く。書き方は AI が知っているので、人が渡すのは決めたことのほうだ。
クライアント側の JavaScript も最小限でいい。リンク遷移は <a>、フォーム送信は
<form> で足りる。部分的な更新が要るなら HTMX(数 KB のライブラリで、フレームワークではない)。WebSocket は本当に必要になったときに足す。最初から SPA を組まない。
FastAPI の書き方と、基幹のロジックを API として出す手順は 2-12: API を作る ── FastAPI で基幹のロジックを出す が受け持つ。ここでは、選ぶ理由までで足りる。
この形で作れるもの
「中身は文字、外枠は HTML と CSS、Python が繋ぐ」は、実例で見るのが速い。
- 個人事業主のポートフォリオ ── 作品ごとの
.adocと画像、テンプレート 1 ファイルとstyle.css1 ファイル - 中小企業のコーポレートサイト ── 会社概要・サービス・問い合わせ・ブログの原稿。問い合わせフォームは FastAPI と 2-08 のメール、求人一覧は SQLite + FastAPI
- NPO のイベント告知 ──
events/2026-04-foo.adocのようなイベントごとの原稿。年別の目次は Python が自動生成、申し込みは 2-12 の予約の頁 - 学校の学級通信(保護者限定)── 週次の原稿と写真、学校の色で
style.css1 ファイル。Basic 認証(htpasswd)か、2-05: 門番を立てる ── PocketBase で認証を一つに の門番の内側に置く - 研究室の論文・データ公開 ── 論文の原稿と、データセットのメタデータ。データは Parquet のまま直接ダウンロードさせる
共通するのは、依存が一桁で、ビルドが短く、追加の費用がほぼ無く、コードのほとんどを AI が書くことだ。作るのも保守するのも軽い。このサイト(aiseed.dev)自身が、この構成で動いている。
この組み合わせは、10 年後も動く
フレームワークは、大きな版が上がると書き方が変わり、古い書き方が通らなくなる。ビルドツールの設定も、版に合わせて書き直しが要る。
HTML、CSS、JavaScript の基本仕様は、後方互換が保たれてきた。1999 年の HTML 4.01 で書いたファイルは、今のブラウザでも開く。Markdown は 2004 年の初版から、AsciiDoc は 2002 年の初版から、ほとんど変わっていない。この先も同じファイルが読める。
フレームワークは時代に依存する。Web 標準と文字の原稿は時代を超える。
数字で見る
決めるときに効く数字は、依存の数だ。このサイト(aiseed.dev)で数えると、Python の依存は requirements.txt に 9 個、焼いた頁は 443、ビルドのスクリプトは 7,928 行
(2026-10-05、公開リポジトリ aiseed-dev/website で数えられる)。React・Next.js・
TypeScript はゼロ。AsciiDoc と Mermaid と最小限の HTML/CSS だけだ。9 個の依存は、一つ残らず読める。
決めたことを、そのまま渡す
この章の決定を一覧にしておく。AI に作らせるときは、これを渡せば足りる。書き方は AI が知っているので、人が渡すのは決めたことのほうだ。
| 決める所 | 決めたこと |
|---|---|
| 中身の形式 | AsciiDoc + Mermaid(Markdown でも同じ)。中身に HTML タグは書かない |
| 外枠 | HTML テンプレート 1〜数ファイル、CSS 1 ファイル、JavaScript は数十行 |
| 繋ぐ所 | Python。pyasciidoc + Jinja2(画像が要るときだけ Pillow) |
| 置き場 | 中身は articles/、外枠は tools/templates/、出力は html/。すべて Git の中に置く |
| 置かない物 | JS フレームワーク、ビルドツール、TypeScript、CSS フレームワーク、npm と node_modules |
| 動く部分 | FastAPI。ORM は積まず SQL を直接書く。テンプレートは Jinja2、返すのは HTML |
| 部分更新 | 要るときだけ HTMX。SPA にはしない |
| 公開 | 焼いた静的ファイルを配る。自分の一台か、Cloudflare Pages か(2-11) |
確かめ方
この章は、次の五つができていれば済みだ。
- 記事一本が AsciiDoc のファイルとして置いてあり、その中に HTML タグが一つも入っていない
- 一つのコマンドでビルドが走り、
html/の下にページが出て、数秒で終わる - できた HTML をブラウザで開くと、外枠(ヘッダー・ナビ・フッター)と本文が出て、Mermaid の図が描かれている
- 依存パッケージの一覧が数行に収まり、
node_modulesがどこにも無い - 同じ原稿から PDF を作ってみて、中身がそのまま出る
uv add pyasciidoc jinja2 pillow
uv run python -c "import pyasciidoc, jinja2; print('ok')"
人が持つ物
人が渡す値
- サイトの構成 ── どのページを出し、どういう順で並べるか
- 配色、ロゴ、書体
- URL の付け方(既存サイトから移すときは、元の URL 構造)
- 公開してよい文章と画像の範囲
- 依存に入れてよいパッケージ
AI が「やる前に言う」操作
- 生成先のフォルダを一括で消す、または既存のサイトを上書きする
- 既存の WordPress を停止する、ホスティング契約を解く
- 公開済みの URL 構造を変える
- 自分以外のサイトへクロールを掛ける(
tools/mirror_site.pyの向け先) - 新しい依存パッケージを足す
確かめた版と日付
pyasciidoc0.5(PyPI)、markdown-it-py、Jinja2、Pillow── 版は指定していない- Playwright、
mermaid-cli── 版は指定していない - FastAPI、asyncpg / psycopg、HTMX ── 版は指定していない
- WordPress の普及率は W3Techs、npm の事件は各社の報告で 2026-10-05 に確認
- 手順を書いたのは 2026-09-21、見直したのは 2026-10-05
- 版が上がっていたら、AI に公式の手順を確かめさせてから進める
まとめ
Web を作る道具を、二層に分ける。
- 二層 ── 中身は AsciiDoc と Mermaid、外枠は HTML と CSS と最小限の JavaScript、両者を Python が繋ぐ
- WordPress から抜ける ── 七つの手順で文字に移す。時間が無ければ Playwright で丸ごと静的に写し取る
- 中身の出口 ── 同じ原稿が、Web にも PDF にも印刷にも AI への入力にも電子書籍にもなる
- 依存は一桁 ── npm のサプライチェーンの事件は、ほぼ毎年ある。一桁の依存なら全部読み通せる。追い切れない依存に AI を入れることは、信頼できない他人をその数だけ招くのと同じだ
- 動く部分 ── FastAPI を一つ選び、ORM も層も積まない
- 時間に強い ── 後方互換が保たれてきた Web 標準と、初版から変わらない文字の記法
ここまでで、公開できる HTML が手元にできた。次章では、その html/ を公開する。置き場は二つ、2-02 の自分の一台(Caddy)か、Cloudflare Pages か。