# 在服务端调用外部 API 并管理缓存｜Creght AI 编程指南

> 页面在服务端 fetch 外部接口后，渲染结果会进 HTML 缓存且不会自动失效。本文说明如何在 getServerSideProps 里取数、用 ctx.cacheDepends 声明缓存依赖，以及如何调用发布接口作废缓存。

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

[查看 llms.txt](/llms.txt)

概览

- [Creght AI 编程指南](/api.md)

AI 可发现性

- [如何优化 llms.txt](/api/optimize-llms-txt.md)

站点配置

- [实现基于域名的多语言路由](/api/domain-locale-routing.md)

后端

- [在服务端调用外部 API 并管理缓存](/api/ssr-external-api-cache.md)
- [使用 Func 构建站点后端能力](/api/func-backend.md)
- [JSON 表：定义、读写与查询](/api/func-json-tables.md)
- [上传文件：直传与 Func 内生成](/api/func-assets-upload.md)
- [超时配置与流式响应](/api/func-timeout-streaming.md)
- [使用 Func 接入支付宝电脑网站支付](/api/func-alipay-payment.md)

集成

- [使用集成发送邮件与验证码](/api/func-email-integration.md)
- [使用集成接入支付宝支付](/api/func-alipay-integration.md)

登录与用户

- [注册时验证邮箱](/api/auth-verified-registration.md)
- [实现找回密码与修改密码](/api/auth-password-reset.md)
- [在 Func 里实现登录](/api/auth-func-login.md)
- [在 Func 里查询用户](/api/func-user-directory.md)

本页目录

