Calling external APIs on the server and managing the cache
Fetch data from outside the site in getServerSideProps, declare it with ctx.cacheDepends, and invalidate the page cache through the publish endpoint — without a declaration the page stays frozen on its first render.
A page can fetch data from outside the site on the server: third-party APIs, your own services, public platform endpoints. The rendered result goes into the HTML cache, and that cache is only invalidated when a publish event arrives. Data that belongs to the site (CMS, forms, tables) registers its dependencies automatically; external data does not. Declare the dependency yourself, or the page keeps serving whatever it rendered the first time.
Where the call belongs
getServerSideProps
Public read-only data that must be in the first paint and visible to search engines and AI crawlers. It is rendered into the HTML.
Func, when secrets are involved
The ctx in getServerSideProps carries no secrets and cannot write to the database. Calls that need an API key, a signature, or persistence belong in /backend/func; the page calls the function key.
The client, for interaction-only data
Data loaded after a click belongs in a browser-side fetch. It never enters the HTML cache, so it needs no dependency declaration.
Site data needs nothing
Data read through platform packages such as talizen/cms registers its dependencies automatically. Editing the content invalidates the cache without cacheDepends.
Calling an external API on the server
fetch is available globally during server-side rendering and behaves as it does in the browser. The returned props are serialized into the HTML and handed to the page component.
// /page/templates.tsx
export async function getServerSideProps(ctx) {
const res = await fetch('https://api.example.com/v1/items?limit=12')
// A failing upstream should not turn the whole page into a 500.
if (!res.ok) return { props: { items: [] } }
const data = await res.json()
return { props: { items: data.list ?? [] } }
}
export default function Templates({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
Throwing fails the render. Always check the response status and prepare a degraded state for external dependencies.
Declaring the cache dependency
Call ctx.cacheDepends(key) inside getServerSideProps to tell the renderer that this page depends on an external data source named key. The key is a string you choose; it only has to match the one used when publishing.
export async function getServerSideProps(ctx) {
ctx.cacheDepends('templates')
const res = await fetch('https://api.example.com/v1/items?limit=12')
if (!res.ok) return { props: { items: [] } }
return { props: { items: (await res.json()).list ?? [] } }
}
- Declare several at once with
ctx.cacheDepends('templates', 'template-tags'), or call it repeatedly; duplicate keys are collapsed. - What is stored is
custom/{site_id}/templates. The site namespace is added by the renderer from the site being rendered, so a page cannot register a key belonging to another site. - Any point during
getServerSidePropsworks — before or after the fetch. - Skip it and the page is only invalidated by existing events such as publishing the site or editing the page; changes to the external data never trigger a re-render.
Invalidating the cache
After the external data changes, call the publish endpoint to invalidate every page that declared the key. The next visitor triggers a fresh render.
POST /api/p/project/{project_id}/site/{site_id}/render_cache/publish
Authorization: Bearer <token>
Content-Type: application/json
{ "keys": ["templates"] }
The response lists the dependency keys that were actually invalidated:
{ "published": ["custom/{site_id}/templates"] }
Permission
The same permission as publishing the site. The site namespace comes from the site_id in the URL, not from the request body, so the endpoint cannot reach another site's cache.
keys cannot be empty
An empty array, or one containing only blank strings, returns 400. Such a call clears nothing, and a 200 would only make you believe it worked.
At most 20 per call
More than that returns 400. No normal workflow needs a larger batch.
Publishing an unknown key is safe
It is not an error; nothing simply gets cleared.
Invalidation deletes rather than recomputes: the entry is dropped and the next visitor pays for the re-render. If you need it warm, request the page yourself right after publishing.
Full example
A page that lists items from an external endpoint and supports filtering. Note that the key is independent of the filter: every page and filter combination of the same data source shares one key, and a single publish invalidates all of them.
// /page/templates.tsx
const API = 'https://api.example.com/v1/templates'
export async function getServerSideProps(ctx) {
ctx.cacheDepends('templates')
const category = ctx.query.category ?? ''
const url = category ? `${API}?category=${encodeURIComponent(category)}` : API
const res = await fetch(url)
if (!res.ok) return { props: { items: [], category } }
const data = await res.json()
return { props: { items: data.list ?? [], category } }
}
And after the external data changes:
await fetch(
`https://creght.cn/api/p/project/${projectId}/site/${siteId}/render_cache/publish`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ keys: ['templates'] }),
},
)
Cache boundaries
- Cache entries are keyed by site, host, path and query string.
?category=aand?category=bare two entries, but they declare the same key and one publish invalidates both. - Reading a cookie in
getServerSidePropsmakes the page cache per that cookie name; writing a cookie keeps the page out of the cache entirely, which makes the dependency declaration pointless. - Preview mode bypasses the cache, so it always shows fresh data. Verify caching behaviour on the real domain.
propsare serialized into the HTML and visible to the browser. Include only what the page renders; never pass an upstream response through verbatim.
Checklist
- External calls live in
getServerSideProps; anything needing a secret or a write goes through a Func. - Response status is checked and a degraded state exists, so the page still renders when the upstream is down.
- Every external data source has a matching
ctx.cacheDepends(key). - Whoever changes the data calls the publish endpoint with exactly the same key.
propscontain no secrets, internal IDs, or unused upstream fields.
