在服务端调用外部 API 并管理缓存
在 getServerSideProps 里取站点之外的数据,用 ctx.cacheDepends 声明依赖,再通过发布接口作废页面缓存——不声明依赖,页面会一直停在第一次渲染的结果上。
页面可以在服务端取站点之外的数据:第三方 API、你自己的服务、平台的公开接口。渲染结果会进 HTML 缓存,而缓存只在收到 publish 事件时失效。站点自己的数据(CMS、表单、数据表)取数时会自动登记依赖,外部数据不会——所以外部数据必须由页面自己声明依赖,否则页面会一直显示第一次渲染时的内容。
取数放在哪里
放在 getServerSideProps
公开的只读数据、首屏就要出现、需要被搜索引擎和 AI 抓到的内容。数据会渲染进 HTML。
需要密钥就放 Func
getServerSideProps 的 ctx 里没有密钥,也没有数据库写入能力。凡是要带 API Key、要签名、要落库的调用,写在 /backend/func 里,页面调用函数键。
只在交互后才需要就放客户端
点击后才加载的数据用浏览器端 fetch。它不进 HTML 缓存,也就不需要声明依赖。
站点自己的数据不用管
通过 talizen/cms 等平台包取的数据会自动登记依赖,内容一改缓存就失效,不需要 cacheDepends。
在服务端调用外部 API
fetch 在服务端渲染期间是全局可用的,用法与浏览器一致。返回的 props 会被序列化进 HTML 交给页面组件。
// /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 是你自己定义的字符串,只要发布时用同一个即可。
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 的页面缓存作废。下一个访问者会触发重新渲染并拿到新数据。
POST /api/p/project/{project_id}/site/{site_id}/render_cache/publish
Authorization: Bearer <token>
Content-Type: application/json
{ "keys": ["templates"] }
返回实际被作废的依赖 key:
{ "published": ["custom/{site_id}/templates"] }
权限
与「发布站点」相同。站点命名空间取自 URL 上的 site_id,不是请求体里的字段,因此拿这个接口清不到别人站点的缓存。
keys 不能为空
空数组或全是空字符串会返回 400。这种调用不会清掉任何东西,返回 200 只会让你以为生效了。
一次最多 20 个
超过返回 400。正常场景不需要一次发这么多。
发布未声明的 key 是安全的
不会报错,只是没有任何缓存被清掉。
作废是「删除」而不是「重算」:缓存条目直接失效,重新渲染的成本由下一个访问者承担。需要预热就在发布之后自己请求一次页面。
完整例子
一个从外部接口取列表、支持按分类筛选的页面。注意 key 与筛选条件无关:同一份数据源的所有分页和筛选组合共用一个 key,一次发布全部作废。
// /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 } }
}
外部数据变更后:
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 和用不到的上游字段。
