Caching and reliability
What is cached where, how fresh a response can be, and what your site sees if the API is slow or down.
A site that fetches at request time depends on the API at request time
That is the honest trade of a hosted CMS, and the rest of this page is about making it a small one. A site that fetches on the server and keeps the last good response, or builds statically and rebuilds on publish, keeps serving through an outage of ours. A site that fetches in the browser on every visit does not, and should be a small one.
What the API caches on its side
Published content is cached in memory for 5 minutes, per project, per key type and per language. The cache is invalidated the moment content is published, unpublished, enabled, disabled or deleted, so a publish is visible on the next request. Five minutes is the ceiling on staleness when nothing has changed, not a delay you wait out after a change. The X-Duggie-App-Cacheheader says which you got:HITfor a cached response,MISSfor one built from the database. It is exposed to browser scripts, so you can read it in the network tab or in code.Secret key and preview responses are never cached. They return drafts, and a draft has to be current. They are sent with Cache-Control: no-store, so nothing between the API and your server keeps a copy either.Images and documents are served from a CDN at cdn.duggiecms.com, cached at the edge near your visitors. Every upload gets a new URL, so a replaced image is a new file and the old URL stays valid for anything still pointing at it.A cached response still counts as a request. The cache protects response times, not your allowance. A free project has 50,000 requests a month, and a site fetching in the browser spends one per visit, so the caching that protects your allowance is the caching on your side, below.
How fresh a response is
| You fetch | A publish shows up | Requests to the API |
|---|---|---|
| In the browser, per visit | On the visitor's next page load | One per visit |
| On the server, cached for N seconds | Within N seconds | One per N seconds, however many visitors |
| At build time | On the next build; a webhook can trigger it on publish | One per build |
Publishing invalidates the API's own cache immediately in every row. The waiting is only ever on your side, and it is yours to set.
Caching on your side
Server-rendered
Fetch once per render and keep the result for a minute or so. Next.js does this with revalidate, and the Getting Started tab shows it. A hundred visitors in that minute share one request.
Static build
Read the API once per deploy. Visitors never touch it, an outage cannot reach them, and a paid project's webhook rebuilds on publish.
In the browser
Fetch once per page load and share the feed across components, never once per component. Keeping the last response in sessionStorage spares repeat visits within a session.
Keep the last good response
On the server, treat the API like any dependency: a timeout of a few seconds, and on failure serve what you last fetched rather than an error. Content that is a few minutes old is a better page than a blank one.
When a request is refused
The API answers 429 with a Retry-After header and a message naming the limit. Free projects have a per-minute limit and a monthly allowance; paid slots have a higher per-minute limit and no monthly ceiling. The figures are on Limits. A refused request is not counted against the allowance, so retrying after the header's delay costs nothing extra.
A site with server-side caching should never see a 429 from visitor traffic, because visitors share one request. A browser-fetching site on a free project can, on a busy day, and the fix is the caching above rather than a retry loop.
When the API is down
Images keep loading. The CDN is separate from the API and keeps serving what it has cached. A static site is unaffected. It was built from a response it already has. Only the next rebuild waits. A server-rendered site serves its last good response if you kept one, which is the whole reason to keep one. A browser-fetching site shows whatever it renders on a failed fetch. Give it something: the markup you shipped, a stored copy, or a quiet fallback. An unhandled rejection is a blank section.
Current status, past incidents and planned maintenance are on status.duggiecms.com, hosted separately from the API so it stays reachable during an outage of ours. It checks the website, the content endpoint and the CDN from four regions every three minutes, and you can subscribe to updates there.