1SSGは大げさすぎないか
静的サイトジェネレーター。Hugo、Astro、Next.js、Gatsby — 選択肢は山ほどある。
だが、何を導入するか悩んでいて気が付いた。もっとシンプルにできないか?
きっかけは、AIによる自社サイトの開発だった。
記事が増えてくると、ヘッダーやフッター、メタタグの共通化がしたくなる。コンポーネント分離、共通要素の管理、条件分岐によるレンダリング。
やりたいことを並べてみると、それってPHPの成り立ちそのものでは?
<?= $var ?> はテンプレート構文。<?php include ?> はコンポーネント。
さらに、PHPはプログラミング言語だ。他にも色々とできる。
であれば、PHP組み込みサーバーでレンダリングした結果をそのまま保存すれば、それがSSGではないか。そう思って作ったのが、この仕組みだ。
2仕組み
仕組みは3ステップ。
これだけ。この仕組み自体は驚くほど単純で、最初のプロトタイプは1時間ほどで完成した。
発想から2時間ほどで、本番(ndocs.jp / nanomix.co.jp )で動き始めた。
3PHPはプログラミング言語だ
ここが一番大事なポイントかもしれない。
一般的なSSGのテンプレートエンジン(Liquid、Nunjucks、Handlebars)は「テンプレート記法」でしかない。条件分岐やループなど最低限のロジックが書ける程度の、制限された構文だ。
PHPは違う。汎用プログラミング言語だ。ビルド時に、何でもできる。
?v=a3f1b2c4 を自動付与テンプレートエンジンの「できること」はテンプレートの仕様で決まる。PHPの「できること」は、プログラミング言語の限界 — つまり、事実上の無限だ。
<!-- 例: 画像をBase64インライン化 -->
<img src="data:image/png;base64,<?= base64_encode(file_get_contents('icon.png')) ?>">
<!-- 例: ビルド時にAPIからデータ取得 -->
<?php $news = json_decode(file_get_contents('https://api.example.com/news')); ?>
<?php foreach ($news as $item): ?>
<article><h2><?= $item->title ?></h2></article>
<?php endforeach; ?>
<!-- 例: キャッシュバスター -->
<link rel="stylesheet" href="/css/style.css?v=<?= Site::v('/css/style.css') ?>">
MVCの世界では「Viewにロジックを書くな」が原則だ。それ自体は正しい。だがここでやっているのはWebアプリケーションのViewではない。静的HTMLを生成するビルドテンプレートだ。ビルドが終われば、PHPのコードは1行も残らない。本番に届くのは純粋なHTMLだけ。
AI時代にはもう一つ理由がある。AIはレイヤーが少ないほど正確に扱える。Controller → Service → Repository → View と分離された構造は、人間の認知負荷を下げるが、AIにとっては参照先が増えるだけだ。「HTMLの中にPHPが混ざっている1ファイル」の方が、AIは壊さない。
4SSGとの比較
既存のSSGと何が違うのか。正直に比較する。
| 一般的なSSG | PHP-SSG | |
|---|---|---|
| ランタイム | Node.js + npm | php.exe のみ |
| 依存サイズ | node_modules 数百MB | 0(ゼロ) |
| テンプレート構文 | JSX / Nunjucks / Liquid 等 | PHP(<?= ?>) |
| 設定ファイル | 複数必要 | なし |
| 学習コスト | フレームワーク固有の概念 | PHPの基本構文のみ |
| ビルド結果の予測 | buildとdevで差異が出ることがある | サーバー表示 = ビルド結果(100%一致) |
| 開発サーバー | Vite / webpack-dev-server 等 | PHP組み込みサーバー(標準搭載) |
| HMR | あり(設定が必要) | 不要(PHPは毎回サーバーサイド処理) |
| バージョン破壊 | メジャーアップデートで壊れがち | PHPの後方互換性は非常に高い |
| ポータビリティ | npm install が必要 | git clone で即動作 |
| 適正規模 | 数十〜数千ページ | ビルドツールの作り込み次第 |
「適正規模はビルドツールの作り込み次第」— これは嘘ではない。build.php を高機能にすれば数百ページでも回る。だが逆に言えば、シンプルなビルドツールのままなら、小〜中規模がちょうどいい。会社のWEBサイト、LP、数十ページ程度のサービスサイト — このあたりが素のまま使うスイートスポット。
道具は問題の大きさに合わせて選ぶ。
そして問題が変われば、道具を育てればいい。
5ディレクトリ構成
ポイントは _php/ ディレクトリ。ここにPHPの共通コード(ヘッダー、フッター、ユーティリティ)を置く。ビルド時にはこのディレクトリごと除外されるので、本番には一切残らない。
開発サーバーの router.php が .html ファイルをPHPとして実行してくれるので、拡張子はそのまま .html のまま。URLも本番と同じ構造で開発できる。
6テンプレートの書き方
テンプレートといっても、独自構文は一切ない。普通のHTMLに <?php を混ぜるだけ。
<?php require_once __DIR__ . '/_php/init.php'; ?>
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- 共通のmeta、CSS読み込み -->
<?= Site::head('ページタイトル') ?>
</head>
<body>
<?= Site::header() ?>
<!-- ページ固有のコンテンツ -->
<main>
<h1>ここは普通のHTML</h1>
</main>
<?= Site::footer() ?>
</body>
</html>
ビルドすると、<?= Site::header() ?> の部分が実際のHTML(ナビゲーションバーなど)に展開された状態で保存される。出力ファイルにPHPのコードは一切残らない。
localhost:8090 を開けば、PHPがリアルタイムで処理した結果が表示される。ファイルを保存してリロードするだけ。HMR(Hot Module Replacement)は不要 — なぜなら、PHPは毎回サーバーサイドで処理するから。7ビルドの仕組み
build.php の処理フローは驚くほどシンプル。コード量は200行もない。
- ソースディレクトリの全ファイルを再帰的に収集(
_php/は除外) - 各ファイルの内容を読み、
<?phpまたは<?=を含むか判定 - PHPテンプレート → 開発サーバーにHTTPリクエスト、レスポンスを保存
- 静的ファイル → タイムスタンプ比較、新しければコピー
- 出力先にだけ存在するファイル(孤立ファイル)を検出
安全装置も組み込んである:
- dest保護 — 出力先のファイルがソースより新しい場合はスキップ。直接編集を誤って上書きしない
- 孤立検出 — ソースに存在しないのに出力先にだけあるファイルを警告
- dry-run —
--dry-runオプションで実行せず対象一覧のみ表示 - 差分ログ —
--diffオプションでビルド後にgit diffを表示
8環境はポータブル
この仕組みの最大の特徴は、環境を選ばないこと。
Node.jsベースのSSGでプロジェクトを移動するとき、何が必要か考えてほしい。Node.jsのインストール、npm install、依存関係の解決、バージョンの互換性確認 — これだけで30分は潰れる。
PHP-SSGは違う。
git cloneするbuild.batをダブルクリックする- 終わり。
なぜか? エコシステムごとバージョン管理に入っているからだ。
PHPのポータブル版(php.exe + 付属DLL)はわずか数十MB。リポジトリの .dev-tools/ に含めておけば、git clone した瞬間にビルド環境が揃う。npmもcomposerもインストール不要。環境構築という概念そのものが消える。
仮にexeをリポジトリに含めたくなくても問題ない。特定のディレクトリに php.exe と php.ini を置くだけで済む。PHPの設定ファイルは php.ini 1つだけ — tsconfig.json も .babelrc も postcss.config.js も要らない。ランタイムの設定すら、たった1ファイル。
.dev-tools/php/ としてPHP本体をリポジトリに含めている。別プロジェクトへの移植は、src/ + _php/ + build.php をコピーするだけ。5分で新しいサイトのビルド環境が立ち上がる。そして忘れてはいけないのが、PHPの後方互換性の高さ。PHP 7で書いたコードは PHP 8 でもほぼそのまま動く。Node.jsのメジャーアップデートで npm install が壊れる — そういう心配がない。エコシステムごとバージョン管理に含めても、数年後に「動かない」が起きにくい。
9AI時代だからこそシンプルが強い
AI(Claude、ChatGPT、Gemini、Copilot)にコードを書かせる時代になった。だからこそ、技術スタックの選び方が変わる。
AIが得意なのは、シンプルで普遍的な技術だ。HTML、CSS、PHP — 30年以上の歴史がある、膨大な学習データがある技術。AIはこれらを非常に安定して扱える。AIは設定ファイルを壊すが、HTMLとPHPはほとんど壊さない。
逆にAIが壊しやすいのは:
vite.config.tsの設定- webpack loader のチェーン
- プラグインのバージョン互換
- tsconfig.json のパス解決
- babel/postcss のプリセット設定
エコシステムが複雑であればあるほど、AIが介入できるポイントが増える。そしてAIが触るポイントが増えるほど、壊れる確率も上がる。
PHP-SSGのスタックは HTML + PHP だけ。
AIが壊す場所がほとんどない。
AIが壊しやすい構成
- node / npm / vite / webpack
- typescript / babel / eslint
- plugin / loader / adapter
- 設定ファイルが10個以上
- バージョン間の互換性問題
AIが壊しにくい構成
- HTML + PHP
- 設定ファイルなし
- 独自構文なし
- 依存ゼロ
- 30年の学習データ
AIにテンプレートを書かせるとき、<?= Site::header() ?> と書くだけでいい。JSXの <Header /> と本質は同じだが、ビルドパイプラインがない分だけ壊れない。
10フロントエンドは20年かけてPHPに戻ってきた
少し歴史を振り返る。
1995年、PHPが誕生したとき。Webページの「コンポーネント化」は、こう書かれていた:
<?php include("header.php"); ?>
<h1>Hello</h1>
<?php include("footer.php"); ?>
2016年、React がSSRを始めたとき:
<Header />
<h1>Hello</h1>
<Footer />
やっていることの本質は同じだ。Viewを部品に分割して、HTMLを生成する。
React が新しくしたのは、仮想DOM、状態管理、クライアントサイドレンダリング、開発体験。だが「コンポーネント化してHTMLを出力する」という構造の思想自体は、PHPが30年前に完成させていた概念だ。
フロントエンドは20年かけてPHPに戻ってきた。
Next.js の App Router、Astro の Islands Architecture、Remix の Server Components — 最新のフロントエンドフレームワークは、どれもサーバーサイドレンダリングに回帰している。PHPが最初からやっていたことに、巨大なエコシステムを構築した上で、再び辿り着いた。
PHP-SSG は、その原点に立ち返る選択だ。30年の実績がある方法で、余計なものを足さずに、静的HTMLを生成する。
11スケールする余地
「小規模向けでしょ?」— そう思うかもしれない。だがこの方式、テンプレートもビルドも拡張の余地は広い。
curl_multi で100ページ同時処理routes.json で10,000ページも対応PHPは起動が軽く、I/Oが速く、HTTP処理が得意。テンプレートレンダリングはまさにPHPの得意分野だ。大規模化しても、言語特性が足を引っ張ることはない。
12まとめ
外部ライブラリはゼロ。設定ファイルもゼロ。学ぶべき独自概念もゼロ。
PHPが動く環境さえあれば、今日から始められる。
PHPを「古い」と感じる人は多いかもしれない。だが、テンプレートエンジンとして見た場合、PHPほど「そのまま使える」言語は他にない。HTMLの中に <?= ?> を書くだけ。これ以上シンプルなテンプレート構文は存在しないだろう。
そしてシンプルな技術は、AI時代に強い。壊れにくく、理解しやすく、持ち運べる。
複雑な道具が悪いわけではない。ただ、問題がシンプルなら、解決策もシンプルでいい。
ブラウザで見えている画面が、そのまま本番になる。それだけのことだ🥴