José Moreira

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

  1. Fetch posts, categories and tags from the API.
  2. Render each post with a tiny template.
  3. Write archive, tag and category pages, the RSS feed and the 404.
  4. 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.