From 9d2df1d73fd17a2138c29f8179ca373c20997580 Mon Sep 17 00:00:00 2001 From: corsacca Date: Wed, 20 May 2026 17:14:35 +0100 Subject: [PATCH] Purge Cloudflare cache on deploy --- .env.example | 8 ++++++++ nuxt.config.ts | 4 ++++ server/plugins/cloudflare-purge.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 server/plugins/cloudflare-purge.ts diff --git a/.env.example b/.env.example index 6323273a..8cc9cc70 100644 --- a/.env.example +++ b/.env.example @@ -52,6 +52,14 @@ NUXT_PUBLIC_STATINATOR_COOKIE_DOMAIN=.doxa.life DEEPL_API_KEY=your-deepl-api-key DEEPL_API_URL=https://api.deepl.com +# Cloudflare cache purge on deploy (server/plugins/cloudflare-purge.ts). +# On container boot the edge cache is purged so stale prerendered HTML can't +# reference content-hashed assets the new deploy replaced. Leave both blank to +# disable (e.g. in dev). Token: My Profile → API Tokens → Custom Token with +# Zone → Cache Purge for the doxa.life zone. Zone ID: zone Overview page. +CF_API_TOKEN= +CF_ZONE_ID= + # OAuth 2.1 server # Issuer is derived from NUXT_PUBLIC_SITE_URL; resource is `${siteUrl}/mcp`. # Generate with: openssl rand -hex 32 diff --git a/nuxt.config.ts b/nuxt.config.ts index 244cb566..a9780373 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -216,6 +216,10 @@ export default defineNuxtConfig({ prayBaseUrl: process.env.NUXT_PRAY_BASE_URL || 'https://pray.doxa.life', deeplApiKey: process.env.DEEPL_API_KEY || '', deeplApiUrl: process.env.DEEPL_API_URL || 'https://api.deepl.com', + // Cloudflare cache purge on deploy (server/plugins/cloudflare-purge.ts). + // Token needs only Zone → Cache Purge for the doxa.life zone. + cfApiToken: process.env.CF_API_TOKEN || '', + cfZoneId: process.env.CF_ZONE_ID || '', public: { appName: process.env.APP_TITLE || 'My App', nodeEnv: process.env.NODE_ENV || '', diff --git a/server/plugins/cloudflare-purge.ts b/server/plugins/cloudflare-purge.ts new file mode 100644 index 00000000..82be0483 --- /dev/null +++ b/server/plugins/cloudflare-purge.ts @@ -0,0 +1,27 @@ +// Purge the Cloudflare edge cache when a fresh container boots — which is what +// every deploy triggers on this platform. The prerendered marketing HTML is +// edge-cached (Cache-Control s-maxage), but it references content-hashed +// /_nuxt assets that the new deploy just replaced. If stale HTML survived in +// the edge it would point at files that no longer exist, so it must be evicted. +// +// Fire-and-forget: a purge failure must never block the server from starting. +// +// Caveat: this also runs on plain restarts / scale-ups, which is harmless (it +// just re-warms the cache). If the app ever enters a crash loop, move the purge +// to a POST_DEPLOY job so the API can't be hammered. +export default defineNitroPlugin(() => { + // Don't fire during local dev or the build-time prerender pass — only when a + // real deployed container comes up. + if (import.meta.dev || import.meta.prerender) return + + const { cfApiToken, cfZoneId } = useRuntimeConfig() + if (!cfApiToken || !cfZoneId) return + + $fetch(`https://api.cloudflare.com/client/v4/zones/${cfZoneId}/purge_cache`, { + method: 'POST', + headers: { Authorization: `Bearer ${cfApiToken}` }, + body: { purge_everything: true } + }) + .then(() => console.log('[cf-purge] Cloudflare edge cache purged on boot')) + .catch((err: unknown) => console.error('[cf-purge] purge failed:', err)) +})