Blog system¶
The blog lives under /blog/ (and /es/blog/) and is rendered by the Website, but
its content comes from WordPress. The Website never calls WordPress directly:
content flows Website → Platform Worker → WordPress REST API.
Routes¶
| Route | Page |
|---|---|
/blog/ |
Listing with pagination and search |
/blog/<slug>/ |
Individual post (blog/@slug/) |
/blog/tag/<slug>/ |
Posts by tag |
/blog/category/<slug>/ |
Posts by category |
/blog/author/<slug>/ |
Posts by author |
Spanish equivalents live under /es/blog/… with translated slugs (e.g.
/es/blog/etiqueta/<slug>/), resolved by the i18n routing layer (see
SSR with Vike).
Data fetching¶
pages/blog/+data.ts runs on the server during SSR and calls the Platform Worker
through the API_WORKER Service Binding via fetchData():
- Posts —
GET /api/posts?per_page=<n>&page=<n>&orderby=date&order=desc&lang=<locale>(plus&search=<q>when searching).per_pageis 4 on mobile user agents and 12 otherwise. - Tags —
GET /api/posts/tags?per_page=15&orderby=count&order=desc&lang=<locale>for the filter chips.
The loader returns the posts, pagination metadata, categories, and SEO metadata
(title/description/keywords/image) to the page. On API failure it returns a graceful
error shape (empty posts + error: true) so the page still renders.
The Worker also builds a category sitemap by fetching
GET /api/posts/categories?...&lang=<en|es> for both locales
(handleSitemapXml in src/worker.ts).
SSR rendering and SEO¶
Blog pages are server-rendered through Vike. For posts, +onRenderHtml.tsx
prefers the Yoast yoast_head_json carried in the post data, emitting the
full Yoast meta set (title, description, canonical, Open Graph, Twitter, and
JSON-LD schema). A Spanish-canonical fix injects the /es/ prefix into Yoast
canonicals when needed, and an LCP <link rel="preload"> is emitted for the
post's featured image.
Multilingual¶
Posts are bilingual via WordPress (WPML). The WordPress theme exposes a custom REST
field that maps each post's translations, which the Platform Worker uses to serve
the correct language and to build hreflang alternates. The Website passes
per-post alternateUrls into +onRenderHtml.tsx, which renders the hreflang link
tags. The lang query param on /api/posts* selects the locale.
Edge caching and purge¶
Anonymous blog-post pages are cached at the edge in caches.default:
- Cacheable:
GET, a final post URL (/blog/<slug>/or/es/blog/<slug>/, not a listing/taxonomy root), and nowordpress_logged_incookie (so editors always see fresh renders). - Cache key drops the query string, so
utm_*,fbclid,gclid, etc. all resolve to a single shared entry. - TTL: 7 days (
s-maxage) as a freshness safety net.
When a post is published, edited, or deleted, WordPress calls the Worker's
purge webhook (POST /api/blog-cache/purge, authenticated with
X-Cache-Api-Key == CACHE_API_SECRET). The Worker purges in two layers:
caches.default.delete()— instant, but only in the colo that handled the request.- Cloudflare Cache Purge API (single-file by URL, using
CF_ZONE_ID/CF_PURGE_TOKEN) — global eviction across every colo.
This is targeted single-file purge (never a full flush); URLs are normalized to the cache key (origin + path, query dropped) so the purge hits the exact stored entry.
Note
The WAF allows the Platform Worker's sub-requests to /wp-json/wp/v2/ while
keeping that path closed to external traffic — see the platform CLOUDFLARE.md
for the server-to-server WAF exceptions.