Turning a headless WordPress into a static blog
WordPress stays private, the public site is plain HTML. A short tour of the PHP generator behind this blog.
This blog is written in WordPress and served as plain static files. WordPress never sees a reader; a small PHP script pulls the posts through the REST API and writes HTML into public_html.
The shape of it
- Fetch posts, categories and tags from the API.
- Render each post with a tiny template.
- Write archive, tag and category pages, the RSS feed and the 404.
- Purge the CDN cache.
Fetching posts
<?php
// Pull every published post from the private API, page by page.
function fetch_posts(string $base, string $token): array {
$posts = [];
$page = 1;
do {
$ctx = stream_context_create(['http' => [
'header' => "Authorization: Bearer {$token}\r\n",
]]);
$json = file_get_contents("{$base}/posts?page={$page}&per_page=50", false, $ctx);
$batch = json_decode($json, true) ?: [];
$posts = array_merge($posts, $batch);
$page++;
} while (count($batch) === 50);
return $posts;
}
Nothing clever: a loop until a page comes back short. The template side is just as dull, which is the point.
<?php
// Escape everything that isn't already trusted HTML from the editor.
function render_post(array $p): string {
$title = htmlspecialchars($p['title'], ENT_QUOTES);
$date = date('j M Y', strtotime($p['date']));
return <<<HTML
<article lang="{$p['lang']}">
<h1>{$title}</h1>
<time datetime="{$p['date']}">{$date}</time>
{$p['content']}
</article>
HTML;
}
What it looks like




Placeholder post — the real write-up will replace this once the generator ships.