Revert "Revert "refactor: write pages router export files directly to server/pages/"" (#93409)
Re-apply #92735
Before re-landing we need to investigate why the pages router middleware
i18n test fails when deployed.
Update: Investigation/fix:
https://github.com/vercel/next.js/pull/92735#issuecomment-4242978559
Reverts vercel/next.js#92733
---
Cursor plan based on the investigation Claude did:
# Fix i18n export orphan HTML files in server/pages/
## Problem
When i18n is configured, the export path map in `build/index.ts` creates
locale-prefixed entries for every page (e.g., `/en/home/a`,
`/fr/home/a`), but only deletes the non-locale entry from `defaultMap`
for SSG pages. For non-SSG static pages, the non-locale entry (e.g.,
`/home/a`) remains, causing the export worker to render and write orphan
HTML files directly to `server/pages/`.
These orphan files interfere with routing on deployment platforms (e.g.,
a `[teamId]/[slug].html` orphan matches rewritten paths that should
404).
## Root Cause
In [packages/next/src/build/index.ts](packages/next/src/build/index.ts)
at line 2988:
```2988:2991:packages/next/src/build/index.ts
if (isSsg) {
// remove non-locale prefixed variant from defaultMap
delete defaultMap[page]
}
```
The `if (isSsg)` guard means non-SSG pages keep their non-locale entry,
which gets rendered and written to `server/pages/` as an orphan file.
## Fix
**Single-line change** in
[packages/next/src/build/index.ts](packages/next/src/build/index.ts):
Remove the `if (isSsg)` guard so `delete defaultMap[page]` always runs
when i18n is enabled.
The locale-prefixed entries are already created in the loop above (line
2981), and they capture the original `page` value via
`defaultMap[page]?.page || page`. The non-locale entry is not needed
after that -- in the old staging-directory flow (`moveExportedPage`), it
was rendered but never moved to the final output directory.
**Safety verification:**
- `updatePagesManifestForExportedPage` (line 3754) is called based on
`combinedPages`, not the export map -- it still correctly replaces
non-locale manifest entries with locale-prefixed ones
- `pageDuration` tracking (line 3745) may return `undefined` for the
non-locale path, but this is typed `number | undefined` and used with
`|| 0` fallback at line 375 of `build/utils.ts` -- purely cosmetic
- SSG pages already have this exact behavior (delete from defaultMap),
so this change makes non-SSG consistent with SSG
- The `ssgPageDurations` invariant check (line 3737-3738) only runs for
SSG pages, which are unaffected
## Test
Add assertions to the existing i18n integration test at
[test/integration/i18n-support/test/index.test.ts](test/integration/i18n-support/test/index.test.ts)
to verify that non-locale-prefixed HTML orphans do not exist in
`server/pages/` after build. The test already has production mode
assertions checking locale-prefixed file existence and manifest
correctness (lines 81-99).