- [取数放在哪里](#where)
- [放在 getServerSideProps](#放在-getserversideprops)
- [需要密钥就放 Func](#需要密钥就放-func)
- [只在交互后才需要就放客户端](#只在交互后才需要就放客户端)
- [站点自己的数据不用管](#站点自己的数据不用管)
- [在服务端调用外部 API](#fetch)
- [声明缓存依赖](#cache-depends)
- [作废缓存](#publish)
- [权限](#权限)
- [keys 不能为空](#keys-不能为空)
- [一次最多 20 个](#一次最多-20-个)
- [发布未声明的 key 是安全的](#发布未声明的-key-是安全的)
- [完整例子](#example)
- [缓存边界](#boundaries)
- [检查清单](#checklist)

后端/在服务端调用外部 API 并管理缓存

# 在服务端调用外部 API 并管理缓存

在 getServerSideProps 里取站点之外的数据，用 ctx.cacheDepends 声明依赖，再通过发布接口作废页面缓存——不声明依赖，页面会一直停在第一次渲染的结果上。

复制 Markdown 链接

页面可以在服务端取站点之外的数据：第三方 API、你自己的服务、平台的公开接口。渲染结果会进 HTML 缓存，而缓存只在收到 publish 事件时失效。站点自己的数据（CMS、表单、数据表）取数时会自动登记依赖，外部数据不会——所以外部数据必须由页面自己声明依赖，否则页面会一直显示第一次渲染时的内容。

**智能体目标**

在 `getServerSideProps` 里 fetch 外部数据，用 `ctx.cacheDepends(key)` 声明这份数据的依赖，数据变更方调用发布接口作废缓存。需要密钥或写操作时改用 Func，不要放在页面里。

## 取数放在哪里

### 放在 getServerSideProps

公开的只读数据、首屏就要出现、需要被搜索引擎和 AI 抓到的内容。数据会渲染进 HTML。

### 需要密钥就放 Func

`getServerSideProps` 的 `ctx` 里没有密钥，也没有数据库写入能力。凡是要带 API Key、要签名、要落库的调用，写在 `/backend/func` 里，页面调用函数键。

### 只在交互后才需要就放客户端

点击后才加载的数据用浏览器端 fetch。它不进 HTML 缓存，也就不需要声明依赖。

### 站点自己的数据不用管

通过 `talizen/cms` 等平台包取的数据会自动登记依赖，内容一改缓存就失效，不需要 `cacheDepends`。

## 在服务端调用外部 API

`fetch` 在服务端渲染期间是全局可用的，用法与浏览器一致。返回的 `props` 会被序列化进 HTML 交给页面组件。

```typescript
// /page/templates.tsx
export async function getServerSideProps(ctx) {
  const res = await fetch('https://api.example.com/v1/items?limit=12')

  // 外部服务挂了不应该让整个页面 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>
  )
}
```

> 抛出异常会让页面渲染失败。外部依赖务必判断响应状态并准备降级内容。

## 声明缓存依赖

在 `getServerSideProps` 里调用 `ctx.cacheDepends(key)`，告诉渲染端「这个页面依赖名为 key 的外部数据」。key 是你自己定义的字符串，只要发布时用同一个即可。

```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 ?? [] } }
}
```

- 可以一次声明多个： `ctx.cacheDepends('templates', 'template-tags')`，也可以分多次调用，重复的 key 会自动去重。
- 实际记录到缓存里的是 `custom/{site_id}/templates`。站点命名空间由渲染端按当前站点补上，页面写不出别的站点的 key。
- 只要在 `getServerSideProps` 执行期间调用即可，放在 fetch 之前或之后都行。
- 不声明的后果：这个页面只会在站点发布、页面改动这类既有事件下失效，外部数据再怎么变都不会重新渲染。

## 作废缓存

外部数据变更之后，调用发布接口把声明了该 key 的页面缓存作废。下一个访问者会触发重新渲染并拿到新数据。

```json
POST /api/p/project/{project_id}/site/{site_id}/render_cache/publish
Authorization: Bearer <token>
Content-Type: application/json

{ "keys": ["templates"] }
```

返回实际被作废的依赖 key：

```json
{ "published": ["custom/{site_id}/templates"] }
```

### 权限

与「发布站点」相同。站点命名空间取自 URL 上的 `site_id`，不是请求体里的字段，因此拿这个接口清不到别人站点的缓存。

### keys 不能为空

空数组或全是空字符串会返回 400。这种调用不会清掉任何东西，返回 200 只会让你以为生效了。

### 一次最多 20 个

超过返回 400。正常场景不需要一次发这么多。

### 发布未声明的 key 是安全的

不会报错，只是没有任何缓存被清掉。

> 作废是「删除」而不是「重算」：缓存条目直接失效，重新渲染的成本由下一个访问者承担。需要预热就在发布之后自己请求一次页面。

## 完整例子

一个从外部接口取列表、支持按分类筛选的页面。注意 key 与筛选条件无关：同一份数据源的所有分页和筛选组合共用一个 key，一次发布全部作废。

```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 } }
}
```

外部数据变更后：

```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'] }),
  },
)
```

## 缓存边界

- 缓存按站点、域名、路径和查询串区分。 `?category=a` 与 `?category=b` 是两条缓存，但它们声明的是同一个 key，一次发布会一起作废。
- 在 `getServerSideProps` 里读 Cookie，页面会按读到的 Cookie 名分变体缓存；写 Cookie 则整页不进缓存，此时声明依赖没有意义。
- 预览模式不走缓存，调试时看到的永远是最新数据。要验证缓存行为请用正式域名访问。
- `props` 会原样序列化进 HTML，浏览器可见。只放页面要渲染的字段，不要把上游接口的完整响应直接透传出去。

## 检查清单

- 外部接口的调用写在 `getServerSideProps` 里，需要密钥或写入的改走 Func。
- 响应状态判断和降级内容都写了，外部服务不可用时页面仍能渲染。
- 每一份外部数据都有对应的 `ctx.cacheDepends(key)`。
- 数据变更方会调用发布接口，key 与页面里写的完全一致。
- `props` 里没有密钥、内部 ID 和用不到的上游字段。

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

此网站使用 [Creght](/) 创建

![微信客服](https://fsu.creght.com/site/2066727200882692096/1785119134612__image.png)

微信客服

## 链接

- [价格](/price.md)
- [解决方案](/solution.md)
- [客户案例](/customers.md)
- [帮助中心](/help.md)
- [联系我们](/contact.md)
- [更新记录 & 博客](/blogs.md)
- [退款说明](/tuikuan.md)

## 资源

- [全部资源](/resources.md)
- [模板](/templates.md)
- [组件库](https://creghtlib.site.creght.com)
- [动效库](/design/effects.md)
- [Figma to Creght](/figma2creght.md)
- [API](/api.md)

## 产品对比

- [对比上线了](/creght-vs-sxl.md)
- [对比凡科建站](/creght-vs-fkw.md)
- [自己写代码 vs Creght](/compare/self-coding.md)
- [外包 vs 自己做](/compare/outsourcing.md)

## 协议

- [用户协议](/legal/terms.md)
- [隐私政策](/legal/privacy.md)
- [可接受使用政策](/legal/acceptable-use.md)

## 社交媒体

- [小红书](https://www.xiaohongshu.com/user/profile/5a38606811be10715f4895b6)
- [哔哩哔哩](https://space.bilibili.com/513308095)

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