From bbfda44248386484079ff2b522bbe53e6dc4e485 Mon Sep 17 00:00:00 2001 From: Sukka Date: Sat, 14 May 2022 21:57:48 +0800 Subject: [PATCH] fix(#36855/#30300): export 404.html correctly (#36910) ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` The PR fixes #30300 and #36855. The corresponding integration test case has been added. --- packages/next/export/index.ts | 21 +++++--- test/integration/export-404/next.config.js | 5 ++ .../pages/404.js | 0 .../integration/export-404/test/index.test.js | 51 +++++++++++++++++++ .../export-override-404/test/index.test.js | 21 -------- 5 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 test/integration/export-404/next.config.js rename test/integration/{export-override-404 => export-404}/pages/404.js (100%) create mode 100644 test/integration/export-404/test/index.test.js delete mode 100644 test/integration/export-override-404/test/index.test.js diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index 366a00e4e1ee..fd08ec9ab0b6 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -418,13 +418,20 @@ export default async function exportApp( }) ) - if ( - !options.buildExport && - !exportPathMap['/404'] && - !exportPathMap['/404.html'] - ) { - exportPathMap['/404'] = exportPathMap['/404.html'] = { - page: '/_error', + // only add missing 404 page when `buildExport` is false + if (!options.buildExport) { + // only add missing /404 if not specified in `exportPathMap` + if (!exportPathMap['/404']) { + exportPathMap['/404'] = { page: '/_error' } + } + + /** + * exports 404.html for backwards compat + * E.g. GitHub Pages, GitLab Pages, Cloudflare Pages, Netlify + */ + if (!exportPathMap['/404.html']) { + // alias /404.html to /404 to be compatible with custom 404 / _error page + exportPathMap['/404.html'] = exportPathMap['/404'] } } diff --git a/test/integration/export-404/next.config.js b/test/integration/export-404/next.config.js new file mode 100644 index 000000000000..d703fe4768a8 --- /dev/null +++ b/test/integration/export-404/next.config.js @@ -0,0 +1,5 @@ +module.exports = (phase) => { + return { + trailingSlash: false, + } +} diff --git a/test/integration/export-override-404/pages/404.js b/test/integration/export-404/pages/404.js similarity index 100% rename from test/integration/export-override-404/pages/404.js rename to test/integration/export-404/pages/404.js diff --git a/test/integration/export-404/test/index.test.js b/test/integration/export-404/test/index.test.js new file mode 100644 index 000000000000..b90a681e4336 --- /dev/null +++ b/test/integration/export-404/test/index.test.js @@ -0,0 +1,51 @@ +/* eslint-env jest */ + +import { promises } from 'fs' +import { join } from 'path' +import { nextBuild, nextExport, File } from 'next-test-utils' + +const { readFile, access, stat } = promises +const appDir = join(__dirname, '../') +const outdir = join(appDir, 'out') + +const nextConfig = new File(join(appDir, 'next.config.js')) + +const fileExist = (path) => + access(path) + .then(() => stat(path)) + .then((stats) => (stats.isFile() ? true : false)) + .catch(() => false) + +// Issue #36855 +// https://github.com/vercel/next.js/issues/36855 +describe('Static 404 Export', () => { + it('only export 404.html when trailingSlash: false', async () => { + await nextBuild(appDir) + await nextExport(appDir, { outdir }) + + expect(await fileExist(join(outdir, '404.html'))).toBe(true) + expect(await fileExist(join(outdir, '404.html.html'))).toBe(false) + expect(await fileExist(join(outdir, '404/index.html'))).toBe(false) + }) + + it('export 404.html and 404/index.html when trailingSlash: true', async () => { + nextConfig.replace(`trailingSlash: false`, `trailingSlash: true`) + await nextBuild(appDir) + await nextExport(appDir, { outdir }) + nextConfig.restore() + + expect(await fileExist(join(outdir, '404/index.html'))).toBe(true) + expect(await fileExist(join(outdir, '404.html.html'))).toBe(false) + expect(await fileExist(join(outdir, '404.html'))).toBe(true) + }) +}) + +describe('Export with a page named 404.js', () => { + it('should export a custom 404.html instead of default 404.html', async () => { + await nextBuild(appDir) + await nextExport(appDir, { outdir }) + + const html = await readFile(join(outdir, '404.html'), 'utf8') + expect(html).toMatch(/this is a 404 page override the default 404\.html/) + }) +}) diff --git a/test/integration/export-override-404/test/index.test.js b/test/integration/export-override-404/test/index.test.js deleted file mode 100644 index 9ce5134a901d..000000000000 --- a/test/integration/export-override-404/test/index.test.js +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-env jest */ - -import { promises } from 'fs' -import { join } from 'path' -import { nextBuild, nextExport } from 'next-test-utils' - -const { readFile } = promises -const appDir = join(__dirname, '../') -const outdir = join(appDir, 'out') - -describe('Export with a page named 404.js', () => { - beforeAll(async () => { - await nextBuild(appDir) - await nextExport(appDir, { outdir }) - }) - - it('should export a custom 404.html instead of default 404.html', async () => { - const html = await readFile(join(outdir, '404.html'), 'utf8') - expect(html).toMatch(/this is a 404 page override the default 404\.html/) - }) -})