mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
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).
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
import { nextTestSetup, isNextDev } from 'e2e-utils'
|
|
;(isNextDev ? describe.skip : describe)('i18n-beforefiles-rewrite', () => {
|
|
const { next } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
const locales = ['en', 'fr']
|
|
|
|
const autoStaticRoutes = [
|
|
{ pathname: '', content: 'index' },
|
|
{ pathname: '/home/a', content: 'Home A' },
|
|
{ pathname: '/home/b', content: 'Home B' },
|
|
{ pathname: '/dynamic/static', content: 'static route' },
|
|
{ pathname: '/dynamic/first', content: 'dynamic route' },
|
|
{ pathname: '/team/slug', content: '/[teamId]/[slug]' },
|
|
]
|
|
|
|
it('should serve locale-prefixed auto-static pages', async () => {
|
|
for (const { pathname, content } of autoStaticRoutes) {
|
|
for (const locale of locales) {
|
|
const url = `/${locale}${pathname}`
|
|
const res = await next.fetch(url)
|
|
const html = await res.text()
|
|
|
|
expect({ url, status: res.status }).toEqual({ url, status: 200 })
|
|
expect(html).toContain(content)
|
|
}
|
|
}
|
|
})
|
|
|
|
it.each(['', '/en', '/fr'])(
|
|
'should 404 for %s/rewrite-before-files',
|
|
async (localePrefix) => {
|
|
// beforeFiles rewrites /rewrite-before-files to /somewhere.
|
|
// /somewhere does not match any page, so this should 404.
|
|
// Before the fix, orphan dynamic HTML output could incorrectly match.
|
|
const res = await next.fetch(`${localePrefix}/rewrite-before-files`)
|
|
expect(res.status).toBe(404)
|
|
}
|
|
)
|
|
})
|