1SSGs Are Overkill

Static site generators. Hugo, Astro, Next.js, Gatsby — there's no shortage of options.
But let's be honest. Do you really need something this heavy?

SSGs are surprisingly bloated.
Node.js, npm, node_modules, config files, plugins, template engines, build pipelines.
All you want is static HTML output. Why does that require such a massive ecosystem?

Think about it.
Component separation, shared element management, conditional rendering — list out what you actually need, and it sounds exactly like what PHP was built for.

<?= $var ?> is template syntax. <?php include ?> is a component.
And PHP is a programming language. It can do much more.

So what if we just render pages with PHP's built-in server and save the output? That would be an SSG — and that's exactly what we built.

2How It Works

Three steps.

Write in PHP Just mix <?php include ?> into HTML
SSR with PHP Built-in server: php -S localhost:8090
Save the Output Fetch with curl → save to file (= SSG)

That's it. The whole thing is remarkably simple. The first prototype was done in about an hour.

This system is already running in production on this site and on our company site (nanomix.co.jp) . Multiple landing pages, policy pages, and the top page — all built this way. It was done almost before we knew it — from concept to production in about two hours.

3PHP Is a Programming Language

This might be the most important point.

Typical SSG template engines (Liquid, Nunjucks, Handlebars) are just "template syntaxes." They offer minimal logic — basic conditionals, loops — within a constrained grammar.

PHP is different. It's a general-purpose programming language. At build time, you can do anything.

  • Inline images as Base64 — embed small icons without HTTP requests
  • Concatenate and minify CSS/JS — merge multiple files into one at build time
  • Fetch data from external APIs — pull content from a CMS or spreadsheet during build
  • Integrate with AI — call an AI API at build time to generate meta descriptions
  • Cache busting — auto-append ?v=a3f1b2c4 from file hashes
  • Environment-based output — emit different code for development vs. production

A template engine's capabilities are defined by the template spec. PHP's capabilities are defined by the language itself — which means they're practically unlimited.

<!-- Example: Inline image as Base64 -->
<img src="data:image/png;base64,<?= base64_encode(file_get_contents('icon.png')) ?>">

<!-- Example: Fetch data from API at build time -->
<?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; ?>

<!-- Example: Cache buster -->
<link rel="stylesheet" href="/css/style.css?v=<?= Site::v('/css/style.css') ?>">
An SSG's plugin system is built on top of the SSG's own spec. PHP's build-time processing is the raw power of the language itself. No plugin APIs to learn. Just write what you need in PHP.

4Compared to Conventional SSGs

How does this differ from existing SSGs? An honest comparison.

Conventional SSG PHP-SSG
Runtime Node.js + npm php.exe only
Dependency size node_modules: hundreds of MB 0 (zero)
Template syntax JSX / Nunjucks / Liquid etc. PHP (<?= ?>)
Config files Multiple required None
Learning curve Framework-specific concepts Basic PHP syntax only
Build predictability Build and dev can diverge Server output = build output (100% match)
Dev server Vite / webpack-dev-server etc. PHP built-in server (included)
HMR Available (needs config) Unnecessary (PHP processes on every request)
Breaking changes Major updates often break things PHP's backward compatibility is excellent
Portability npm install required Works right after git clone
Optimal scale Dozens to thousands of pages Depends on how far you develop the build tool

"Optimal scale depends on the build tool" — fair enough. If you make build.php sophisticated enough, it can handle hundreds of pages. But conversely, with a simple build tool, small to medium scale is the sweet spot. Company websites, landing pages, service sites with a few dozen pages — that's where this shines out of the box.

Choose your tools to match the size of your problem.
And when the problem grows, grow your tools.

5Directory Structure

