Static Export & Middleware
Explains the relationship between static export (output: 'export') and src/middleware.ts, and the static replacements.
Static Export (output: 'export') and src/middleware.ts
TL;DR
output: 'export' (static export) does not support middleware. To enable static export you must remove src/middleware.ts and hand its responsibilities (locale validation, prefix stripping, root redirect) over to generateStaticParams, setRequestLocale, localePrefix: 'always', and an nginx redirect.
What middleware does in normal mode
In non-static mode, src/middleware.ts uses next-intl's createMiddleware(routing) for i18n routing. It has three core jobs:
- Locale detection & validation — derive the language from the URL, 404 for invalid locales.
- Prefix stripping (as-needed) — with
localePrefix: 'as-needed', the default locale (e.g. zh) has no prefix; middleware treats/zh/project-configand/project-configas the same route at runtime. - Root redirect — redirect
/to the default-locale path.
Key point: all of this happens dynamically in the Node edge runtime.
Why static export can't run middleware
output: 'export'turnsnext buildoutput into pure static files (out/) served directly by nginx — there is no Node runtime.- Middleware runs in the edge/server runtime and needs request-time execution. There is no place for that logic in static files.
- So Next.js ignores (or errors on) middleware when
output: 'export'is set.
The fundamental conflict: as-needed vs static export
Even keeping middleware wouldn't help, because localePrefix: 'as-needed' itself conflicts with static export:
as-neededrelies on the runtime to strip the default locale's prefix, making/project-configequivalent to/zh/project-config.- Static export pre-generates routes via
generateStaticParams, which only emits prefixed pages (/en/*,/zh/*). - The unprefixed
/and/project-configare never generated → they 404.
Verified: after generateStaticParams returns ['en', 'zh'], out/ contains only en/ and zh/ directories — no root index.html.
Static-export replacements (responsibility map)
| Middleware responsibility | Static-export replacement |
|---|---|
| Locale validation (404) | hasLocale + notFound() in [locale]/layout.tsx |
| Prefix stripping (as-needed) | switch to localePrefix: 'always' |
| Pre-generate routes | generateStaticParams() in [locale]/layout.tsx |
Root / redirect | nginx location = / { return 301 /zh/; } |
| Mark locale for static rendering | setRequestLocale(locale) |
Checklist to switch the build to static export
In react-next-admin:
- Add
output: "export"(optionallytrailingSlash: true) tonext.config.ts. - Delete
src/middleware.ts. - Change
localePrefix: 'as-needed'to'always'insrc/i18n/routing.ts. - Wrap
useSearchParams()usages in<Suspense>(required for static prerendering). - Keep
generateStaticParams+setRequestLocale+hasLocalein[locale]/layout.tsx. - Let nginx handle the root redirect at deploy time.
Note: after removing middleware,
next startno longer applies; previewout/with any static server (e.g.npx serve out).