静态导出与 middleware
说明静态导出(output: 'export')与 src/middleware.ts 的关系,以及静态模式下的替代方案。
静态导出(output: 'export')与 src/middleware.ts 的关系
一句话结论
output: 'export'(静态导出)不支持 middleware。要做静态导出,必须删除 src/middleware.ts,并把它的职责(locale 校验、前缀剥离、根路径重定向)改由 generateStaticParams、setRequestLocale、localePrefix: 'always' 和 nginx 重定向来接管。
背景:middleware 在普通模式下的作用
在非静态模式下,src/middleware.ts 用 next-intl 的 createMiddleware(routing) 做 i18n 路由,核心职责有三:
- locale 检测与校验:根据 URL 判断语言,非法 locale 走 404。
- 前缀剥离(as-needed):当
localePrefix: 'as-needed'时,默认语言(如 zh)的 URL 不带前缀,middleware 在运行时把/zh/project-config与/project-config视为同一路由。 - 根路径重定向:访问
/时重定向到默认语言路径。
关键点:这些都是在 Node 边缘运行时里动态完成的。
为什么静态导出不支持 middleware
output: 'export'会把next build的产物变成纯静态文件(out/目录),交给 nginx 等静态服务器直接托管,没有 Node 运行时。- middleware 运行在边缘/服务器运行时,依赖请求时动态执行。静态文件里没有地方运行这段逻辑。
- 因此 Next.js 在
output: 'export'下会忽略(或直接报错)middleware。
as-needed 与静态导出的根本矛盾
即使强行保留 middleware 也无济于事,因为 localePrefix: 'as-needed' 本身就和静态导出冲突:
as-needed依赖运行时把默认语言的前缀去掉,让/project-config等价于/zh/project-config。- 静态导出靠
generateStaticParams预生成路由,只会产出/en/*、/zh/*这些带前缀的页面。 - 无前缀的
/、/project-config根本不会被生成 → 访问即 404。
实测:generateStaticParams 返回 ['en', 'zh'] 后,out/ 里只有 en/ 和 zh/ 两个目录,没有根 index.html。
静态导出下的替代方案(职责对照)
| middleware 原职责 | 静态导出下的替代 |
|---|---|
| locale 校验(非法 404) | [locale]/layout.tsx 里 hasLocale + notFound() |
| 前缀剥离(as-needed) | 改为 localePrefix: 'always',所有路由固定带前缀 |
| 预生成所有路由 | [locale]/layout.tsx 的 generateStaticParams() |
根路径 / 重定向 | nginx location = / { return 301 /zh/; } |
| locale 静态渲染标记 | setRequestLocale(locale) |
落地改动清单
在 react-next-admin 里把构建切换到静态导出,需要:
next.config.ts增加output: "export"(可选trailingSlash: true)。- 删除
src/middleware.ts。 src/i18n/routing.ts把localePrefix: 'as-needed'改成'always'。- 页面里用到
useSearchParams()的地方用<Suspense>包裹(静态预渲染要求)。 [locale]/layout.tsx保留generateStaticParams+setRequestLocale+hasLocale校验。- 部署时由 nginx 处理根路径重定向。
注意:删除 middleware 后
next start也不再适用;本地预览out/用任意静态服务器(如npx serve out)。