Skip to content
Skip to main contentWhere does your team stand on AI adoption?
CONTACT SALESSTART BUILDING

Performance and uptime

Builder’s Content API is designed for speed and resilience, but performance depends on how requests are structured.

This guide explains how to maximize cache efficiency, avoid patterns that bypass protection, and set up an optional intermediate caching layer for business-critical workloads.

Overview

This guide shows how to:

  • avoid patterns that bypass protective caching
  • keep cache hit rates high for speed and resiliency
  • optional: adopt an intermediate cache for mission-critical workloads

Do not use cachebust=true in runtime traffic

cachebust=true bypasses protective edge caching. Reserve it for controlled maintenance tasks only, such as webhook-triggered prefetch jobs. Remove it from all production user requests.

Rely on CDN stale caching

Builder’s CDN uses stale-while-revalidate and stale-if-error. That lets caches serve immediately from a recent copy while updating in the background, and it shields users during origin hiccups.

Keep request cardinality low

Builder’s CDN cache keys include the full URL. Fewer unique URLs means more cache hits and faster, safer responses.

Use low-cardinality userAttributes

Good examples: audience=menswear, isNewUser=true, region=us, plan=premium

Avoid per-visitor or highly unique values like visitorId, timestamps, random tokens, long free-text.

GET https://cdn.builder.io/api/v3/content/page ?apiKey=YOUR_PUBLIC_KEY &userAttributes.audience=menswear &userAttributes.isNewUser=true

Tips

  • Keep attribute value sets small and well defined.
  • Prefer enums or booleans over free-text.
  • Keep parameter order consistent to improve de-dupe in intermediate caches.
  • Do not include PII in query params.

If published changes don't appear on your live site, it may be due to caching issues rather than a problem with Builder.

Builder publishes content to its API immediately. Delays in displaying changes are typically due to caching at the hosting provider level or within your application's caching strategy.

Work directly with your hosting provider to adjust caching for your deployed application. Some common guides are included below:

Optional: adopt an intermediate cache for mission-critical workloads

For businesses where even an occasional moment of degraded performance can be business-critical, you may want to consider an intermediate cache.

To have zero reliance on Builder at request time, you can place your own cache between your app and Builder. All production traffic then hits your cache only.

Your cache serves the last known good response and refreshes asynchronously. If Builder is slow or returns errors, users keep getting the cached copy until refresh succeeds.

Core design

  • Cache key: include model name plus only low-cardinality userAttributes
  • TTL: set a reasonable max-age and allow stale reads during refresh
  • Refresh: revalidate asynchronously in the background
  • On error: serve stale from your cache

Example: Varnish

sub vcl_backend_response { set beresp.ttl = 5m; set beresp.grace = 60m; # serve stale up to 60m while revalidating }

Example: Fastly (VCL sketch)

sub vcl_backend_response { set beresp.ttl = 300s; set beresp.stale_if_error = 3600s; set beresp.stale_while_revalidate = 60s; }

Example: Cloudflare Workers

export default { async fetch(req, env, ctx) { const url = new URL(req.url); // Build a low-cardinality cache key const key = new Request(url.toString(), { method: 'GET' }); const cache = caches.default; // 1) Try cache let res = await cache.match(key); if (res) { // 2) Kick off background refresh ctx.waitUntil(refreshAndPut(key)); return res; } // 3) Fetch origin and populate cache res = await fetch(key).catch(() => null); if (res && res.ok) { ctx.waitUntil(cache.put(key, res.clone())); return res; } // Optional: read a last-known-good from KV or R2 return new Response('Service temporarily unavailable', { status: 503 }); } } async function refreshAndPut(key) { const cache = caches.default; const fresh = await fetch(key).catch(() => null); if (fresh && fresh.ok) await cache.put(key, fresh); }

Use the REST API over SDK in a middle layer

To use your own intermediate layer, switch SDK fetches (builder.get(...), fetchOneEntry(...)) to direct REST calls to our Content API. The SDKs are thin wrappers; using HTTP directly makes it easy to route through your cache.

Minimal server example

const url = new URL('https://cdn.builder.io/api/v3/content/page'); url.searchParams.set('apiKey', process.env.BUILDER_PUBLIC_KEY); url.searchParams.set('userAttributes.audience', 'menswear'); url.searchParams.set('userAttributes.isNewUser', 'true'); const res = await fetch(url.toString(), { headers: { 'Accept': 'application/json' }, }); const data = await res.json(); // Store in your cache, render, or transform as needed

Request examples

Good

GET /api/v3/content/page?apiKey=...&userAttributes.audience=menswear&userAttributes.isNewUser=true

Risky

GET ...&userAttributes.visitorId=3d7f9c1a-... # extreme cardinality GET ...&cachebust=true # bypasses protective cache

Checklist

  • Remove cachebust=true from runtime requests
  • Use a small set of stable userAttributes values
  • Add an intermediate cache if you need 0 reliance on our APIs at runtime
Was this article helpful?