Skip to content

Keys and vary

Cache keys decide whether two requests can reuse the same response. Layeron builds keys deterministically so route handlers and product Workers can agree on the same Cloudflare Cache entry.

apiCache.key(request) returns a URL-like key:

Terminal window
const key = apiCache.key(
new Request("https://api.example.com/products?limit=20", {
headers: {
"accept-language": "en-US",
},
}),
)

Layeron normalizes the key by:

  • Removing URL fragments.
  • Sorting query parameters.
  • Adding the instance namespace.
  • Adding the uppercase HTTP method.
  • Adding configured vary header values.

Cache keys include the platform namespace:

Terminal window
const marketingCache = cache({
name: "pages",
namespace: "marketing",
})
const appCache = cache({
name: "pages",
namespace: "app",
})

Both instances use the same name while producing separate keys and stats. See Namespaces for platform namespace behavior.

The HTTP method is part of the key. A GET /items response and a HEAD /items response are separate entries.

For API response caching, use GET and HEAD routes. Write routes usually perform purge after changing data.

Query parameters are sorted before the key is created:

Terminal window
/api/products?sort=price&limit=20
/api/products?limit=20&sort=price

These requests map to the same key after normalization.

High-cardinality query parameters produce many entries. Use small TTLs for search, autocomplete, and highly personalized filters.

vary adds selected request header values to the key:

Terminal window
const apiCache = cache({
name: "localized-api",
vary: ["accept-language"],
})

Use vary for dimensions that change the actual response body:

HeaderUse Case
accept-languageLocalized content.
acceptJSON, HTML, or alternate media formats.
x-tenant-idTenant-specific public data when your app controls the header.

Keep vary lists short. A header with many unique values creates many cache entries.

You can override vary for one operation:

Terminal window
await apiCache.put(request, response.clone(), {
vary: ["accept-language", "accept"],
tags: ["products"],
})
const cached = await apiCache.match(request, {
vary: ["accept-language", "accept"],
})

The same vary list must be used for match, put, and delete when it differs from the cache instance default.

Tags are stored with entries for invalidation. They stay outside the cache key.

Terminal window
await apiCache.put(request, response.clone(), {
tags: ["products", "product:123"],
})

The same request can later be matched with only the request:

Terminal window
const cached = await apiCache.match(request)

This keeps reads simple while preserving tag-based purge behavior.