Skip to content

Deploy and purge

The repository builds two Workers -- the site worker (src/index.ts) and the media worker (src/media/index.ts) -- across five Wrangler environments. This page covers the environment layout, the deploy and dry-run scripts, the secrets each env needs, and the cache-purge endpoint including the hostname-scoped purge.

Source: wrangler.toml, package.json, src/admin/purge.ts, src/cache/index.ts.

Environment layout

wrangler.toml defines five [env.*] blocks. Three run the site worker, two run the media worker.

Env Worker name Worker Route Notes
site-stage cms-cf-worker-stage site stg.wantskicks.com/* (zone) Stage site worker, KV-backed.
site-stage-mk2 cms-cf-worker-stage-mk2 site stg2.wantskicks.com (custom_domain) P1 generic-engine canary on stg2; runs the CSS override.
site-prod cms-cf-worker-prod site wantskicks.com/* (zone) Production site worker.
media-stage cms-cf-media-stage media stg.wantskicks.com/cmp-media/*, stg2.wantskicks.com/cmp-media/* (zone) Serves stage and stg2 media from the stage S3 bucket.
media-prod cms-cf-media-prod media wantskicks.com/cmp-media/* (zone) Production media worker.

A few details worth calling out:

  • site-stage-mk2 is the stg2 canary. It deploys the data-driven engine in parallel with the legacy stage worker, on a custom_domain rather than a zone route. custom_domain = true lets Wrangler provision the stg2 DNS record and TLS cert without a manual edit on the prod zone. It also carries the CSS override env vars (see CSS override).
  • The media worker route is more specific than the stg2 custom domain. A worker route at stg2.wantskicks.com/cmp-media/* beats the custom domain for that subpath, so media-stage serves stg2's media from the same stage bucket as stg.
  • Only the site worker uses KV. Each site env binds a KV namespace (with per-env namespace ids in wrangler.toml). The media worker is stateless -- edge Cache API only, no KV.

Site worker env vars

Each site env sets non-secret vars in its [env.*.vars] table: CMS_API_BASE_URL (the CMS public v2 API base -- cms-api-stg.skyneto.com for stage, cms-api-prd.skyneto.com for prod), CMS_SITE_ID (WK), and PAGE_TYPE_TTLS (per-page-type cache TTLs). site-stage-mk2 additionally sets CSS_OVERRIDE_SLUG and CSS_OVERRIDE_VERSION.

Media worker env vars

Each media env sets S3_BUCKET (cms-media-stg-891708857119 for stage, cms-media-891708857119 for prod), S3_REGION (us-east-1), and MEDIA_CACHE_TTL (604800, 7 days).

Deploy and dry-run scripts

package.json wraps wrangler deploy --env <env> in npm scripts:

"deploy:site-stage": "wrangler deploy --env site-stage",
"deploy:site-prod": "wrangler deploy --env site-prod",
"deploy:media-stage": "wrangler deploy --env media-stage",
"deploy:media-prod": "wrangler deploy --env media-prod",
"dry-run:site-stage": "wrangler deploy --dry-run --env site-stage",
"dry-run:site-prod": "wrangler deploy --dry-run --env site-prod",
"dry-run:media-stage": "wrangler deploy --dry-run --env media-stage",
"dry-run:media-prod": "wrangler deploy --dry-run --env media-prod"

Each dry-run:* script builds and validates the bundle for that env without publishing -- use it as a pre-flight check before the matching deploy:*.

Deploying the stg2 canary

There is no deploy:site-stage-mk2 npm script. The stg2 canary is deployed directly: wrangler deploy --env site-stage-mk2. This is the command the CSS-override switch references (see CSS override).

The lint and test scripts (npm run lint, npm test) do not read docs/ and are unaffected by the documentation package.

Required secrets

Secrets are never placed in the vars table or committed to source. They are set per env with wrangler secret put <NAME> --env <env>:

Secret Used by Purpose
CMS_API_KEY site Bearer token for the CMS public v2 API (cmsFetch).
ADMIN_PURGE_SECRET site Bearer token for POST /__cms/purge and the ?_preview= bypass.
AWS_ACCESS_KEY_ID media S3 credentials for media reads.
AWS_SECRET_ACCESS_KEY media S3 credentials for media reads.

The KV namespace ids in wrangler.toml are environment-specific; create them with wrangler kv:namespace create on first deploy and fill in the ids per env.

Cache purge: POST /__cms/purge

Purge clears cached pages and settings so a change becomes visible without waiting out a TTL. The endpoint takes absolute priority in handleRequest (step 1) and bypasses every cache layer.

handlePurge (src/admin/purge.ts) requires POST and an Authorization: Bearer <ADMIN_PURGE_SECRET> header, and host is always required in the body. It accepts three body shapes.

Single URL

curl -X POST https://stg.wantskicks.com/__cms/purge \
  -H "Authorization: Bearer $ADMIN_PURGE_SECRET" \
  -H "content-type: application/json" \
  -d '{"host": "stg.wantskicks.com", "url": "/some-article"}'

List of URLs

curl -X POST https://stg.wantskicks.com/__cms/purge \
  -H "Authorization: Bearer $ADMIN_PURGE_SECRET" \
  -H "content-type: application/json" \
  -d '{"host": "stg.wantskicks.com", "urls": ["/a", "/b", "/c"]}'

A single url and a urls list are both routed to purge (src/cache/index.ts), which deletes each (host, pathname) entry from both the Cache API and KV. URLs are normalized to their pathname (query stripped) to match the page cache key.

Hostname-scoped (site-wide)

curl -X POST https://stg.wantskicks.com/__cms/purge \
  -H "Authorization: Bearer $ADMIN_PURGE_SECRET" \
  -H "content-type: application/json" \
  -d '{"host": "stg.wantskicks.com", "site": true}'

site: true routes to purgeSite (src/cache/index.ts), which clears the whole host at once:

export async function purgeSite(host: string, env: SiteEnv): Promise<string[]> {
  const prefix = `${host}:`;
  const purged: string[] = [];
  let cursor: string | undefined;
  do {
    const listing = await env.KV.list({ prefix, cursor });
    for (const k of listing.keys) {
      await env.KV.delete(k.name);
      purged.push(k.name);
    }
    cursor = listing.list_complete ? undefined : listing.cursor;
  } while (cursor);

  await env.KV.delete(`site:${host}`);
  purged.push(`site:${host}`);
  // ... also deletes the site-settings Cache API entry
}

It enumerates every KV page entry under the <host>: prefix and deletes it, then deletes the KV site-settings entry (site:<host>) and the Cache API site-settings entry. KV is authoritative here; per-page Cache API entries are best-effort because Cloudflare does not expose a list or tag API to a Worker for deleting them in bulk.

Why hostname-scoped purge is needed to switch CSS site-wide

A rendered page has its stylesheet <link> baked into the cached HTML. When the design changes (for example bumping css_version, or switching the CSS override on stg2), the future renders link the new stylesheet, but every already-cached page keeps the old link until it expires. Purging a single URL fixes only that page. The hostname-scoped site: true purge clears every page for the host in one call, so the whole site re-renders with the new link on the next request. This is the purge that pairs with a redeploy when switching the CSS override.

Health: POST-free /__cms/health

/__cms/health (handled in step 1, src/admin/health.ts) is the lightweight liveness endpoint, also cache-bypassing. Use it for uptime checks; use the purge endpoint for cache invalidation.