Skip to content

Caching

Turbulence caches the HTML it serves so that the second-and-later visitors to a page get a fast response without re-running the whole transformation pipeline. Understanding how the cache works will help you predict when a change to your site will be visible to visitors, and how to force a refresh if you need to.

Each unique URL is cached independently. When you add a domain through the dashboard, the default cache TTL is 1 hour (3600 seconds) — configurable per domain under Settings. After that window, the cache is considered stale. Stale entries are kept for up to 7 days (the max-stale age) while Turbulence revalidates in the background. When an entry expires entirely, the next visitor triggers a fresh fetch from your origin.

If no per-domain TTL has been synced yet, the worker falls back to 15 minutes (900 seconds).

If you need to invalidate a page right now — for example, you just published a critical fix — Turbulence supports a few ways to do that, listed below.

Turbulence caches the transformed HTML response for each unique URL. The full URL (including the path and the query string) is the cache key. A request to /about and a request to /about?utm_source=twitter are treated as two distinct pages and cached separately.

The cache lives in Cloudflare R2, the same storage your self-hosted fonts and static assets are stored in. The transformed HTML is served from the edge CDN after that, so the response is fast for visitors regardless of where they are.

  • Non-HTML responses (CSS, JS, images, JSON, anything with a non-HTML Content-Type) are not cached by Turbulence. They pass straight through. The HTML is cached; the assets referenced from the HTML are served as-is by your origin (or by PicPerf, in the case of optimized images).
  • Cache-bypass parameters. If a request includes ?pp_verify=1, Turbulence skips the cache for that one request. The ?bust=1 parameter also bypasses the cache, but only in the worker’s development environment. More on this in Verifying It Works.
  • POST requests and other non-GET methods are not cached.

Each cached entry has two lifetimes associated with it:

  • Time to live (TTL). How long the cached version is considered “fresh” — the period during which Turbulence returns it without checking with your origin. Dashboard default: 3600 seconds (1 hour). Worker fallback: 900 seconds (15 minutes).
  • Max stale age. The longest Turbulence will keep serving a stale entry while it revalidates in the background. After this, the next request triggers a full re-fetch. Default: 604800 seconds (7 days). This is not currently configurable from the dashboard.

While the cache is fresh, every request gets the cached version instantly.

Once the TTL is up but you’re still within the max-stale window, the behavior is:

  1. The visitor gets the cached version immediately. Their response is just as fast as a fresh-cache hit.
  2. In the background, Turbulence revalidates the entry by fetching from your origin and running the transformation pipeline again.
  3. The next visitor (and every one after that, until the entry re-stales) gets the freshly-revalidated version.

This is called stale-while-revalidate, and it’s the default behavior. It’s the right tradeoff for almost every site: visitors get fast responses, your origin doesn’t have to handle every request, and updates to your pages are picked up once the configured TTL elapses (default: 1 hour) and background revalidation runs.

After the max-stale age is up, the entry is considered expired. The next request after that goes to your origin, and the freshly-transformed HTML is written to the cache to replace it.

Per-domain cache TTL lives in PicPerf’s KV store and can be configured from the dashboard under each domain’s Settings tab. Valid range: 60 seconds to 86400 seconds (24 hours). Default: 3600 (1 hour).

The max-stale age (7 days) is set at the worker level and is not exposed in the dashboard. If you’re not sure what TTL to use, the default is fine for most sites. Lower it if you publish frequently and need faster cache turnover; raise it if your content is mostly static.

Fresh HTML (X-PicPerf-Cache: HIT or MISS) is served with:

Cache-Control: public, max-age=3600, stale-while-revalidate=86400

That lets browsers and CDNs reuse the response for an hour, and keep a stale copy for up to a day while they revalidate. Stale HTML from Turbulence (X-PicPerf-Cache: STALE) is sent with max-age=0, must-revalidate so it is not frozen at the edge for that full day.

This browser/CDN policy is separate from Turbulence’s own HTML cache. Visitors can still see an old page from their cache after you purge — use a hard refresh or a private window to confirm.

When Turbulence serves a request, the response includes a few headers that tell you what happened. They’re useful for verifying things are working and for debugging:

  • X-PicPerf-Cache — one of HIT (served from the edge cache, fresh), STALE (served from the edge cache, but revalidating in the background), or MISS (no cache entry, fetched from origin and transformed just now).
  • X-PicPerf-Cache-Age — the age of the cached entry in seconds, when applicable.
  • X-PicPerf-Pipeline-Stagefull if all transformations completed inline, or partial if some (the slow ones: font self-hosting, static asset upload) are still finishing in the background. The response is still complete and functional in either case.
  • X-PicPerf-Optimizedtrue if at least one transformation step actually changed the HTML. false means the pipeline ran but produced identical output (rare, but possible on already-optimized pages).
  • X-PicPerf-Steps — a comma-separated list of the steps that modified the HTML, like images,image-sizing,resource-hints,scripts. Useful for confirming which optimizations are firing.

There are a few ways to get Turbulence to fetch a page from your origin instead of serving a cached copy:

  • Purge the cache. Use Bust all caches on the domain page, or the cache purge webhook on Settings after a publish or deploy is already live at origin. That is the right tool when visitors need the new HTML immediately.
  • Wait for the cache to go stale. Once your configured TTL elapses (default: 1 hour), Turbulence starts background revalidation. Visitors still get the cached version immediately while the refresh happens behind the scenes.
  • Lower the cache TTL in Settings. If you need faster turnover going forward, reduce the TTL on your domain’s Settings page. This affects new cache cycles, not entries already stored.
  • Append ?pp_verify=1 to a URL. This bypasses the cache for that single request and runs the full pipeline synchronously. It’s primarily meant for development and debugging — see Verifying It Works for details. Note: this does not write the fresh result to the cache, so it’s a way to check the pipeline without affecting what other visitors see.

If Turbulence tries to revalidate and your origin is unreachable, the cached version keeps being served (within the max-stale window). Visitors see your site as if nothing happened. After the max-stale age expires, the next request will fail until your origin is back, but the failure is graceful — Turbulence will return a 502 with a clear error, not corrupt HTML.

This is one of the big benefits of the cache: it provides a buffer against brief origin outages.