# 为什么 SSR 必须缓存 · 渲染一次，服务千次

> SSR 每个请求都要现算 HTML——这很贵。缓存的本质，是让「渲染一次的结果」被复用千次万次。这一章讲清缓存为何是 SSR 的命门、缓存的分层结构，以及那条贯穿始终的「新鲜度 vs 扛量」权衡。

- 作者：David（道雾轩）
- 专栏：SSR 从入门到精通（https://daiw.org/manual/ssr.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/ssr/why-cache
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 为什么 SSR 必须缓存 · 渲染一次，服务千次

我们正式进入本专栏的重点。开篇先把最重要的直觉钉牢：

> **SSR 的本质是「每请求现算」，而缓存的本质是「算一次、用很多次」。** 没有缓存的 SSR，几乎无法上规模。

[SSR 那章](https://daiw.org/manual/ssr/what-is-ssr) 说过：每一个请求，服务器都要查库、调 API、把组件渲染成 HTML 字符串。假设渲染一个页面要 50ms CPU——一个请求还好，可当这个页面每秒被访问 2000 次，服务器每秒就要花 100 秒的 CPU 去渲染。**渲染开销随流量线性增长，服务器很快就被自己压垮。**

缓存改变了这个算式：**把渲染好的结果存起来，后续请求直接拿现成的，根本不再触发渲染。** 一次渲染的成果，可以服务成千上万个请求。这就是缓存对 SSR 的意义——它不是「优化项」，而是**让 SSR 在生产环境可用的前提**。

## 命中与未命中：一切的分水岭

缓存世界里最重要的两个词：

- **命中(hit)**：请求要的东西缓存里有，直接返回。**快、几乎零成本。**
- **未命中(miss)**：缓存里没有，只能老老实实去**现算一遍**（触发完整 SSR），再把结果存进缓存。**慢、贵。**

于是**缓存的全部目标，就是尽可能提高命中率(hit ratio)**。命中率 99% 意味着 100 个请求里只有 1 个真的触发了渲染，其余 99 个都是从缓存里秒回的——服务器的渲染压力直接降到 1%。命中率从 90% 提到 99%，不是「好一点」，是渲染负载**降为原来的十分之一**。

## 缓存是分层的

缓存不是一个东西，而是**一串关卡**。一个请求从用户出发，会依次经过多层缓存，**在任何一层命中，就不必再往后走**——尤其是不必走到最贵的那一步：源站现算。

```mermaid
flowchart LR
  U["用户请求"] --> B["① 浏览器缓存"]
  B -->|"未命中"| C["② CDN / 边缘缓存"]
  C -->|"未命中"| A["③ 应用 / 数据缓存"]
  A -->|"未命中"| O["④ 源站:现算 SSR(贵)"]
```

- **① 浏览器缓存**：用户自己电脑上的缓存。命中的话，请求**根本没离开设备**，最快。
- **② CDN / 边缘缓存**：离用户最近的 CDN 节点。命中的话，请求**没到你的服务器**——这是保护源站最关键的一层，后面[专门讲](https://daiw.org/manual/ssr/cdn-caching)。
- **③ 应用 / 数据缓存**：你服务器内部的缓存（Redis、内存、框架的数据缓存）。渲染时要的数据命中这里，就省掉查库 / 调 API。
- **④ 源站现算**：全都没命中，才真正跑一次完整 SSR。**这是你最想避免触发的一步。**

<Callout type="info">
  记住这个「关卡」心智：**每往后一层，成本高一个数量级。** 浏览器命中 ≈ 免费；CDN 命中 ≈ 一次就近网络读取；应用缓存命中 ≈ 省掉数据库；源站现算 ≈ 全套开销。**缓存优化的艺术，就是让尽可能多的请求在尽可能靠前的关卡就被拦下。** 后面几章会逐层展开：HTTP 头（浏览器）、CDN（边缘）、数据缓存（应用）。
</Callout>

## 缓存缓存的到底是什么

「缓存」在 SSR 语境里，其实指两类不同的东西，别混：

- **整页 HTML 缓存(full-page / route cache)**：把**渲染好的整份 HTML** 存起来。命中时直接吐 HTML，连渲染都省了。最省，但只适合「对所有人一样」的页面。
- **数据缓存(data cache)**：缓存的不是 HTML，而是渲染时要用的**数据**（某个 API 的返回、某次查询的结果）。命中时仍要渲染，但省掉了取数。适合「HTML 因人而异、但底层数据可共享」的场景。

这两类对应不同的层、解决不同的问题，后面都有专章。现在你只要知道：**「缓存 HTML」和「缓存数据」是两把不同的刀。**

## 绕不开的权衡：新鲜度 vs 扛量

[渲染光谱那章](https://daiw.org/manual/ssr/rendering-spectrum) 提过的核心矛盾，在缓存这里变成了最日常的决策：

<Callout type="warn">
  **缓存的每一个好处，都是用「一点点不新鲜」换来的。**

  你把一份渲染结果缓存 60 秒——这 60 秒内，哪怕源数据变了，用户看到的还是旧的。你换来了这 60 秒内**近乎无限的扛量**（全走缓存），代价是**最多 60 秒的陈旧**。缓存时间(TTL)越长，越省、越能扛，但越可能给用户看旧数据；越短，越新鲜，但命中率越低、越常触发昂贵的渲染。

  **缓存的全部功夫，就是在「能容忍多旧」和「想省多少」之间，为每一类内容找到那个甜点。** 一个每周更新的博客，缓存一小时都嫌短；一个秒级变动的股价，缓存 5 秒都嫌长。没有万能 TTL，只有「这个内容能容忍多旧」。
</Callout>

## 小结与下一章

SSR 每请求现算、开销随流量线性涨，缓存用「算一次、用很多次」破了这个局。缓存是**分层的关卡**（浏览器 → CDN → 应用 → 源站），越靠前越省；缓存的对象分**整页 HTML** 和**数据**两类；而所有缓存决策都逃不开**新鲜度 vs 扛量**的权衡。

理解了「为什么」，接下来逐层攻破「怎么做」。第一层、也是一切缓存的基石，是 **HTTP 协议本身的缓存机制**——服务器靠一个 `Cache-Control` 响应头，就能指挥浏览器和 CDN 怎么缓存。下一章从零讲透它。👉 [HTTP 缓存基石：Cache-Control 全家桶](https://daiw.org/manual/ssr/http-caching)。

## 参考文献

- [1] MDN.「HTTP caching.」https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching —— 缓存分层与命中概念。
- [2] web.dev.「Love your cache.」https://web.dev/articles/love-your-cache —— 缓存对性能与成本的意义。
- [3] Cloudflare.「What is a CDN cache? Cache hit ratio.」https://www.cloudflare.com/learning/cdn/what-is-caching/ —— 命中率与分层缓存。
