静态导出的 Next.js 网站就是一个文件夹里的 HTML、CSS、JavaScript 和静态资源。你可以把它放到任何静态托管上——Cloudflare Pages、GitHub Pages、S3、Netlify 或一台 Nginx 服务器——不需要运行、维护或付费的 Node.js 服务器。这个网站就是这样构建的。
本指南介绍静态导出在 App Router 中的工作方式、你会失去什么,以及每个限制的绕过方法。
开启静态导出
从 Next.js 14 开始,next export 命令已被移除,静态导出改为配置项:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
// Optional: /about → /about/index.html instead of /about.html
trailingSlash: true,
images: { unoptimized: true },
}
export default nextConfig现在 next build 会把网站输出到 out/ 目录。用任意静态服务器预览:
npx serve out可以用的功能
比很多人想象的多:
- 服务端组件(Server Components)。 它们在构建时运行一次。获取数据、读取文件、调用 API——结果直接写进 HTML。
- 客户端组件(Client Components)、hooks、context 以及所有客户端库。
- 动态路由,前提是用
generateStaticParams列出所有路径。 - 只有
GET的 Route Handlers,会生成静态文件(很适合feed.json、sitemap.xml、robots.txt)。 - Metadata API:标题、描述、Open Graph、canonical 链接。
- 使用
<Link>的客户端导航和预加载。
不能用的功能,以及替代方案
| 功能 | 失效原因 | 替代方案 |
|---|---|---|
| 默认图片优化 | 需要服务器 | images.unoptimized: true、预先裁好尺寸,或自定义 loader(Cloudinary、imgix、Cloudflare Images) |
没有 generateStaticParams 的动态路由 |
页面必须在构建时确定 | 列出所有参数,并设置 dynamicParams = false |
cookies()、headers() 等请求时数据 |
运行时没有请求 | 在客户端读取,或把逻辑移到 API |
| Server Actions | 需要服务器 | POST 到无服务器函数(Workers、Lambda) |
| Middleware | 需要服务器 | 托管平台层面的规则:_redirects、_headers、边缘函数 |
配置中的 redirects、rewrites、headers |
由 Next 服务器执行 | 同上:在托管平台配置 |
ISR / revalidate |
没有服务器来重新生成 | 内容变化时重新构建(Git 推送或 webhook) |
| 内置 i18n 路由 | Pages Router 的功能 | 使用语言路由段:app/[lang]/... 配合 generateStaticParams |
正确处理动态路由
每个动态路由段都需要在构建时提供所有取值:
import { getAllPosts, getPost } from '@/lib/blog'
import { notFound } from 'next/navigation'
export const dynamicParams = false // unknown slugs are a 404, not a runtime render
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map((post) => ({ slug: post.slug }))
}
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
if (!post) notFound()
return <article dangerouslySetInnerHTML={{ __html: post.html }} />
}注意:在 Next.js 15 中 params 是一个 Promise,需要 await。
一个坑:如果 generateStaticParams 返回空数组,构建会失败,因为这个路由不会生成任何页面。如果集合可能为空(比如刚建的博客),就返回一个占位参数,让它渲染 404。
图片
有三种合理的选择:
unoptimized: true,自己导出合适尺寸的图片(WebP 或 AVIF,显示宽度的 1–2 倍)。只要你足够自律,这种方式简单又快。- 自定义 loader,指向图片 CDN,按需返回不同尺寸:
export default function loader({ src, width, quality }) {
return `https://images.example.com/${src}?w=${width}&q=${quality || 75}`
}const nextConfig = {
output: 'export',
images: { loader: 'custom', loaderFile: './image-loader.js' },
}- 普通
<img>,配合srcset、loading="lazy"以及明确的width/height,避免布局偏移。
表单和其他服务端功能
静态网站依然可以有表单、搜索和登录——只是服务端部分放在别处。常见做法是一个小型无服务器函数,页面通过 fetch 调用它。我把本站使用的方案写在了用 Cloudflare Workers 和 D1 搭建无服务器联系表单。
重定向和响应头
在托管平台配置。在 Cloudflare Pages 和 Netlify 上,public/ 里的 _redirects 文件会被复制到 out/:
/old-page /new-page 301
/blog/* /journal/:splat 301用 _headers 配置缓存和安全响应头:
/_next/static/*
Cache-Control: public, max-age=31536000, immutable没有服务器如何更新内容
因为没有 ISR,内容变化需要重新构建。这其实没有听起来那么麻烦:把托管平台连接到 Git 仓库,每次推送都会在一两分钟内自动构建和部署。如果内容放在 CMS 里,把 CMS 的 webhook 指向托管平台的部署钩子即可。对博客来说,把文章作为 Markdown 放在仓库里是最简单的方案——见不用 CMS,在 Next.js 中搭建 Markdown 博客。
什么时候不适合静态导出
- 依赖登录用户、需要在服务端渲染的页面。
- 成千上万、每几分钟就变化的页面(构建时间随页面数增长)。
- 确实需要请求时渲染的场景,比如个性化定价。
对于官网、作品集、文档、博客和落地页,静态导出通常是最快、最便宜也最稳定的方案。
检查清单
-
next.config中设置output: 'export' - 每个动态路由都有
generateStaticParams和dynamicParams = false - 图片已处理(unoptimized、自定义 loader 或
<img>) - 没有使用
cookies()、headers()、Server Actions 或 middleware - 重定向和响应头在托管平台配置
- 部署由 Git 推送或 CMS webhook 触发
- 首次部署前在本地预览过
out/
下一步:确保搜索引擎能理解你导出的页面——Next.js App Router 技术 SEO 清单。

