1SSGは大げさすぎないか

静的サイトジェネレーター。Hugo、Astro、Next.js、Gatsby — 選択肢は山ほどある。
だが、何を導入するか悩んでいて気が付いた。もっとシンプルにできないか?

きっかけは、AIによる自社サイトの開発だった。
記事が増えてくると、ヘッダーやフッター、メタタグの共通化がしたくなる。コンポーネント分離、共通要素の管理、条件分岐によるレンダリング。

やりたいことを並べてみると、それってPHPの成り立ちそのものでは?

<?= $var ?> はテンプレート構文。<?php include ?> はコンポーネント。
さらに、PHPはプログラミング言語だ。他にも色々とできる。

であれば、PHP組み込みサーバーでレンダリングした結果をそのまま保存すれば、それがSSGではないか。そう思って作ったのが、この仕組みだ。

2仕組み

仕組みは3ステップ。

PHPで書く HTMLに <?php include ?> を混ぜるだけ
PHPでSSRする 組み込みサーバー php -S localhost:8090
PHPで結果を保存する curlで取得 → 保存(= SSG)

これだけ。この仕組み自体は驚くほど単純で、最初のプロトタイプは1時間ほどで完成した。

発想から2時間ほどで、本番(ndocs.jp / nanomix.co.jp )で動き始めた。

3PHPはプログラミング言語だ

ここが一番大事なポイントかもしれない。

一般的なSSGのテンプレートエンジン(Liquid、Nunjucks、Handlebars)は「テンプレート記法」でしかない。条件分岐やループなど最低限のロジックが書ける程度の、制限された構文だ。

PHPは違う。汎用プログラミング言語だ。ビルド時に、何でもできる。

画像のインライン化
小さなアイコンをBase64でHTTPリクエストなしに埋め込む
CSS/JSの結合・圧縮
複数ファイルを1つにまとめてビルド
外部APIとの連携
CMSやスプレッドシートからコンテンツを取得
AIとの連携
メタディスクリプションの自動生成など
キャッシュバスター
ファイルハッシュから ?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') ?>">
SSGのプラグインシステムは、SSGの仕様の上に作られた拡張。PHPのビルド時処理は、言語そのものの力。プラグインのAPIを覚える必要もない。やりたいことをPHPで書くだけ。
補足: Viewにロジックを書くのはアンチパターン?

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ディレクトリ構成

www.example.com/ ├─ build.php # ビルドツール本体 ├─ build.bat # ダブルクリックでビルド ├─ start-server-dev.bat # 開発サーバー起動 ├─ src/ # ソース(PHPテンプレート)← 開発サーバーのドキュメントルートも兼ねる │ ├─ _php/ # PHP基盤(ビルド除外) │ │ ├─ init.php # 共通初期化 │ │ ├─ Site.php # staticメソッド集 │ │ ├─ router.php # .html→PHP実行ルーター │ │ └─ partials/ # コンポーネント(HTML部品) │ │ ├─ header.php # 共通ヘッダー │ │ ├─ footer.php # 共通フッター │ │ └─ nav.php # グローバルナビ │ ├─ index.html # <?php include ?> 入り │ └─ css/ js/ img/ # 静的ファイル └─ public_html/ # 出力(本番デプロイ対象) ├─ index.html # 純粋なHTML(PHPタグなし) └─ css/ js/ img/ # そのままコピー

ポイントは _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行もない。

  1. ソースディレクトリの全ファイルを再帰的に収集(_php/ は除外)
  2. 各ファイルの内容を読み、<?php または <?= を含むか判定
  3. PHPテンプレート → 開発サーバーにHTTPリクエスト、レスポンスを保存
  4. 静的ファイル → タイムスタンプ比較、新しければコピー
  5. 出力先にだけ存在するファイル(孤立ファイル)を検出

安全装置も組み込んである:

  • dest保護 — 出力先のファイルがソースより新しい場合はスキップ。直接編集を誤って上書きしない
  • 孤立検出 — ソースに存在しないのに出力先にだけあるファイルを警告
  • dry-run--dry-run オプションで実行せず対象一覧のみ表示
  • 差分ログ--diff オプションでビルド後にgit diffを表示
レンダリング結果をHTTPで取得するということは、開発サーバーが見せる画面と100%同じHTMLが保存されるということ。CSS/JSのパス解決も、相対パスの展開も、全てサーバーが処理した結果がそのまま出力される。「ビルドしたら壊れた」が原理的に起きない。

8環境はポータブル

この仕組みの最大の特徴は、環境を選ばないこと。

Node.jsベースのSSGでプロジェクトを移動するとき、何が必要か考えてほしい。Node.jsのインストール、npm install、依存関係の解決、バージョンの互換性確認 — これだけで30分は潰れる。

PHP-SSGは違う。

  1. git clone する
  2. build.bat をダブルクリックする
  3. 終わり。

なぜか? エコシステムごとバージョン管理に入っているからだ。

PHPのポータブル版(php.exe + 付属DLL)はわずか数十MB。リポジトリの .dev-tools/ に含めておけば、git clone した瞬間にビルド環境が揃う。npmもcomposerもインストール不要。環境構築という概念そのものが消える。

仮にexeをリポジトリに含めたくなくても問題ない。特定のディレクトリに php.exephp.ini を置くだけで済む。PHPの設定ファイルは php.ini 1つだけ — tsconfig.json.babelrcpostcss.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 /> と本質は同じだが、ビルドパイプラインがない分だけ壊れない。

誤解のないように — 複雑なエコシステムが悪いわけではない。Reactの仮想DOMによる高速な差分レンダリング、Next.jsのISR・ミドルウェア・API Routes — それぞれ強力な武器を持っている。だが、それらの機能が不要な場面では、そのエコシステムは過剰だ。AI時代は特に、スタックの複雑さ = リスクになる。

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スケールする余地

「小規模向けでしょ?」— そう思うかもしれない。だがこの方式、テンプレートもビルドも拡張の余地は広い。

レイアウト継承
ベーステンプレートで共通構造を一元管理
データ駆動
JSON/CSVから一覧ページを自動生成
Markdown対応
.mdファイルをPHPテンプレートでラップ
並列ビルド
curl_multi で100ページ同時処理
差分ビルド
変更のあったページだけ再ビルド
ルート駆動
routes.json で10,000ページも対応

PHPは起動が軽く、I/Oが速く、HTTP処理が得意。テンプレートレンダリングはまさにPHPの得意分野だ。大規模化しても、言語特性が足を引っ張ることはない。

もちろん、数千ページのブログやドキュメントサイトを最初からPHP-SSGで始める必要はない。HugoやAstroが適切な場面は確実にある。ポイントは、PHP-SSGが「小規模専用」ではないということ。成長に合わせて道具を育てる選択肢がある。

12まとめ

外部ライブラリはゼロ。設定ファイルもゼロ。学ぶべき独自概念もゼロ。
PHPが動く環境さえあれば、今日から始められる。

PHPを「古い」と感じる人は多いかもしれない。だが、テンプレートエンジンとして見た場合、PHPほど「そのまま使える」言語は他にない。HTMLの中に <?= ?> を書くだけ。これ以上シンプルなテンプレート構文は存在しないだろう。

そしてシンプルな技術は、AI時代に強い。壊れにくく、理解しやすく、持ち運べる。


複雑な道具が悪いわけではない。ただ、問題がシンプルなら、解決策もシンプルでいい。

ブラウザで見えている画面が、そのまま本番になる。それだけのことだ🥴