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 WP SSR Framework ships a Static Site Generator (SSG) subsystem that pre-renders your React/Vite SPA pages into plain HTML files. Three REST endpoints under /api/v1/ssg/ drive this process: one to generate (or regenerate) a single page, one to cache every eligible post at once, and one to invalidate a cached file. All three endpoints are authenticated — they require a valid WordPress nonce in the request header and are intended to be called from the WP Admin SSG dashboard rather than from public-facing client code.

Authentication

Every /api/v1/ssg/* request must include an X-WP-Nonce header containing a nonce generated server-side with wp_create_nonce('wp_rest'). The WordPress REST API verifies this nonce automatically before the controller action runs.
// Generating the nonce in PHP (enqueued via wp_localize_script)
wp_localize_script('ssg-admin', 'ssgData', [
    'nonce' => wp_create_nonce('wp_rest'),
]);

// Sending it from JavaScript
fetch('/api/v1/ssg/page', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-WP-Nonce': ssgData.nonce,
    },
    body: JSON.stringify({ id: 42 }),
});
Nonces expire after 24 hours. The admin UI fetches a fresh nonce on page load; long-lived scripts or cron tasks must regenerate the nonce before it expires.

How rendering works

When an SSG endpoint triggers HTML generation, StaticSiteGeneratorService calls an external headless-browser rendering API:
  • Production: https://api.ahon.dev/web/render
  • Development (WP_ENV=development): http://127.0.0.1:8888/web/render
The render request is authenticated with a Bearer token from the WEB_RENDER_TOKEN environment variable. The service requests a full-page screenshot at a 1 440 × 900 viewport, waits for networkidle, and expects the API to return { success: true, html: "<string>" }. The resulting HTML is written to web/app/cache/client/static/{slug}.html and three post-meta keys are updated on the WordPress post.
If WEB_RENDER_TOKEN is not set the generator throws a RuntimeException immediately and the endpoint returns a 500 error response.

POST /api/v1/ssg/page

Generates (or regenerates) the static HTML cache for a single WordPress post or page. This is the primary endpoint used by the admin dashboard’s per-item “Generate” button and its bulk-generation progress loop. Base URL (with rewrites): POST /api/v1/ssg/page
Base URL (without rewrites): POST /wp-json/api/v1/ssg/page

Request body

id
integer
required
The WordPress post ID to render. Must pass PHP’s FILTER_VALIDATE_INT. Accepted as a JSON body parameter or a query string value.

Responses

success
boolean
true when HTML was generated and stored successfully.
code
integer
HTTP status code echoed in the body. 200 on success.
data
object
Example success response
{
  "success": true,
  "code": 200,
  "data": {
    "html": "<!DOCTYPE html><html lang=\"fr\">..."
  }
}
Error responses
// Invalid or non-integer id
{
  "success": false,
  "code": 500,
  "error": {
    "message": "Invalid query parameter",
    "context": "global"
  }
}
// Post not found in WordPress
{
  "success": false,
  "code": 500,
  "error": {
    "message": "Invalid post id",
    "context": "global"
  }
}
// Rendering API failure (e.g. WEB_RENDER_TOKEN missing, network error)
{
  "success": false,
  "code": 500,
  "error": {
    "message": "WEB_RENDER_TOKEN environment variable is not set.",
    "context": "global"
  }
}

Example request

curl -s -X POST https://example.com/api/v1/ssg/page \
  -H "Content-Type: application/json" \
  -H "X-WP-Nonce: abc123def456" \
  -d '{ "id": 42 }'

Side effects

After a successful render, StaticSiteGeneratorService::storeHTML() performs three operations:
ActionDetail
Writes HTML fileweb/app/cache/client/static/{slug}.html — directory is created (0755) if it does not exist
_ssg_cached_pathPost meta updated to the relative path /cache/client/static/{slug}.html
_ssg_last_cachedPost meta updated to the current MySQL datetime via current_time('mysql')
_ssg_content_hashPost meta updated to md5(post_content . post_modified) for staleness detection
The slug is derived from post_name, falling back to post-{ID} when post_name is empty. The filename is run through sanitize_file_name() before writing.

POST /api/v1/ssg/all

Generates and caches static HTML for all cacheable posts and pages in a single request. The controller calls StaticSiteGeneratorService::getCacheableItems() to fetch the full list of cacheable posts, then calls generateHTML() on each item sequentially. Base URL (with rewrites): POST /api/v1/ssg/all
Base URL (without rewrites): POST /wp-json/api/v1/ssg/all

Request body

No request body is required. The endpoint determines the cacheable set automatically.

Responses

success
boolean
true when the operation completes.
code
integer
200 on success.
data
object
Example success response
{
  "success": true,
  "code": 200,
  "data": {
    "pages": [
      {
        "id": 1,
        "type": "page",
        "type_label": "page",
        "title": "Accueil",
        "cached_path": "/cache/client/static/accueil.html",
        "last_cached": "2025-01-15 10:32:00",
        "up_to_date": true
      },
      {
        "id": 7,
        "type": "single",
        "type_label": "projet",
        "title": "Refonte e-commerce",
        "cached_path": null,
        "last_cached": "Never",
        "up_to_date": false
      }
    ]
  }
}

Example request

curl -s -X POST https://example.com/api/v1/ssg/all \
  -H "X-WP-Nonce: abc123def456"
This is a long-running, synchronous operation. Rendering each page involves an outbound HTTP request to the headless browser API and can take several seconds per page. For sites with many cacheable posts the request may time out at the web-server or PHP level. The admin dashboard avoids this by calling POST /api/v1/ssg/page in a JavaScript loop with per-item progress tracking instead of using this bulk endpoint directly.

DELETE /api/v1/ssg/delete

Deletes the cached static HTML file for a single WordPress post and clears all related post meta. Use this endpoint to invalidate a stale cache entry after content is updated, or to force a clean regeneration. Base URL (with rewrites): DELETE /api/v1/ssg/delete
Base URL (without rewrites): DELETE /wp-json/api/v1/ssg/delete

Request body

id
integer
required
The WordPress post ID whose cache should be deleted. Must pass FILTER_VALIDATE_INT.

Responses

success
boolean
true when the deletion operation completes (even if no file existed on disk).
code
integer
200 on success.
data
object
Example success response
{
  "success": true,
  "code": 200,
  "data": {
    "deleted": true
  }
}
Error responses (same shapes as /page)
// Invalid or non-integer id
{
  "success": false,
  "code": 500,
  "error": {
    "message": "Invalid query parameter",
    "context": "global"
  }
}
// Post not found
{
  "success": false,
  "code": 500,
  "error": {
    "message": "Invalid post id",
    "context": "global"
  }
}

Example request

curl -s -X DELETE https://example.com/api/v1/ssg/delete \
  -H "Content-Type: application/json" \
  -H "X-WP-Nonce: abc123def456" \
  -d '{ "id": 42 }'

Side effects

StaticSiteGeneratorService::deleteCachedFile() performs the following steps:
  1. Reads _ssg_cached_path post meta. Returns false immediately if no path is stored.
  2. Resolves the absolute path via app_root($relativePath).
  3. Calls unlink() if the file exists and is a regular file.
  4. Removes the three post-meta keys: _ssg_cached_path, _ssg_last_cached, _ssg_content_hash.
If the .html file has already been deleted from disk (e.g. manually or by a deployment script) the endpoint still returns deleted: true as long as the post meta entry existed — the meta cleanup happens regardless of whether the physical file was found.

Route registration summary

The three SSG routes are registered in src/_configuration/routes/api.php as a grouped route set:
Route::group('/ssg', [
    Route::post('/page',     [SSGController::class, 'page']),
    Route::post('/all',      [SSGController::class, 'all']),
    Route::delete('/delete', [SSGController::class, 'delete']),
]);
This produces the canonical paths /api/v1/ssg/page, /api/v1/ssg/all, and /api/v1/ssg/delete when URL rewrites are active, or /wp-json/api/v1/ssg/* as the fallback.

Build docs developers (and LLMs) love