A cached theme palette fetcher that still keeps contrast honest

What’s up everyone? I’m wiring up a theming panel and I’m trying to fetch a brand palette from an API without hammering it, but I also don’t want stale colors to stick around after a deploy (ETag/304 weirdness + cache invalidation races).

const mem = new Map();

export async function getThemePalette(url, { ttlMs = 5 * 60_000 } = {}) {
  const now = Date.now();
  const hit = mem.get(url);
  if (hit && hit.expiresAt > now) return hit.value;

  const res = await fetch(url, {
    cache: "no-cache", // allow revalidation, avoid stuck CDN/browser cache
    headers: hit?.etag ? { "If-None-Match": hit.etag } : {}
  });

  if (res.status === 304 && hit) return hit.value;

  const etag = res.headers.get("etag") || undefined;
  const json = await res.json();

  const value = normalizePalette(json); // expects { primaryHsl: [h,s,l], ... }
  mem.set(url, { value, etag, expiresAt: now + ttlMs });
  return value;
}

export function themeCssVars(p) {
  const [h, s, l] = p.primaryHsl;
  const bg = `hsl(${h} ${s}% ${Math.max(6, l - 42)}%)`;
  const fg = `hsl(${h} ${Math.min(22, s)}% ${Math.min(96, l + 52)}%)`;
  return {
    "--primary": `hsl(${h} ${s}% ${l}%)`,
    "--bg": bg,
    "--fg": fg,
    "--grad": `linear-gradient(135deg, ${bg}, hsl(${h} ${s}% ${l}%))`
  };
}

The neat part is it gives me a stable HSL-based palette and a quick gradient/contrast-ish set of CSS vars while still playing nice with revalidation, so the UI doesn’t flash between old/new themes when the network is flaky.

1 Like

Your TTL + ETag combo is fine, but the part that’ll bite you is concurrency: two callers can miss the mem hit and you end up with two fetches racing, and the slower one can overwrite the newer palette in mem if deploy timing is unlucky. I usually stash an in-flight promise per URL (or a monotonically increasing request id) so only the latest response is allowed to commit to the cache. That gets rid of the “flash back” behavior without leaning on CDN cache semantics.

1 Like