Cloudflare Workers¶
The Website runs as a single Cloudflare Worker (swapps-client). The Worker is
the application server: it serves static assets, applies redirects and security
headers, edge-caches blog pages, and renders pages with Vike SSR.
The entry point is src/worker.ts, bundled to dist/worker.js by esbuild
(scripts/build-worker.ts) and declared as main in wrangler.toml.
Wrangler bindings¶
name = "swapps-client"
main = "dist/worker.js"
[assets]
directory = "dist/client"
binding = "ASSETS"
[[services]]
binding = "API_WORKER"
service = "swapps-worker"
| Binding | Type | Purpose |
|---|---|---|
ASSETS |
Static Assets | Serves files from dist/client |
API_WORKER |
Service Binding | Worker-to-Worker calls to the Platform (swapps-worker) |
Edge routing¶
On the swapps.com zone, the React site coexists with WordPress on the same
hostname. Cloudflare resolves this with Workers Routes: the swapps-client
Worker captures the site via a wildcard route plus explicit per-section routes;
WordPress paths run with "Workers disabled" and fall through to the origin
(Pantheon).
Routes served by swapps-client¶
| Pattern | Worker |
|---|---|
swapps.com |
swapps-client |
swapps.com/* |
swapps-client |
swapps.com/assets/* |
swapps-client |
swapps.com/api* |
swapps-client |
swapps.com/about-us* |
swapps-client |
swapps.com/services* |
swapps-client |
swapps.com/blog* |
swapps-client |
swapps.com/case-studies* |
swapps-client |
swapps.com/case-studies/sector* |
swapps-client |
swapps.com/case-studies/tech* |
swapps-client |
swapps.com/case-studies/service* |
swapps-client |
swapps.com/get-swapps* |
swapps-client |
swapps.com/support-center* |
swapps-client |
swapps.com/style-guide* |
swapps-client |
swapps.com/es* |
swapps-client |
The wildcard swapps.com/* already captures the site; the explicit routes pin
those sections to the Worker even when more specific "Workers disabled" routes
exist on the same domain.
Routes NOT served by the Worker (origin / other Workers)¶
| Pattern | Handled by |
|---|---|
swapps.com/api/ai/* |
swapps-ai Worker |
swapps.com/wp-content/*, /wp-admin/*, /wp-includes/*, /wp-login.php* |
WordPress (origin) |
swapps.com/wp-json/* |
WordPress REST API (origin) |
swapps.com/wp-cron.php, /health |
WordPress (origin) |
swapps.com/sitemap.xml, /sitemap_index.xml, /post-sitemap.xml, /author-sitemap.xml |
Yoast / origin |
Note
Mental model: on swapps.com, WordPress paths (/wp-*, plus /health) run
"Workers disabled" and fall to origin; everything else is the Worker. When
adding a new site section, the wildcard usually covers it. When exposing
something new from WordPress, add its route with "Workers disabled".
The full zone configuration (48 routes, WAF, DNS) lives in the platform
CLOUDFLARE.md.
Request lifecycle¶
src/worker.ts's fetch handler runs these stages in order:
- Blog cache-purge webhook —
POST /api/blog-cache/purge(auth viaX-Cache-Api-KeymatchingCACHE_API_SECRET); handled first so the body/method survive. - WordPress permalink redirect — legacy
?p=/ Yoast AMP query shapes →/. - Legacy redirects — old WordPress URLs via
checkLegacyRedirect(). - Trailing-slash redirect — append a trailing slash (301) outside assets/API/files.
- Canonical redirect — redirect non-canonical hosts to
BASE_URL(skipped in dev /workers.dev). - Sentry tunnel —
/api/eventsproxies Sentry envelopes to bypass ad blockers. - SEO files —
/robots.txt,/sitemap.xml,/page-sitemap.xml,/page-sitemap.xsl. - Static assets —
isStaticAsset()paths served via theASSETSbinding with tuned cache headers. - Blog edge cache — anonymous blog-post URLs are served from
caches.defaultif cached. - SSR —
renderPage()(Vike) renders the page; 404s re-check legacy redirects; successful blog renders are written back to the edge cache.
Security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options,
Referrer-Policy, Permissions-Policy) are added to every response by
addSecurityHeaders().
SSR at the edge¶
renderSSRPage() builds the Vike pageContextInit with urlOriginal, the
original request headers, the runtime env (BASE_URL, API_URL, NODE_ENV),
and the Cloudflare bindings — crucially API_WORKER — so that +data.ts files
can reach the Platform Worker through the Service Binding during SSR. React 19
requires a MessageChannel polyfill in the Workers runtime, which the Worker
defines at module load.
Edge caching for the blog¶
Anonymous blog-post pages are cached in caches.default:
- Cacheable =
GET+ a final blog-post URL (/blog/<slug>/or/es/blog/<slug>/), not a listing/taxonomy root, and nowordpress_logged_incookie. - Cache key drops the query string, so tracking params (
utm_*,fbclid, …) never fragment or bust the cache. - TTL is 7 days (
s-maxage), acting as a safety net; the WordPress publish webhook actively purges a post on edit/delete (locally viacaches.default.delete()and globally via the Cloudflare Cache Purge API).
See Blog system for the end-to-end flow.
Observability¶
Runtime logs are enabled in wrangler.toml ([observability.logs] enabled = true).
Application errors are tracked in Sentry, with events tunneled through the Worker's
/api/events endpoint.