静态还是 SSR,这一个决定悄悄定下了你的托管账单、首字节时间、Core Web Vitals,以及半夜被 500 报警叫醒的频率。内容站的答案几乎总是”以静态为主,只在真正需要的地方撒点动态”。本文教你怎么真的做判断,看看当前 Next.js 和 Astro 里配置长什么样,以及 2026 年 6 月各条路分别要花多少钱。
一句话结论
- 默认静态:凡是对所有匿名访客都返回同一份 HTML 的页面(文章、文档、营销页),一律静态。
- ISR(每 N 秒重新生成)是”基本静态、偶尔刷新”的中间档。多数人想用 SSR 时,其实想要的就是它。
- SSR 只用在响应真的依赖请求的地方:登录态、地区、A/B 分组,或者每次渲染都必须最新的实时数据。
- 静态资源在 Cloudflare、Firebase Hosting、GitHub Pages、S3 上都是免费且不计量的。一旦上了 SSR,就把 Node/Worker 运行时塞进了请求链路,原本免费的页面就变成了按次计费的函数调用。
三种模式横向对比
| 模式 | 渲染时机 | 从哪里发 | 新鲜度 | 成本形态 |
|---|---|---|---|---|
| 静态(SSG) | 构建时 | CDN 边缘,无服务器 | 到下次构建为止 | 最便宜,出口流量通常免费 |
| ISR | 构建时生成,到点后重渲 | CDN 边缘 + 偶尔构建 | 最多滞后 revalidate 秒 | 主要是 CDN + 少量函数调用 |
| SSR | 每次请求 | Node/Worker 函数 | 始终最新 | 按请求付费,有冷启动 |
对内容站来说,90% 以上的路由都属于第一行。
每种模式在代码里长什么样
Next.js 15(App Router)
在 Next.js 15 里,路由 segment 默认不再缓存——默认的 dynamic = 'auto' 是能静态就静态、必须动态才动态。文章页通常你想去掉这种”猜”,直接强制静态:
// app/articles/[slug]/page.tsx
export const dynamic = 'force-static';
export const revalidate = false;
export async function generateStaticParams() {
return getAllSlugs().map((slug) => ({ slug }));
}
export default async function Article({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return <Markdown source={post.body} />;
}
ISR 结构一样,只是内容按周期刷新而不用重新部署。页面里所有 fetch 中最小的那个 revalidate 决定整页:
export const revalidate = 300; // 最多每 5 分钟重渲一次
SSR——只在页面真的依赖请求时才用。调用 cookies() 或 headers() 会自动把这个 segment 转成动态,所以你确实要动态时就显式写出来:
export const dynamic = 'force-dynamic'; // 每次请求都打到函数
Next.js 15 里服务端 fetch() 不再像 14 那样默认隐式缓存了,所以要写明确:要缓存就传 { cache: 'force-cache' },要定时重验就传 { next: { revalidate: N } }。留着隐式不写,正是路由悄悄变动态的根源。
Astro 5
Astro 默认就是静态。注意 Astro 5 的一个变化:旧的 output: 'hybrid' 模式被移除,合并进了 static。现在按路由 SSR 不再靠改配置开关——你只要加一个 adapter,需要按需渲染的路由导出 prerender = false 即可:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'static', // 默认,输出纯 HTML
adapter: node({ mode: 'standalone' }), // 只有某些路由需要 SSR 时才加
});
---
// src/pages/dashboard.astro —— 单独把这条路由开成 SSR
export const prerender = false;
---
如果没有任何路由需要服务器,干脆把 adapter 去掉,直接发纯静态文件。(本站就是这么跑的。)
怎么判断一条路由该用哪种
每条路由只问一句:同一分钟里,两个匿名访客之间有什么不同?
- 什么都不变(同一篇文章、同一个文档页)——静态。
- 因人或因地区而异(登录态 dashboard、地理优惠、购物车)——SSR。
- 想要 CDN 便宜托管、延迟稳定——静态。
- 每次渲染都必须最新的实时数据(价格、库存、比分)——SSR,或短窗口 ISR。
- 部署到 Cloudflare Pages、Firebase Hosting、GitHub Pages、S3——静态。这些平台发的是文件;SSR 需要 Worker/函数,会改变成本和故障模型。
实际要花多少钱(2026 年 6 月)
到这里,选择就不再是纸上谈兵了。
| 托管 | 静态 | SSR / 函数 |
|---|---|---|
| Cloudflare | 静态资源所有套餐免费且不计量 | Workers/Pages Functions 免费档:每天 10 万次请求、每次 30ms CPU;超出按 Workers 付费价 |
| Firebase Hosting | Spark 套餐免费,允许商用 | Cloud Functions 需要 Blaze 按量付费套餐 |
| Vercel | Hobby 免费但仅限非商用;100 GB 流量、4 小时 active CPU | Hobby 每月 100 万次函数调用;Pro 每席 $20、1 TB 流量,超出 $0.15/GB |
| GitHub Pages / S3 | 静态免费 / 近乎免费 | 无服务器运行时,做不了 SSR |
有两点最容易踩坑:Vercel 的免费 Hobby 套餐仅限非商用,所以挂广告或联盟链接的内容站,严格说要上 Pro,每席每月 $20(Vercel 定价)。还有 Cloudflare 上,请求静态资源是免费且无限的,但请求一旦命中 Function(SSR),就要计入每天 10 万次的免费额度(Cloudflare Pages 定价)。不小心走了动态,那是真账单,不是假设。
为什么 Core Web Vitals 上静态更占优
Google 的”良好”门槛(取真实 CrUX 现场数据的第 75 百分位,截至 2026 年 6 月)是 LCP 低于 2.5 秒、INP 低于 200 毫秒、CLS 低于 0.1。从 CDN 边缘发预构建好的 HTML,TTFB 几乎为零,而 TTFB 正是影响 LCP 最大的杠杆。SSR 给每个未命中缓存的请求都加了一次运行时往返和冷启动抖动,恰恰把 LCP 预算花在了最不该花的地方。INP 如今是最容易不达标的指标——约 43% 的站点过不了 200ms 这条线——但那是客户端 JavaScript 的问题,跟渲染模式无关,上 SSR 并不能解决。
一步步审一遍你的路由
- 列出每条路由,逐条标:静态、ISR(带
revalidate窗口)、SSR、客户端渲染。 - 每个标了 SSR 的,问”同一分钟两个匿名访客之间有什么变化”。没变化就该静态。
- 每个 ISR 选一个
revalidate窗口。多数博客 60 秒够用;价格页可能要 300 秒;几乎没有真需要 1 秒的。 - Next.js 里需要确定静态的路由就写上
dynamic = 'force-static',免得 layout 里一个误用的cookies()把整棵子树悄悄翻成动态。 - 每个服务端
fetch()都显式写cache/next.revalidate,别依赖那个在 Next.js 14 到 15 之间变过的默认行为。 - 构建后读输出。Next.js 会把每条路由标成
Static、SSG或Dynamic;逐条审,把不该是Dynamic的揪出来。 - 对部署后的页面跑 Lighthouse。TTFB 和 LCP 干净就说明选对了。
容易踩的坑
- 不小心变动态:共享 layout 里一个
cookies()或headers(),整棵子树在 Next.js 里都变动态。 - ISR revalidate 设太狠:日更内容配 10 秒窗口,白白烧函数调用还没好处。
- “以防万一”选 SSR:过早灵活和过早优化是一回事——你立刻就在冷启动和账单上付了钱。
- 需要保持 CDN 可缓存的路由忘了写
dynamic = 'force-static'。 - 在只发文件的托管上硬上 SSR(GitHub Pages、纯 S3、不带 Function 的 Cloudflare Pages),然后不明白为什么不行。
- Astro 留着用不上的 adapter:给一个本可纯静态的站点白白塞了个服务器运行时。
这篇适合谁
独立内容站——博客、文档、垂直目录、课程站——95% 以上流量看到同一份 HTML。
什么时候 SSR 才是对的
有登录墙、个性化流、购物车,或内容跟访客绑定(auth、geo、A/B)的 App。这些是真需要 SSR 或客户端渲染的,硬逼成静态会直接坏掉。
FAQ
- ISR 是不是就是缓存版 SSR?: 效果上是。Next.js 在
revalidate窗口过期后的第一次请求时重建页面,然后给后续所有人发缓存 HTML,直到下次过期。你拿到的是”准实时”的内容,加上 CDN 的成本。 - 用了 CMS 还能静态吗?: 能。build 时从 Contentful / Sanity / Notion 拉数据仍然是静态。发布时用 webhook 触发重新构建,既保新鲜又保 CDN 成本。
- Astro 真把 hybrid 模式去掉了?: 是。从 Astro 5 起,
output: 'hybrid'没有了,合并进了output: 'static'。加一个 adapter,给需要服务器的那几条路由写prerender = false,其余保持静态。 - 搜索怎么办?: 基于静态 JSON 索引的客户端搜索,几千篇文章以内的内容站都够用。除非真要做大规模服务端过滤,才上 SSR 搜索。
- 一个项目能静态和 SSR 混用吗?: 能——这正是 Next.js App Router 和 Astro adapter 的核心。每条路由自选策略。