www.example.com/ ├─ build.php # Build tool ├─ build.bat # Double-click to build ├─ start-server-dev.bat # Start dev server ├─ src/ # Source (PHP templates) ← also serves as dev server docroot │ ├─ _php/ # PHP core (excluded from build) │ │ ├─ init.php # Common initialization │ │ ├─ Site.php # Static method collection │ │ ├─ router.php # Routes .html to PHP execution │ │ └─ partials/ # Components (HTML fragments) │ │ ├─ header.php # Shared header │ │ ├─ footer.php # Shared footer │ │ └─ nav.php # Global navigation │ ├─ index.html # Contains <?php include ?> │ └─ css/ js/ img/ # Static assets └─ public_html/ # Output (deploy target) ├─ index.html # Pure HTML (no PHP tags) └─ css/ js/ img/ # Copied as-is

The key is the _php/ directory. This is where PHP shared code lives — headers, footers, utilities. Since this entire directory is excluded during build, nothing from it ends up in production.

The dev server's router.php executes .html files as PHP, so the file extension stays .html. URLs in development match production exactly.

6Writing Templates

There is no proprietary syntax. Just plain HTML with <?php mixed in.

<?php require_once __DIR__ . '/_php/init.php'; ?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- Shared meta tags and CSS -->
    <?= Site::head('Page Title') ?>
</head>
<body>
    <?= Site::header() ?>

    <!-- Page-specific content -->
    <main>
        <h1>This is plain HTML</h1>
    </main>

    <?= Site::footer() ?>
</body>
</html>

When built, the <?= Site::header() ?> part is expanded into actual HTML (the navigation bar, etc.) and saved. No PHP code remains in the output.

During development, just open localhost:8090 in your browser to see PHP-processed results in real time. Save the file, reload the page. HMR (Hot Module Replacement) is unnecessary — because PHP processes server-side on every request.

7The Build Process

The build.php workflow is remarkably simple. The code is under 200 lines.

  1. Recursively collect all files in the source directory (_php/ is excluded)
  2. Read each file and check if it contains <?php or <?=
  3. PHP templates → send HTTP request to dev server, save the response
  4. Static files → compare timestamps, copy if newer
  5. Detect orphan files (files that exist only in the output directory)

Safety mechanisms are built in:

  • Destination protection — if the output file is newer than the source, skip it. Prevents accidentally overwriting direct edits
  • Orphan detection — warns about files that exist in the output but not in the source
  • Dry-run — the --dry-run flag shows what would be processed without executing
  • Diff log — the --diff flag shows git diff after the build
Fetching rendered output via HTTP means the exact same HTML that the dev server displays gets saved. CSS/JS path resolution, relative path expansion — everything the server processes ends up in the output as-is. "It broke after building" is structurally impossible.

8The Environment Is Portable

The biggest advantage of this approach: it doesn't care about your environment.

Think about what it takes to move a Node.js-based SSG project to a new machine. Install Node.js, run npm install, resolve dependency conflicts, check version compatibility — there goes 30 minutes.

PHP-SSG is different.

  1. git clone the repo
  2. Double-click build.bat
  3. Done.

Why? Because the entire ecosystem is version-controlled.

A portable PHP build (php.exe + bundled DLLs) is only a few dozen megabytes. Include it in a .dev-tools/ directory in the repo, and the moment you git clone, the build environment is ready. No npm, no composer. The concept of "environment setup" simply vanishes.

Even if you'd rather not include the executable in the repo, it's not a problem. Just drop php.exe and php.ini into a designated directory. PHP's configuration file is just php.ini — no tsconfig.json, no .babelrc, no postcss.config.js. Even the runtime's configuration is a single file.

At nanomix, we include PHP itself in the repo under .dev-tools/php/. Porting to a new project means copying src/ + _php/ + build.php. A new site's build environment is up and running in five minutes.

And don't forget PHP's excellent backward compatibility. Code written for PHP 7 runs on PHP 8 with almost no changes. The "npm install broke after a Node.js major update" scenario? That doesn't happen here. Even when you version-control the entire ecosystem, it's unlikely to stop working years later.

9Simplicity Wins in the AI Era

We're in an era where AI (Claude, ChatGPT, Copilot) writes code. That changes how we choose our tech stack.

