Skip to content

CSS override (per-worker)

A site's stylesheet normally follows its active template in the CMS database -- the design every worker on that site shares. The CSS override lets a single worker render a different template's stylesheet without touching that shared template, so one deployment can preview or canary a design in isolation.

Source: src/index.ts, src/assets/serve.ts, src/layout/index.ts, wrangler.toml.

What the override does

Two optional env vars on the worker enable it:

  • CSS_OVERRIDE_SLUG -- the template slug whose CSS to serve.
  • CSS_OVERRIDE_VERSION -- that template's css_version.

When both are set, handleRequest cosmetically rewrites the resolved settings' active_template in memory, after resolveSite returns:

// mk2 canary: per-worker CSS override. Render a chosen template's stylesheet
// without touching the shared site's active template (which carries layout +
// routing for every worker on this site). active_template is read only for the
// CSS <link>, so overriding its slug/css_version here is purely cosmetic and
// isolated to this worker. Idempotent: the same constant values each request.
if (env.CSS_OVERRIDE_SLUG && env.CSS_OVERRIDE_VERSION) {
  settings.site_settings.active_template = {
    ...(settings.site_settings.active_template ?? {}),
    slug: env.CSS_OVERRIDE_SLUG,
    css_version: env.CSS_OVERRIDE_VERSION,
  };
}

Three properties make this safe:

  • Cosmetic. active_template is read only to build the stylesheet <link>. Layout and routing come from the descriptors, not from this field, so overriding it changes only the CSS the page links to.
  • Isolated to this worker. The change is to the in-memory settings of this worker's request only. The shared template row in the CMS database is never written, so other workers on the same site are unaffected.
  • Idempotent. The same two constant values are applied on every request.

buildStyle (src/layout/index.ts) reads active_template.slug and css_version and emits a versioned, immutable stylesheet link:

const active = ss.active_template;
const slug = typeof active?.slug === "string" ? active.slug : "";
const cssVersion = typeof active?.css_version === "string" ? active.css_version : "";
const linkTag =
  slug && cssVersion
    ? `<link rel="stylesheet" href="/_assets/styles/${encodeURIComponent(slug)}/${encodeURIComponent(cssVersion)}.css">`
    : `<link rel="stylesheet" href="/_assets/styles/site.css?v=${STYLESHEET_VERSION}">`;

With the override set, slug and cssVersion are the override values, so the page links to /_assets/styles/<override-slug>/<override-version>.css. When no template is set, it falls back to the bundled single-file stylesheet so the page is never left without CSS.

How the CSS is served: serveTemplateCss

A request for /_assets/styles/<slug>/<version>.css is parsed by parseTemplateCssPath (step 1b in src/index.ts) and served by serveTemplateCss (src/assets/serve.ts). The bytes come from the CMS database, fetched over the public v2 API -- not from R2:

const path = `/public/v2/templates/${encodeURIComponent(safeSlug)}/css?v=${encodeURIComponent(safeVersion)}`;
// Long edge cache on the subrequest; the versioned ?v busts it on change.
const resp = await cmsFetch(path, env, { cacheTtl: 86400 });
if (resp.status !== 200) {
  return new Response("Not found", { status: 404 });
}
const css = await resp.text();
return new Response(css, {
  status: 200,
  headers: {
    "content-type": "text/css; charset=utf-8",
    "cache-control": "public, max-age=31536000, immutable",
    "x-cms-css-source": "db",
  },
});

Notes:

  • The served response carries x-cms-css-source: db, making the DB origin observable.
  • The subrequest to the CMS uses an 86400 s (cacheTtl) edge cache (the L4 cf layer; see Caching). The ?v=<version> is part of the URL, so a new css_version is a new cache key -- the CSS propagates without waiting out the 24 h TTL and without a worker redeploy.
  • On any miss or upstream error, serveTemplateCss returns 404 and step 1b in src/index.ts falls back to the bundled stylesheet -- a missing asset never 500s a page.

Worked example: the stg2 mk2 canary

wrangler.toml runs the override on the site-stage-mk2 worker (the stg2 canary) to render the editorial-magazine design while leaving stg's WK template untouched:

[env.site-stage-mk2.vars]
CMS_API_BASE_URL = "https://cms-api-stg.skyneto.com"
CMS_SITE_ID = "WK"
# Per-worker CSS override: stg2 renders this template's stylesheet WITHOUT
# changing WK's active template in the DB, so stg is never affected.
CSS_OVERRIDE_SLUG = "editorial-magazine"
CSS_OVERRIDE_VERSION = "07a2889d"

The wrangler comment lists the templates available to switch between: celebrity-v1/66bcae1a (original), celebrity-sunset/3f9e9aa8 (recolor), and editorial-magazine/07a2889d (full serif/cream reskin).

Switching the override (and why you must purge by hostname)

To change the stg2 palette:

  1. Edit CSS_OVERRIDE_SLUG / CSS_OVERRIDE_VERSION in the site-stage-mk2 block of wrangler.toml.
  2. Redeploy that env: wrangler deploy --env site-stage-mk2. The override lives in the worker's vars, so this step is mandatory -- the change cannot take effect without it. The redeploy also clears the isolate memMap (see Caching).
  3. Purge stg2's edge cache by hostname so every page picks up the new link, not just the homepage.

Step 3 is the subtle one. Already-rendered pages sit in the edge cache with the old stylesheet <link> baked into their HTML. Bumping the override changes the markup of future renders, but the cached pages keep the old link until they expire. A per-URL purge would only fix the pages you list; a hostname-scoped purge clears the whole site at once. That is exactly what purgeSite does -- see the site: true purge in Deploy and purge.