我的几个站全部用 Next.js 的 output: 'export' 静态导出:构建产物是纯
HTML/CSS/JS,扔到任意静态服务器就能跑,没有 Node 进程、没有服务器运维。
这个选择不是免费的,这篇文章把边界讲清楚。
换来了什么
- 部署极简:
out/目录整个拷上服务器/OSS 就完事,不需要 PM2、 不需要容器、不需要考虑 Node 版本漂移; - 天然抗流量:静态文件随便被 CDN 缓存,官网场景下几乎不存在 「被打挂」的概念;
- 安全面小:没有服务端运行时,攻击面收缩到静态文件本身;
- 构建期报错:所有数据获取都发生在
next build,内容错了构建直接 失败,坏内容到不了线上。
失去了什么
静态导出本质是把所有「动态」挪到构建期或客户端,以下能力直接不可用:
| 能力 | 静态导出下的状态 | 替代方案 |
|---|---|---|
| 服务端 API Routes | 不可用(除非 force-static) | 外部服务/表单代收 |
| ISR 增量再生 | 不可用 | 重新构建重新发布 |
| 请求级动态渲染 | 不可用 | 客户端 fetch |
| 中间件 Middleware | 不可用 | CDN 边缘规则 |
| 图片优化服务 | 关闭(unoptimized) | 构建期压好图 |
对品牌官网和技术博客,这个清单几乎无痛:内容发布频率低(重新构建即 「ISR」),交互都在客户端,表单可以走第三方代收再通知到企微/邮件。
常用的「伪装动态」手法
1. Route Handler + force-static 生成派生文件。 RSS、sitemap 这类「构建期可确定」的动态产物:
// app/rss.xml/route.ts
export const dynamic = "force-static";
export async function GET() {
return new Response(buildRss(), {
headers: { "Content-Type": "application/rss+xml; charset=utf-8" },
});
}构建时执行一次,输出落成真正的 out/rss.xml 静态文件。
2. generateStaticParams 展开所有路径。
/posts/[slug] 这类动态路由必须在构建期穷举:
export function generateStaticParams() {
return getAllPosts().map((post) => ({ slug: post.slug }));
}忘了写这个函数,动态路由在静态导出下会被静默跳过——不报错,只是 产物里没有那些页面,发布后才发现 404。
3. 数据获取全部收口到构建期模块。 文件读进内存、校验、缓存,页面组件只消费纯函数结果。好处是测试 不需要起 Next,数据层单测就是普通 Node 测试。
判断 checklist
什么项目适合静态导出:
- 内容发布频率低,发新内容 = 触发一次重新构建可以接受;
- 没有按请求变化的个性化内容(登录态、A/B、地理定向);
- 搜索/评论/表单这类动态能力愿意交给第三方或客户端方案;
- 有 CI 能自动构建发布(没有的话,发布 = 手动跑命令)。
反过来,只要有任何一条硬性不满足——比如需要 SSR 做 SEO 的个性化
首页——就老实用 next start 或托管到 Vercel,别跟 output: 'export'
较劲,它的边界是设计如此,不是 bug。
一句话结论
静态导出适合「内容即产品」的站:官网、博客、文档站。它把复杂度从 运行时挪到了构建期,而构建期复杂度是靠 CI 和纪律管理的,比运行时 复杂度便宜得多。