AI excels with simple, universal technologies. HTML, CSS, PHP — technologies with 30+ years of history and massive training data. AI handles these remarkably well. AI breaks config files, but it rarely breaks HTML and PHP.

What AI tends to break:

  • vite.config.ts settings
  • webpack loader chains
  • Plugin version compatibility
  • tsconfig.json path resolution
  • babel/postcss preset configuration

The more complex the ecosystem, the more places AI has to intervene. The more it touches, the higher the chance something breaks.

PHP-SSG's stack is just HTML + PHP.
There's almost nothing for AI to break.

Easy for AI to Break

  • node / npm / vite / webpack
  • typescript / babel / eslint
  • plugin / loader / adapter
  • 10+ config files
  • Cross-version compatibility issues

Hard for AI to Break

  • HTML + PHP
  • No config files
  • No proprietary syntax
  • Zero dependencies
  • 30 years of training data

When you ask AI to write a template, all it needs to write is <?= Site::header() ?>. Essentially the same as React's <Header />, but with no build pipeline to break.

To be clear — complex ecosystems aren't inherently bad. React's virtual DOM diffing, Next.js's ISR, middleware, API Routes — each brings powerful capabilities. But when those features aren't needed, the ecosystem is overhead. Especially in the AI era, stack complexity = risk.

10Frontend Took 20 Years to Come Back to PHP

A brief look at history.

In 1995, when PHP was born, this is how you "componentized" a web page:

<?php include("header.php"); ?>
<h1>Hello</h1>
<?php include("footer.php"); ?>

In 2016, when React introduced SSR:

<Header />
<h1>Hello</h1>
<Footer />

The essence is the same. Split the view into components and generate HTML.

What React innovated was the virtual DOM, state management, client-side rendering, and developer experience. But the structural idea of "componentize and output HTML" is a concept PHP had already perfected 30 years ago.

Frontend took 20 years to come back to PHP.

Next.js App Router, Astro Islands Architecture, Remix Server Components — the latest frontend frameworks are all converging back toward server-side rendering. They built massive ecosystems, only to arrive back where PHP started.

PHP-SSG is the choice to return to that origin. Generate static HTML using a method with 30 years of proven track record, without adding anything unnecessary.

11Room to Scale

"It's only for small projects, right?" — you might think. But look at the architecture. It actually has significant headroom.

Break down PHP-SSG's structure, and three responsibilities separate cleanly:

  • PHP — build engine (template processing, data transformation)
  • PHP built-in server — SSR renderer (the runtime that generates HTML)
  • build.php — orchestrator (what to build, when, and how)

In other words, just grow the orchestrator (build.php) — the renderer and engine stay as they are. Concrete extensions:

  • Parallel builds — use curl_multi for simultaneous rendering. 100 pages at once is feasible
  • Incremental builds — use hash(src) to build only changed pages
  • Route-driven — define routes.json and handle 10,000 pages
  • Section builds — run build blog, build docs by category

PHP starts fast, handles I/O well, and excels at HTTP processing. Template rendering is precisely PHP's forte. Even at large scale, the language characteristics won't hold you back.

Of course, there's no need to start a thousand-page blog or documentation site with PHP-SSG from day one. Hugo and Astro are the right choice in those scenarios. The point is that PHP-SSG is not "small-scale only." The option to grow your tools alongside your project is always there.

12Summary

PHP-SSG has exactly three components.

  1. PHP built-in serverphp -S localhost:8090
  2. build.php — fetch HTML from the server with curl and save it
  3. PHP templates — include, function calls, conditionals. Standard PHP features only

Zero external libraries. Zero config files. Zero proprietary concepts to learn.

Many dismiss PHP as "old." But as a template engine, no other language is as ready to use out of the box. Just write <?= ?> inside HTML — a simpler template syntax probably doesn't exist.

And simple technology is powerful in the AI era. Hard to break, easy to understand, easy to port.


Complex tools aren't bad. But when the problem is simple, the solution can be simple too.

What you see in the browser goes straight to production. That's all there is to it.