返回首页

静态导出与 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 路由,核心职责有三:

  1. locale 检测与校验:根据 URL 判断语言,非法 locale 走 404。
  2. 前缀剥离(as-needed):当 localePrefix: 'as-needed' 时,默认语言(如 zh)的 URL 不带前缀,middleware 在运行时把 /zh/project-config 与 /project-config 视为同一路由。
  3. 根路径重定向:访问 / 时重定向到默认语言路径。

关键点:这些都是在 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 里把构建切换到静态导出,需要:

  1. next.config.ts 增加 output: "export"(可选 trailingSlash: true)。
  2. 删除 src/middleware.ts。
  3. src/i18n/routing.ts 把 localePrefix: 'as-needed' 改成 'always'。
  4. 页面里用到 useSearchParams() 的地方用 <Suspense> 包裹(静态预渲染要求)。
  5. [locale]/layout.tsx 保留 generateStaticParams + setRequestLocale + hasLocale 校验。
  6. 部署时由 nginx 处理根路径重定向。

注意:删除 middleware 后 next start 也不再适用;本地预览 out/ 用任意静态服务器(如 npx serve out)。