# Calling external APIs on the server and managing the cache | Creght AI guide

> Once a page fetches an external endpoint on the server, the rendered HTML is cached and never invalidated on its own. This guide covers fetching in getServerSideProps, declaring cache dependencies with ctx.cacheDepends, and invalidating them through the publish endpoint.

[![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)API for AI](/)

[View llms.txt](/llms.txt)

Overview

- [Creght API for AI](/api.md)

Discoverability

- [How to optimize llms.txt](/api/optimize-llms-txt.md)

Site configuration

- [Implement domain-based locale routing](/api/domain-locale-routing.md)

Backend

- [Calling external APIs on the server and managing the cache](/api/ssr-external-api-cache.md)
- [Build site backend workflows with Func](/api/func-backend.md)
- [JSON tables: definition, reads, and queries](/api/func-json-tables.md)
- [Uploads: signed direct upload and Func-generated files](/api/func-assets-upload.md)
- [Timeouts and streaming responses](/api/func-timeout-streaming.md)
- [Integrate Alipay PC Web Payment with Func](/api/func-alipay-payment.md)

Integrations

- [Send Email and Verification Codes with Integrations](/api/func-email-integration.md)
- [Take Alipay payments with an integration](/api/func-alipay-integration.md)

Auth

- [Require a Verified Email to Sign Up](/api/auth-verified-registration.md)
- [Reset and Change Passwords](/api/auth-password-reset.md)
- [Sign In From a Func](/api/auth-func-login.md)
- [Query users from a Func](/api/func-user-directory.md)

On this page

- [Where the call belongs](#where)
- [getServerSideProps](#getserversideprops)
- [Func, when secrets are involved](#func-when-secrets-are-involved)
- [The client, for interaction-only data](#the-client-for-interaction-only-data)
- [Site data needs nothing](#site-data-needs-nothing)
- [Calling an external API on the server](#fetch)
- [Declaring the cache dependency](#cache-depends)
- [Invalidating the cache](#publish)
- [Permission](#permission)
- [keys cannot be empty](#keys-cannot-be-empty)
- [At most 20 per call](#at-most-20-per-call)
- [Publishing an unknown key is safe](#publishing-an-unknown-key-is-safe)
- [Full example](#example)
- [Cache boundaries](#boundaries)
- [Checklist](#checklist)

Backend/Calling external APIs on the server and managing the cache

# 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.

Copy Markdown link

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.

**Agent objective**

Fetch external data in `getServerSideProps`, declare it with `ctx.cacheDepends(key)`, and have whoever changes the data call the publish endpoint to invalidate the cache. Anything that needs a secret or writes data belongs in a Func, not in the page.

## 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.

```typescript
// /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.

```typescript
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 `getServerSideProps` works — 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.

```json
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:

```json
{ "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.

```typescript
// /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:

```typescript
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=a` and `?category=b` are two entries, but they declare the same key and one publish invalidates both.
- Reading a cookie in `getServerSideProps` makes 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.
- `props` are 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.
- `props` contain no secrets, internal IDs, or unused upstream fields.

![Creght](https://ugc.talizen.com/_assets/site/2061660904709165056/1780797461299__creght_logo.png)

This website is built with [Creght](/)

[Discord](https://discord.gg/Qvq2nmZNnb)

## Links

- [Pricing](/price.md)
- [Solutions](/solution.md)
- [Customers](/customers.md)
- [Help Center](/help.md)
- [Contact Us](/contact.md)
- [Update Logs & Blogs](/blogs.md)
- [Refund Policy](/tuikuan.md)

## Resources

- [All Resources](/resources.md)
- [Templates](/templates.md)
- [Components](https://creghtlib.site.creght.com)
- [Animations](/design/effects.md)
- [Figma to Creght](/figma2creght.md)
- [API for AI](/api.md)

## Terms

- [Terms of Service](/legal/terms.md)
- [Privacy Policy](/legal/privacy.md)
- [Acceptable Use Policy](/legal/acceptable-use.md)

## Social Media

- [Twitter](https://twitter.com)
- [Facebook](https://www.facebook.com)
- [LinkedIn](https://www.linkedin.com)

[蜀ICP备2023038192号-2](https://beian.miit.gov.cn)
