Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Ahondev/portfolio-v2/llms.txt

Use this file to discover all available pages before exploring further.

The Static Site Generator (SSG) is one of the most powerful features in WP SSR Framework. Because the frontend is a React SPA, search engine crawlers and AI agents would normally see a blank <div id="root"> — missing all your content. SSG solves this by calling an external headless-browser render API, saving the fully-rendered HTML to disk, and serving it directly to bots via the BotDetector middleware, while human visitors continue to enjoy the fast client-side SPA experience.

How SSG Works

1

Build the URL from the post

StaticSiteGeneratorService::generatePostHTML(WP_Post $post) resolves the correct public URL for the post. For WordPress pages it uses WebRouter::slugToPath() combined with get_home_url(); for custom post types it calls get_post_permalink().
public function generatePostHTML(\WP_Post $post): string
{
    if ($post->post_type === 'page') {
        $path = $this->router->slugToPath($post->post_name);
        $url  = get_home_url() . $path;
    } else {
        $url = get_post_permalink($post);
    }

    $html = $this->generateHTML($url);
    $this->storeHTML($post, $html);

    return $html;
}
2

Call the headless-browser render API

generateHTML(string $url): string POSTs to the external render API with a WEB_RENDER_TOKEN Bearer token. The request waits for the network to go idle, uses a 1440×900 viewport, and requests French locale content.
$endpoint = env('WP_ENV') === 'development'
    ? 'http://127.0.0.1:8888/web/render'   // local dev server
    : 'https://api.ahon.dev/web/render';    // production

$response = Http::withHeaders([
    'Authorization' => 'Bearer ' . $token,
    'Accept'        => 'application/json',
    'Content-Type'  => 'application/json',
])->post($endpoint, [
    'url'        => $url,
    'wait_until' => 'networkidle',
    'timeout'    => 15000,
    'viewport'   => ['width' => 1440, 'height' => 900],
    'headers'    => ['Accept-Language' => 'fr-FR'],
    'full_page'  => true,
]);
The API must return { "success": true, "html": "..." }. Any non-2xx response or missing html key throws a RuntimeException.
3

Store the HTML file and update post meta

storeHTML(WP_Post $post, string $html): string writes the rendered HTML to /cache/client/static/{slug}.html and records three post-meta keys for cache-invalidation tracking.
$relativePath = '/cache/client/static/' . $filename;

file_put_contents($filePath, $html);

update_post_meta($post->ID, '_ssg_cached_path',    $relativePath);
update_post_meta($post->ID, '_ssg_last_cached',    current_time('mysql'));
update_post_meta($post->ID, '_ssg_content_hash',   md5($post->post_content . $post->post_modified));
The content hash lets the admin dashboard flag pages whose WordPress content has changed since they were last cached.
4

Serve static HTML to bots

On every front-end request, BotDetector::isBot() inspects the User-Agent header against a curated list of known crawlers (Googlebot, GPTBot, facebookexternalhit, and dozens more). If a bot is detected and a cached file exists, WordPress serves the pre-rendered HTML directly with an X-SSG-Cache: HIT response header and full 304 Not Modified support. Human visitors — and any request with ?json=1 — always bypass the static cache and hit the live SPA.
// BotDetector::isBot() — simplified
$ua = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');

foreach (self::$keywords as $keyword) {
    if (str_contains($ua, $keyword)) {
        return true;
    }
}

return apply_filters('ahon_is_bot', false, $ua);
You can add your own crawler patterns by hooking into the ahon_is_bot filter and returning true.

Cache Lifecycle Methods

MethodDescription
generatePostHTML(WP_Post $post)Full pipeline: resolve URL → render → store
generateHTML(string $url)Call the render API and return raw HTML
storeHTML(WP_Post $post, string $html)Write file + update post meta
deleteCachedFile(WP_Post $post)Remove .html file + delete all three post-meta keys
clearAll()Wipe every file in /cache/client/static/ + bulk-delete post meta via $wpdb
getCacheableItems()Return an array of all cacheable posts with their cache status

getCacheableItems() return shape

[
    'id'          => 42,
    'type'        => 'page',        // 'page' or 'single'
    'type_label'  => 'page',        // raw post_type string
    'title'       => 'Accueil',
    'cached_path' => '/cache/client/static/accueil.html',
    'last_cached' => '2025-01-15 10:32:00',
    'up_to_date'  => true,          // false when content hash has changed
]

WP Admin Dashboard

The SSG dashboard lives at WP Admin → SSG (admin.php?page=ssg-dashboard). It is registered by SSGServiceProvider and requires the manage_options capability.

Generate & Clear

”⚡ Generate All Pages” iterates every cacheable item and calls the render API in sequence, with a live progress bar and scrollable log. “🗑️ Clear All Cache” wipes all files and post meta in one click.

Bulk Actions

Select individual rows and use ”🔄 Regenerate Selected” or “🗑️ Delete Selected” to operate on a subset of pages. The counter badge updates live as you tick checkboxes.

Daily Cron

A checkbox toggles a WordPress cron event (ssg_daily_generation) that regenerates all pages once daily via wp_schedule_event. Uncheck to cancel the schedule.

Admin Bar Status

A persistent indicator in the WP admin bar shows ✅ SSG: Complete (green) or ⚠️ SSG: Need Generation (pulsing red) so you can spot stale caches at a glance without opening the dashboard.

REST API

SSGController exposes three authenticated endpoints under /api/v1/ssg/. All requests must include a valid X-WP-Nonce header.
POST /api/v1/ssg/page
Content-Type: application/json
X-WP-Nonce: <nonce>

{ "id": 42 }
Success response:
{
  "success": true,
  "data": {
    "html": "<!DOCTYPE html>..."
  }
}

Environment Variable

Set WEB_RENDER_TOKEN in your .env file before using SSG. The service throws a RuntimeException at generation time if the variable is missing.
# .env
WEB_RENDER_TOKEN='your-secret-token-here'
Never commit your WEB_RENDER_TOKEN to version control. Add .env to .gitignore and manage secrets through your deployment pipeline.
In local development, spin up the render server on port 8888 and set WP_ENV=development in your .env. The service will automatically target http://127.0.0.1:8888/web/render instead of the production endpoint.

SEO Management

Populate ACF SEO fields so every pre-rendered page has the correct title, description, and OG image.

Caching

Understand the transient cache layer that complements SSG for dynamic API data.

Build docs developers (and LLMs) love