From c09324a66c5ea37b40986066ab0c6ae3db31f4a0 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 12 Feb 2021 15:54:45 -0600 Subject: [PATCH 1/9] Add generating static 500 status page --- packages/next/build/index.ts | 29 ++- packages/next/lib/constants.ts | 2 +- packages/next/next-server/lib/constants.ts | 1 + .../next/next-server/server/next-server.ts | 15 +- packages/next/next-server/server/render.tsx | 15 +- packages/next/server/next-dev-server.ts | 6 + test/integration/500-page/next.config.js | 1 + test/integration/500-page/pages/500.js | 2 + test/integration/500-page/pages/err.js | 5 + test/integration/500-page/pages/index.js | 1 + test/integration/500-page/test/index.test.js | 245 ++++++++++++++++++ 11 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 test/integration/500-page/next.config.js create mode 100644 test/integration/500-page/pages/500.js create mode 100644 test/integration/500-page/pages/err.js create mode 100644 test/integration/500-page/pages/index.js create mode 100644 test/integration/500-page/test/index.test.js diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 6a7e7d62d4d26..c393162661902 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -11,7 +11,7 @@ import { pathToRegexp } from 'next/dist/compiled/path-to-regexp' import path from 'path' import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages' import { - PAGES_404_GET_INITIAL_PROPS_ERROR, + STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR, PUBLIC_DIR_MIDDLEWARE_CONFLICT, } from '../lib/constants' import { fileExists } from '../lib/file-exists' @@ -41,6 +41,7 @@ import { SERVERLESS_DIRECTORY, SERVER_DIRECTORY, SERVER_FILES_MANIFEST, + STATIC_STATUS_PAGES, } from '../next-server/lib/constants' import { getRouteRegex, @@ -746,7 +747,9 @@ export default async function build( !workerResult.isStatic && !workerResult.hasStaticProps ) { - throw new Error(PAGES_404_GET_INITIAL_PROPS_ERROR) + throw new Error( + `\`pages/404\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + ) } // we need to ensure the 404 lambda is present since we use // it when _app has getInitialProps @@ -757,6 +760,16 @@ export default async function build( staticPages.delete(page) } } + + if ( + STATIC_STATUS_PAGES.includes(page) && + !workerResult.isStatic && + !workerResult.hasStaticProps + ) { + throw new Error( + `\`pages${page}\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + ) + } } catch (err) { if (err.message !== 'INVALID_DEFAULT_EXPORT') throw err invalidPages.add(page) @@ -887,8 +900,18 @@ export default async function build( const { i18n } = config + const usedStaticStatusPages = STATIC_STATUS_PAGES.filter( + (page) => + mappedPages[page] && mappedPages[page].startsWith('private-next-pages') + ) + usedStaticStatusPages.forEach((page) => { + if (!ssgPages.has(page)) { + staticPages.add(page) + } + }) + await traceAsyncFn(tracer.startSpan('static-generation'), async () => { - if (staticPages.size > 0 || ssgPages.size > 0 || useStatic404) { + if (useStatic404 || ssgPages.size > 0 || staticPages.size > 0) { const combinedPages = [...staticPages, ...ssgPages] detectConflictingPaths( diff --git a/packages/next/lib/constants.ts b/packages/next/lib/constants.ts index 9d202591121d7..878779267890f 100644 --- a/packages/next/lib/constants.ts +++ b/packages/next/lib/constants.ts @@ -30,7 +30,7 @@ export const SERVER_PROPS_GET_INIT_PROPS_CONFLICT = `You can not use getInitialP export const SERVER_PROPS_SSG_CONFLICT = `You can not use getStaticProps or getStaticPaths with getServerSideProps. To use SSG, please remove getServerSideProps` -export const PAGES_404_GET_INITIAL_PROPS_ERROR = `\`pages/404\` can not have getInitialProps/getServerSideProps, https://err.sh/next.js/404-get-initial-props` +export const STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR = `can not have getInitialProps/getServerSideProps, https://err.sh/next.js/404-get-initial-props` export const SERVER_PROPS_EXPORT_ERROR = `pages with \`getServerSideProps\` can not be exported. See more info here: https://err.sh/next.js/gssp-export` diff --git a/packages/next/next-server/lib/constants.ts b/packages/next/next-server/lib/constants.ts index 03a4fc100705b..c07b817533ce2 100644 --- a/packages/next/next-server/lib/constants.ts +++ b/packages/next/next-server/lib/constants.ts @@ -38,3 +38,4 @@ export const PERMANENT_REDIRECT_STATUS = 308 export const STATIC_PROPS_ID = '__N_SSG' export const SERVER_PROPS_ID = '__N_SSP' export const OPTIMIZED_FONT_PROVIDERS = ['https://fonts.googleapis.com/css'] +export const STATIC_STATUS_PAGES = ['/500'] diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index 970ab91d2510b..0b957150e62e2 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -32,6 +32,7 @@ import { ROUTES_MANIFEST, SERVERLESS_DIRECTORY, SERVER_DIRECTORY, + STATIC_STATUS_PAGES, TEMPORARY_REDIRECT_STATUS, } from '../lib/constants' import { @@ -1364,6 +1365,12 @@ export default class Server { res.statusCode = 404 } + // ensure correct status is set when visiting a status page + // directly e.g. /500 + if (STATIC_STATUS_PAGES.includes(pathname)) { + res.statusCode = parseInt(pathname.substr(1), 10) + } + // handle static page if (typeof components.Component === 'string') { return components.Component @@ -1908,9 +1915,15 @@ export default class Server { result = await this.findPageComponents('/404', query) using404Page = result !== null } + let statusPage = `/${res.statusCode}` + + if (!result && STATIC_STATUS_PAGES.includes(statusPage)) { + result = await this.findPageComponents(statusPage, query) + } if (!result) { result = await this.findPageComponents('/_error', query) + statusPage = '/_error' } if ( @@ -1928,7 +1941,7 @@ export default class Server { html = await this.renderToHTMLWithComponents( req, res, - using404Page ? '/404' : '/_error', + statusPage, result!, { ...this.renderOpts, diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 98f44887d3a7d..8c4b6d695f5a5 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -8,7 +8,7 @@ import { GSP_NO_RETURNED_VALUE, GSSP_COMPONENT_MEMBER_ERROR, GSSP_NO_RETURNED_VALUE, - PAGES_404_GET_INITIAL_PROPS_ERROR, + STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR, SERVER_PROPS_GET_INIT_PROPS_CONFLICT, SERVER_PROPS_SSG_CONFLICT, SSG_GET_INITIAL_PROPS_CONFLICT, @@ -22,6 +22,7 @@ import { AMP_RENDER_TARGET, SERVER_PROPS_ID, STATIC_PROPS_ID, + STATIC_STATUS_PAGES, } from '../lib/constants' import { defaultHead } from '../lib/head' import { HeadManagerContext } from '../lib/head-manager-context' @@ -530,7 +531,17 @@ export async function renderToHTML( } if (pathname === '/404' && (hasPageGetInitialProps || getServerSideProps)) { - throw new Error(PAGES_404_GET_INITIAL_PROPS_ERROR) + throw new Error( + `\`pages/404\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + ) + } + if ( + STATIC_STATUS_PAGES.includes(pathname) && + (hasPageGetInitialProps || getServerSideProps) + ) { + throw new Error( + `\`pages${pathname}\` ${STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR}` + ) } } if (isAutoExport) renderOpts.autoExport = true diff --git a/packages/next/server/next-dev-server.ts b/packages/next/server/next-dev-server.ts index 4ef111da02d47..b45bfc71a5ba3 100644 --- a/packages/next/server/next-dev-server.ts +++ b/packages/next/server/next-dev-server.ts @@ -20,6 +20,7 @@ import { PHASE_DEVELOPMENT_SERVER, CLIENT_STATIC_FILES_PATH, DEV_CLIENT_PAGES_MANIFEST, + STATIC_STATUS_PAGES, } from '../next-server/lib/constants' import { getRouteMatcher, @@ -627,6 +628,11 @@ export default class DevServer extends Server { await this.devReady if (res.statusCode === 404 && (await this.hasPage('/404'))) { await this.hotReloader!.ensurePage('/404') + } else if ( + STATIC_STATUS_PAGES.includes(`/${res.statusCode}`) && + (await this.hasPage(`/${res.statusCode}`)) + ) { + await this.hotReloader!.ensurePage(`/${res.statusCode}`) } else { await this.hotReloader!.ensurePage('/_error') } diff --git a/test/integration/500-page/next.config.js b/test/integration/500-page/next.config.js new file mode 100644 index 0000000000000..4ba52ba2c8df6 --- /dev/null +++ b/test/integration/500-page/next.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/test/integration/500-page/pages/500.js b/test/integration/500-page/pages/500.js new file mode 100644 index 0000000000000..36344491336b4 --- /dev/null +++ b/test/integration/500-page/pages/500.js @@ -0,0 +1,2 @@ +const page = () => 'custom 500 page' +export default page diff --git a/test/integration/500-page/pages/err.js b/test/integration/500-page/pages/err.js new file mode 100644 index 0000000000000..61c7136f7ba7e --- /dev/null +++ b/test/integration/500-page/pages/err.js @@ -0,0 +1,5 @@ +const page = () => 'page with err' +page.getInitialProps = () => { + throw new Error('oops') +} +export default page diff --git a/test/integration/500-page/pages/index.js b/test/integration/500-page/pages/index.js new file mode 100644 index 0000000000000..f6c15d1f66e8a --- /dev/null +++ b/test/integration/500-page/pages/index.js @@ -0,0 +1 @@ +export default () => 'hello from index' diff --git a/test/integration/500-page/test/index.test.js b/test/integration/500-page/test/index.test.js new file mode 100644 index 0000000000000..3b9e071bff867 --- /dev/null +++ b/test/integration/500-page/test/index.test.js @@ -0,0 +1,245 @@ +/* eslint-env jest */ + +import fs from 'fs-extra' +import { join } from 'path' +import { + killApp, + findPort, + launchApp, + nextStart, + nextBuild, + renderViaHTTP, + fetchViaHTTP, + waitFor, + getPageFileFromPagesManifest, +} from 'next-test-utils' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '../') +const pages500 = join(appDir, 'pages/500.js') +const nextConfig = join(appDir, 'next.config.js') +const gip500Err = /`pages\/500` can not have getInitialProps\/getServerSideProps/ + +let nextConfigContent +let appPort +let app + +const runTests = (mode = 'server') => { + it('should use pages/500', async () => { + const html = await renderViaHTTP(appPort, '/500') + expect(html).toContain('custom 500 page') + }) + + it('should set correct status code with pages/500', async () => { + const res = await fetchViaHTTP(appPort, '/500') + expect(res.status).toBe(500) + }) + + it('should not error when visited directly', async () => { + const res = await fetchViaHTTP(appPort, '/500') + expect(res.status).toBe(500) + expect(await res.text()).toContain('custom 500 page') + }) + + if (mode !== 'dev') { + it('should output 500.html during build', async () => { + const page = getPageFileFromPagesManifest(appDir, '/500') + expect(page.endsWith('.html')).toBe(true) + }) + + it('should add /500 to pages-manifest correctly', async () => { + const manifest = await fs.readJSON( + join(appDir, '.next', mode, 'pages-manifest.json') + ) + expect('/500' in manifest).toBe(true) + }) + } +} + +describe('500 Page Support', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests('dev') + }) + + describe('server mode', () => { + beforeAll(async () => { + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(() => killApp(app)) + + runTests('server') + }) + + describe('serverless mode', () => { + beforeAll(async () => { + nextConfigContent = await fs.readFile(nextConfig, 'utf8') + await fs.writeFile( + nextConfig, + ` + module.exports = { + target: 'serverless' + } + ` + ) + await nextBuild(appDir) + appPort = await findPort() + app = await nextStart(appDir, appPort) + }) + afterAll(async () => { + await fs.writeFile(nextConfig, nextConfigContent) + await killApp(app) + }) + + runTests('serverless') + }) + + it('shows error with getInitialProps in pages/500 build', async () => { + await fs.move(pages500, `${pages500}.bak`) + await fs.writeFile( + pages500, + ` + const page = () => 'custom 500 page' + page.getInitialProps = () => ({ a: 'b' }) + export default page + ` + ) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + await fs.remove(pages500) + await fs.move(`${pages500}.bak`, pages500) + + expect(stderr).toMatch(gip500Err) + expect(code).toBe(1) + }) + + it('shows error with getInitialProps in pages/500 dev', async () => { + await fs.move(pages500, `${pages500}.bak`) + await fs.writeFile( + pages500, + ` + const page = () => 'custom 500 page' + page.getInitialProps = () => ({ a: 'b' }) + export default page + ` + ) + + let stderr = '' + appPort = await findPort() + app = await launchApp(appDir, appPort, { + onStderr(msg) { + stderr += msg || '' + }, + }) + await renderViaHTTP(appPort, '/500') + await waitFor(1000) + + await killApp(app) + + await fs.remove(pages500) + await fs.move(`${pages500}.bak`, pages500) + + expect(stderr).toMatch(gip500Err) + }) + + it('does not show error with getStaticProps in pages/500 build', async () => { + await fs.move(pages500, `${pages500}.bak`) + await fs.writeFile( + pages500, + ` + const page = () => 'custom 500 page' + export const getStaticProps = () => ({ props: { a: 'b' } }) + export default page + ` + ) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + await fs.remove(pages500) + await fs.move(`${pages500}.bak`, pages500) + + expect(stderr).not.toMatch(gip500Err) + expect(code).toBe(0) + }) + + it('does not show error with getStaticProps in pages/500 dev', async () => { + await fs.move(pages500, `${pages500}.bak`) + await fs.writeFile( + pages500, + ` + const page = () => 'custom 500 page' + export const getStaticProps = () => ({ props: { a: 'b' } }) + export default page + ` + ) + + let stderr = '' + appPort = await findPort() + app = await launchApp(appDir, appPort, { + onStderr(msg) { + stderr += msg || '' + }, + }) + await renderViaHTTP(appPort, '/abc') + await waitFor(1000) + + await killApp(app) + + await fs.remove(pages500) + await fs.move(`${pages500}.bak`, pages500) + + expect(stderr).not.toMatch(gip500Err) + }) + + it('shows error with getServerSideProps in pages/500 build', async () => { + await fs.move(pages500, `${pages500}.bak`) + await fs.writeFile( + pages500, + ` + const page = () => 'custom 500 page' + export const getServerSideProps = () => ({ props: { a: 'b' } }) + export default page + ` + ) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + await fs.remove(pages500) + await fs.move(`${pages500}.bak`, pages500) + + expect(stderr).toMatch(gip500Err) + expect(code).toBe(1) + }) + + it('shows error with getServerSideProps in pages/500 dev', async () => { + await fs.move(pages500, `${pages500}.bak`) + await fs.writeFile( + pages500, + ` + const page = () => 'custom 500 page' + export const getServerSideProps = () => ({ props: { a: 'b' } }) + export default page + ` + ) + + let stderr = '' + appPort = await findPort() + app = await launchApp(appDir, appPort, { + onStderr(msg) { + stderr += msg || '' + }, + }) + await renderViaHTTP(appPort, '/500') + await waitFor(1000) + + await killApp(app) + + await fs.remove(pages500) + await fs.move(`${pages500}.bak`, pages500) + + expect(stderr).toMatch(gip500Err) + }) +}) From a504ce951d457f98bf7ac52b53f2792122c01135 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Feb 2021 09:33:17 -0600 Subject: [PATCH 2/9] Add test for _app GIP and static 500 --- test/integration/500-page/test/index.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/integration/500-page/test/index.test.js b/test/integration/500-page/test/index.test.js index 3b9e071bff867..24d665670c392 100644 --- a/test/integration/500-page/test/index.test.js +++ b/test/integration/500-page/test/index.test.js @@ -18,6 +18,7 @@ jest.setTimeout(1000 * 60 * 2) const appDir = join(__dirname, '../') const pages500 = join(appDir, 'pages/500.js') +const pagesApp = join(appDir, 'pages/_app.js') const nextConfig = join(appDir, 'next.config.js') const gip500Err = /`pages\/500` can not have getInitialProps\/getServerSideProps/ @@ -102,6 +103,25 @@ describe('500 Page Support', () => { runTests('serverless') }) + it('still build statically with getInitialProps in _app', async () => { + await fs.writeFile( + pagesApp, + ` + const page = () => 'custom 500 page' + page.getInitialProps = () => ({ a: 'b' }) + export default page + ` + ) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + await fs.remove(pagesApp) + + expect(stderr).not.toMatch(gip500Err) + expect(code).toBe(0) + expect( + await fs.pathExists(join(appDir, '.next/server/pages/500.html')) + ).toBe(true) + }) + it('shows error with getInitialProps in pages/500 build', async () => { await fs.move(pages500, `${pages500}.bak`) await fs.writeFile( From f850843a559524c5770558eb298a9f2c5cd88dd1 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Feb 2021 09:35:47 -0600 Subject: [PATCH 3/9] Update test --- test/integration/500-page/test/index.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/integration/500-page/test/index.test.js b/test/integration/500-page/test/index.test.js index 24d665670c392..66fa0e3ae64b8 100644 --- a/test/integration/500-page/test/index.test.js +++ b/test/integration/500-page/test/index.test.js @@ -107,8 +107,10 @@ describe('500 Page Support', () => { await fs.writeFile( pagesApp, ` - const page = () => 'custom 500 page' - page.getInitialProps = () => ({ a: 'b' }) + import App from 'next/app' + + const page = ({ Component, pageProps }) => + page.getInitialProps = (ctx) => App.getInitialProps(ctx) export default page ` ) From d6061d1761225a93ae6af798fc796eec128efc97 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Feb 2021 11:54:42 -0600 Subject: [PATCH 4/9] Always generate 500.html --- packages/next/build/index.ts | 646 +++++++++---------- packages/next/export/worker.ts | 4 + test/integration/500-page/test/index.test.js | 42 +- 3 files changed, 364 insertions(+), 328 deletions(-) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index c393162661902..bebc6680ebebb 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -910,400 +910,392 @@ export default async function build( } }) + const hasPages500 = usedStaticStatusPages.includes('/500') + await traceAsyncFn(tracer.startSpan('static-generation'), async () => { - if (useStatic404 || ssgPages.size > 0 || staticPages.size > 0) { - const combinedPages = [...staticPages, ...ssgPages] - - detectConflictingPaths( - [ - ...combinedPages, - ...pageKeys.filter((page) => !combinedPages.includes(page)), - ], - ssgPages, - additionalSsgPaths - ) - const exportApp = require('../export').default - const exportOptions = { - silent: false, - buildExport: true, - threads: config.experimental.cpus, - pages: combinedPages, - outdir: path.join(distDir, 'export'), - statusMessage: 'Generating static pages', - } - const exportConfig: any = { - ...config, - initialPageRevalidationMap: {}, - ssgNotFoundPaths: [] as string[], - // Default map will be the collection of automatic statically exported - // pages and incremental pages. - // n.b. we cannot handle this above in combinedPages because the dynamic - // page must be in the `pages` array, but not in the mapping. - exportPathMap: (defaultMap: any) => { - // Dynamically routed pages should be prerendered to be used as - // a client-side skeleton (fallback) while data is being fetched. - // This ensures the end-user never sees a 500 or slow response from the - // server. - // - // Note: prerendering disables automatic static optimization. - ssgPages.forEach((page) => { - if (isDynamicRoute(page)) { - tbdPrerenderRoutes.push(page) - - if (ssgStaticFallbackPages.has(page)) { - // Override the rendering for the dynamic page to be treated as a - // fallback render. - if (i18n) { - defaultMap[`/${i18n.defaultLocale}${page}`] = { - page, - query: { __nextFallback: true }, - } - } else { - defaultMap[page] = { page, query: { __nextFallback: true } } + const combinedPages = [...staticPages, ...ssgPages] + + detectConflictingPaths( + [ + ...combinedPages, + ...pageKeys.filter((page) => !combinedPages.includes(page)), + ], + ssgPages, + additionalSsgPaths + ) + const exportApp = require('../export').default + const exportOptions = { + silent: false, + buildExport: true, + threads: config.experimental.cpus, + pages: combinedPages, + outdir: path.join(distDir, 'export'), + statusMessage: 'Generating static pages', + } + const exportConfig: any = { + ...config, + initialPageRevalidationMap: {}, + ssgNotFoundPaths: [] as string[], + // Default map will be the collection of automatic statically exported + // pages and incremental pages. + // n.b. we cannot handle this above in combinedPages because the dynamic + // page must be in the `pages` array, but not in the mapping. + exportPathMap: (defaultMap: any) => { + // Dynamically routed pages should be prerendered to be used as + // a client-side skeleton (fallback) while data is being fetched. + // This ensures the end-user never sees a 500 or slow response from the + // server. + // + // Note: prerendering disables automatic static optimization. + ssgPages.forEach((page) => { + if (isDynamicRoute(page)) { + tbdPrerenderRoutes.push(page) + + if (ssgStaticFallbackPages.has(page)) { + // Override the rendering for the dynamic page to be treated as a + // fallback render. + if (i18n) { + defaultMap[`/${i18n.defaultLocale}${page}`] = { + page, + query: { __nextFallback: true }, } } else { - // Remove dynamically routed pages from the default path map when - // fallback behavior is disabled. - delete defaultMap[page] + defaultMap[page] = { page, query: { __nextFallback: true } } } + } else { + // Remove dynamically routed pages from the default path map when + // fallback behavior is disabled. + delete defaultMap[page] + } + } + }) + // Append the "well-known" routes we should prerender for, e.g. blog + // post slugs. + additionalSsgPaths.forEach((routes, page) => { + const encodedRoutes = additionalSsgPathsEncoded.get(page) + + routes.forEach((route, routeIdx) => { + defaultMap[route] = { + page, + query: { __nextSsgPath: encodedRoutes?.[routeIdx] }, } }) - // Append the "well-known" routes we should prerender for, e.g. blog - // post slugs. - additionalSsgPaths.forEach((routes, page) => { - const encodedRoutes = additionalSsgPathsEncoded.get(page) - - routes.forEach((route, routeIdx) => { - defaultMap[route] = { - page, - query: { __nextSsgPath: encodedRoutes?.[routeIdx] }, - } - }) - }) + }) - if (useStatic404) { - defaultMap['/404'] = { - page: hasPages404 ? '/404' : '/_error', - } + if (useStatic404) { + defaultMap['/404'] = { + page: hasPages404 ? '/404' : '/_error', } + } - if (i18n) { - for (const page of [ - ...staticPages, - ...ssgPages, - ...(useStatic404 ? ['/404'] : []), - ]) { - const isSsg = ssgPages.has(page) - const isDynamic = isDynamicRoute(page) - const isFallback = isSsg && ssgStaticFallbackPages.has(page) - - for (const locale of i18n.locales) { - // skip fallback generation for SSG pages without fallback mode - if (isSsg && isDynamic && !isFallback) continue - const outputPath = `/${locale}${page === '/' ? '' : page}` - - defaultMap[outputPath] = { - page: defaultMap[page]?.page || page, - query: { __nextLocale: locale }, - } + // ensure 500.html is always generated even if pages/500.html + // doesn't exist + if (!hasPages500) { + defaultMap['/500'] = { + page: '/_error', + } + } - if (isFallback) { - defaultMap[outputPath].query.__nextFallback = true - } + if (i18n) { + for (const page of [ + ...staticPages, + ...ssgPages, + ...(useStatic404 ? ['/404'] : []), + ]) { + const isSsg = ssgPages.has(page) + const isDynamic = isDynamicRoute(page) + const isFallback = isSsg && ssgStaticFallbackPages.has(page) + + for (const locale of i18n.locales) { + // skip fallback generation for SSG pages without fallback mode + if (isSsg && isDynamic && !isFallback) continue + const outputPath = `/${locale}${page === '/' ? '' : page}` + + defaultMap[outputPath] = { + page: defaultMap[page]?.page || page, + query: { __nextLocale: locale }, } - if (isSsg) { - // remove non-locale prefixed variant from defaultMap - delete defaultMap[page] + if (isFallback) { + defaultMap[outputPath].query.__nextFallback = true } } - } - - return defaultMap - }, - trailingSlash: false, - } - await exportApp(dir, exportOptions, exportConfig) + if (isSsg) { + // remove non-locale prefixed variant from defaultMap + delete defaultMap[page] + } + } + } + console.error(defaultMap) + return defaultMap + }, + trailingSlash: false, + } - const postBuildSpinner = createSpinner({ - prefixText: `${Log.prefixes.info} Finalizing page optimization`, - }) - ssgNotFoundPaths = exportConfig.ssgNotFoundPaths + await exportApp(dir, exportOptions, exportConfig) - // remove server bundles that were exported - for (const page of staticPages) { - const serverBundle = getPagePath(page, distDir, isLikeServerless) - await promises.unlink(serverBundle) - } - const serverOutputDir = path.join( - distDir, - isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY - ) + const postBuildSpinner = createSpinner({ + prefixText: `${Log.prefixes.info} Finalizing page optimization`, + }) + ssgNotFoundPaths = exportConfig.ssgNotFoundPaths - const moveExportedPage = async ( - originPage: string, - page: string, - file: string, - isSsg: boolean, - ext: 'html' | 'json', - additionalSsgFile = false - ) => { - return traceAsyncFn( - tracer.startSpan('move-exported-page'), - async () => { - file = `${file}.${ext}` - const orig = path.join(exportOptions.outdir, file) - const pagePath = getPagePath( - originPage, - distDir, - isLikeServerless - ) + // remove server bundles that were exported + for (const page of staticPages) { + const serverBundle = getPagePath(page, distDir, isLikeServerless) + await promises.unlink(serverBundle) + } + const serverOutputDir = path.join( + distDir, + isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY + ) - const relativeDest = path - .relative( - serverOutputDir, + const moveExportedPage = async ( + originPage: string, + page: string, + file: string, + isSsg: boolean, + ext: 'html' | 'json', + additionalSsgFile = false + ) => { + return traceAsyncFn( + tracer.startSpan('move-exported-page'), + async () => { + file = `${file}.${ext}` + const orig = path.join(exportOptions.outdir, file) + const pagePath = getPagePath(originPage, distDir, isLikeServerless) + + const relativeDest = path + .relative( + serverOutputDir, + path.join( path.join( - path.join( - pagePath, - // strip leading / and then recurse number of nested dirs - // to place from base folder - originPage - .substr(1) - .split('/') - .map(() => '..') - .join('/') - ), - file - ) + pagePath, + // strip leading / and then recurse number of nested dirs + // to place from base folder + originPage + .substr(1) + .split('/') + .map(() => '..') + .join('/') + ), + file ) - .replace(/\\/g, '/') - - const dest = path.join( - distDir, - isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY, - relativeDest ) + .replace(/\\/g, '/') - if (!isSsg) { - pagesManifest[page] = relativeDest - } + const dest = path.join( + distDir, + isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY, + relativeDest + ) - const isNotFound = ssgNotFoundPaths.includes(page) - - // for SSG files with i18n the non-prerendered variants are - // output with the locale prefixed so don't attempt moving - // without the prefix - if ((!i18n || additionalSsgFile) && !isNotFound) { - await promises.mkdir(path.dirname(dest), { recursive: true }) - await promises.rename(orig, dest) - } else if (i18n && !isSsg) { - // this will be updated with the locale prefixed variant - // since all files are output with the locale prefix - delete pagesManifest[page] - } + if (!isSsg) { + pagesManifest[page] = relativeDest + } - if (i18n) { - if (additionalSsgFile) return + const isNotFound = ssgNotFoundPaths.includes(page) + + // for SSG files with i18n the non-prerendered variants are + // output with the locale prefixed so don't attempt moving + // without the prefix + if ((!i18n || additionalSsgFile) && !isNotFound) { + await promises.mkdir(path.dirname(dest), { recursive: true }) + await promises.rename(orig, dest) + } else if (i18n && !isSsg) { + // this will be updated with the locale prefixed variant + // since all files are output with the locale prefix + delete pagesManifest[page] + } - for (const locale of i18n.locales) { - const curPath = `/${locale}${page === '/' ? '' : page}` - const localeExt = page === '/' ? path.extname(file) : '' - const relativeDestNoPages = relativeDest.substr( - 'pages/'.length - ) + if (i18n) { + if (additionalSsgFile) return - if (isSsg && ssgNotFoundPaths.includes(curPath)) { - continue - } + for (const locale of i18n.locales) { + const curPath = `/${locale}${page === '/' ? '' : page}` + const localeExt = page === '/' ? path.extname(file) : '' + const relativeDestNoPages = relativeDest.substr('pages/'.length) - const updatedRelativeDest = path - .join( - 'pages', - locale + localeExt, - // if it's the top-most index page we want it to be locale.EXT - // instead of locale/index.html - page === '/' ? '' : relativeDestNoPages - ) - .replace(/\\/g, '/') + if (isSsg && ssgNotFoundPaths.includes(curPath)) { + continue + } - const updatedOrig = path.join( - exportOptions.outdir, + const updatedRelativeDest = path + .join( + 'pages', locale + localeExt, - page === '/' ? '' : file - ) - const updatedDest = path.join( - distDir, - isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY, - updatedRelativeDest + // if it's the top-most index page we want it to be locale.EXT + // instead of locale/index.html + page === '/' ? '' : relativeDestNoPages ) + .replace(/\\/g, '/') - if (!isSsg) { - pagesManifest[curPath] = updatedRelativeDest - } - await promises.mkdir(path.dirname(updatedDest), { - recursive: true, - }) - await promises.rename(updatedOrig, updatedDest) + const updatedOrig = path.join( + exportOptions.outdir, + locale + localeExt, + page === '/' ? '' : file + ) + const updatedDest = path.join( + distDir, + isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY, + updatedRelativeDest + ) + + if (!isSsg) { + pagesManifest[curPath] = updatedRelativeDest } + await promises.mkdir(path.dirname(updatedDest), { + recursive: true, + }) + await promises.rename(updatedOrig, updatedDest) } } - ) - } + } + ) + } - // Only move /404 to /404 when there is no custom 404 as in that case we don't know about the 404 page - if (!hasPages404 && useStatic404) { - await moveExportedPage('/_error', '/404', '/404', false, 'html') - } + // Only move /404 to /404 when there is no custom 404 as in that case we don't know about the 404 page + if (!hasPages404 && useStatic404) { + await moveExportedPage('/_error', '/404', '/404', false, 'html') + } - for (const page of combinedPages) { - const isSsg = ssgPages.has(page) - const isStaticSsgFallback = ssgStaticFallbackPages.has(page) - const isDynamic = isDynamicRoute(page) - const hasAmp = hybridAmpPages.has(page) - const file = normalizePagePath(page) + if (!hasPages500) { + await moveExportedPage('/_error', '/500', '/500', false, 'html') + } - // The dynamic version of SSG pages are only prerendered if the - // fallback is enabled. Below, we handle the specific prerenders - // of these. - const hasHtmlOutput = !(isSsg && isDynamic && !isStaticSsgFallback) + for (const page of combinedPages) { + const isSsg = ssgPages.has(page) + const isStaticSsgFallback = ssgStaticFallbackPages.has(page) + const isDynamic = isDynamicRoute(page) + const hasAmp = hybridAmpPages.has(page) + const file = normalizePagePath(page) - if (hasHtmlOutput) { - await moveExportedPage(page, page, file, isSsg, 'html') - } + // The dynamic version of SSG pages are only prerendered if the + // fallback is enabled. Below, we handle the specific prerenders + // of these. + const hasHtmlOutput = !(isSsg && isDynamic && !isStaticSsgFallback) - if (hasAmp && (!isSsg || (isSsg && !isDynamic))) { - const ampPage = `${file}.amp` - await moveExportedPage(page, ampPage, ampPage, isSsg, 'html') + if (hasHtmlOutput) { + await moveExportedPage(page, page, file, isSsg, 'html') + } - if (isSsg) { - await moveExportedPage(page, ampPage, ampPage, isSsg, 'json') - } - } + if (hasAmp && (!isSsg || (isSsg && !isDynamic))) { + const ampPage = `${file}.amp` + await moveExportedPage(page, ampPage, ampPage, isSsg, 'html') if (isSsg) { - // For a non-dynamic SSG page, we must copy its data file - // from export, we already moved the HTML file above - if (!isDynamic) { - await moveExportedPage(page, page, file, isSsg, 'json') - - if (i18n) { - // TODO: do we want to show all locale variants in build output - for (const locale of i18n.locales) { - const localePage = `/${locale}${page === '/' ? '' : page}` - - if (!ssgNotFoundPaths.includes(localePage)) { - finalPrerenderRoutes[localePage] = { - initialRevalidateSeconds: - exportConfig.initialPageRevalidationMap[localePage], - srcRoute: null, - dataRoute: path.posix.join( - '/_next/data', - buildId, - `${file}.json` - ), - } + await moveExportedPage(page, ampPage, ampPage, isSsg, 'json') + } + } + + if (isSsg) { + // For a non-dynamic SSG page, we must copy its data file + // from export, we already moved the HTML file above + if (!isDynamic) { + await moveExportedPage(page, page, file, isSsg, 'json') + + if (i18n) { + // TODO: do we want to show all locale variants in build output + for (const locale of i18n.locales) { + const localePage = `/${locale}${page === '/' ? '' : page}` + + if (!ssgNotFoundPaths.includes(localePage)) { + finalPrerenderRoutes[localePage] = { + initialRevalidateSeconds: + exportConfig.initialPageRevalidationMap[localePage], + srcRoute: null, + dataRoute: path.posix.join( + '/_next/data', + buildId, + `${file}.json` + ), } } - } else { - finalPrerenderRoutes[page] = { - initialRevalidateSeconds: - exportConfig.initialPageRevalidationMap[page], - srcRoute: null, - dataRoute: path.posix.join( - '/_next/data', - buildId, - `${file}.json` - ), - } - } - // Set Page Revalidation Interval - const pageInfo = pageInfos.get(page) - if (pageInfo) { - pageInfo.initialRevalidateSeconds = - exportConfig.initialPageRevalidationMap[page] - pageInfos.set(page, pageInfo) } } else { - // For a dynamic SSG page, we did not copy its data exports and only - // copy the fallback HTML file (if present). - // We must also copy specific versions of this page as defined by - // `getStaticPaths` (additionalSsgPaths). - const extraRoutes = additionalSsgPaths.get(page) || [] - for (const route of extraRoutes) { - const pageFile = normalizePagePath(route) + finalPrerenderRoutes[page] = { + initialRevalidateSeconds: + exportConfig.initialPageRevalidationMap[page], + srcRoute: null, + dataRoute: path.posix.join( + '/_next/data', + buildId, + `${file}.json` + ), + } + } + // Set Page Revalidation Interval + const pageInfo = pageInfos.get(page) + if (pageInfo) { + pageInfo.initialRevalidateSeconds = + exportConfig.initialPageRevalidationMap[page] + pageInfos.set(page, pageInfo) + } + } else { + // For a dynamic SSG page, we did not copy its data exports and only + // copy the fallback HTML file (if present). + // We must also copy specific versions of this page as defined by + // `getStaticPaths` (additionalSsgPaths). + const extraRoutes = additionalSsgPaths.get(page) || [] + for (const route of extraRoutes) { + const pageFile = normalizePagePath(route) + await moveExportedPage(page, route, pageFile, isSsg, 'html', true) + await moveExportedPage(page, route, pageFile, isSsg, 'json', true) + + if (hasAmp) { + const ampPage = `${pageFile}.amp` await moveExportedPage( page, - route, - pageFile, + ampPage, + ampPage, isSsg, 'html', true ) await moveExportedPage( page, - route, - pageFile, + ampPage, + ampPage, isSsg, 'json', true ) + } - if (hasAmp) { - const ampPage = `${pageFile}.amp` - await moveExportedPage( - page, - ampPage, - ampPage, - isSsg, - 'html', - true - ) - await moveExportedPage( - page, - ampPage, - ampPage, - isSsg, - 'json', - true - ) - } - - finalPrerenderRoutes[route] = { - initialRevalidateSeconds: - exportConfig.initialPageRevalidationMap[route], - srcRoute: page, - dataRoute: path.posix.join( - '/_next/data', - buildId, - `${normalizePagePath(route)}.json` - ), - } + finalPrerenderRoutes[route] = { + initialRevalidateSeconds: + exportConfig.initialPageRevalidationMap[route], + srcRoute: page, + dataRoute: path.posix.join( + '/_next/data', + buildId, + `${normalizePagePath(route)}.json` + ), + } - // Set route Revalidation Interval - const pageInfo = pageInfos.get(route) - if (pageInfo) { - pageInfo.initialRevalidateSeconds = - exportConfig.initialPageRevalidationMap[route] - pageInfos.set(route, pageInfo) - } + // Set route Revalidation Interval + const pageInfo = pageInfos.get(route) + if (pageInfo) { + pageInfo.initialRevalidateSeconds = + exportConfig.initialPageRevalidationMap[route] + pageInfos.set(route, pageInfo) } } } } + } - // remove temporary export folder - await recursiveDelete(exportOptions.outdir) - await promises.rmdir(exportOptions.outdir) - await promises.writeFile( - manifestPath, - JSON.stringify(pagesManifest, null, 2), - 'utf8' - ) + // remove temporary export folder + await recursiveDelete(exportOptions.outdir) + await promises.rmdir(exportOptions.outdir) + await promises.writeFile( + manifestPath, + JSON.stringify(pagesManifest, null, 2), + 'utf8' + ) - if (postBuildSpinner) postBuildSpinner.stopAndPersist() - console.log() - } + if (postBuildSpinner) postBuildSpinner.stopAndPersist() + console.log() }) const analysisEnd = process.hrtime(analysisBegin) diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 01e17ade79006..ca2eaa5734db9 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -189,6 +189,10 @@ export default async function exportPage({ ...headerMocks, } as unknown) as ServerResponse + if (path === '/500' && page === '/_error') { + res.statusCode = 500 + } + envConfig.setConfig({ serverRuntimeConfig, publicRuntimeConfig: renderOpts.runtimeConfig, diff --git a/test/integration/500-page/test/index.test.js b/test/integration/500-page/test/index.test.js index 66fa0e3ae64b8..bd491fd453578 100644 --- a/test/integration/500-page/test/index.test.js +++ b/test/integration/500-page/test/index.test.js @@ -19,6 +19,7 @@ jest.setTimeout(1000 * 60 * 2) const appDir = join(__dirname, '../') const pages500 = join(appDir, 'pages/500.js') const pagesApp = join(appDir, 'pages/_app.js') +const pagesError = join(appDir, 'pages/_error.js') const nextConfig = join(appDir, 'next.config.js') const gip500Err = /`pages\/500` can not have getInitialProps\/getServerSideProps/ @@ -103,7 +104,7 @@ describe('500 Page Support', () => { runTests('serverless') }) - it('still build statically with getInitialProps in _app', async () => { + it('still builds 500 statically with getInitialProps in _app', async () => { await fs.writeFile( pagesApp, ` @@ -124,6 +125,45 @@ describe('500 Page Support', () => { ).toBe(true) }) + it('builds 500 statically by default with no pages/500', async () => { + await fs.rename(pages500, `${pages500}.bak`) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + await fs.rename(`${pages500}.bak`, pages500) + + expect(stderr).not.toMatch(gip500Err) + expect(code).toBe(0) + expect( + await fs.pathExists(join(appDir, '.next/server/pages/500.html')) + ).toBe(true) + }) + + it('builds 500 statically by default with no pages/500 and custom _error', async () => { + await fs.rename(pages500, `${pages500}.bak`) + await fs.writeFile( + pagesError, + ` + function Error({ statusCode }) { + return

Error status: {statusCode}

+ } + Error.getInitialProps = ({ res, err }) => { + return { + statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404 + } + } + export default Error + ` + ) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + await fs.rename(`${pages500}.bak`, pages500) + await fs.remove(pagesError) + console.log(stderr) + expect(stderr).not.toMatch(gip500Err) + expect(code).toBe(0) + expect( + await fs.pathExists(join(appDir, '.next/server/pages/500.html')) + ).toBe(true) + }) + it('shows error with getInitialProps in pages/500 build', async () => { await fs.move(pages500, `${pages500}.bak`) await fs.writeFile( From a725760782e79ec5a16982cba08fd2e73a9ea078 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Feb 2021 12:11:04 -0600 Subject: [PATCH 5/9] Handle i18n case --- packages/next/build/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index bebc6680ebebb..7250b297109ec 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -1001,6 +1001,7 @@ export default async function build( ...staticPages, ...ssgPages, ...(useStatic404 ? ['/404'] : []), + ...(!hasPages500 ? ['/500'] : []), ]) { const isSsg = ssgPages.has(page) const isDynamic = isDynamicRoute(page) From e3a5870cfd1116b45d39348caa84a2cf28cd2769 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Feb 2021 12:13:34 -0600 Subject: [PATCH 6/9] remove log --- packages/next/build/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 7250b297109ec..4bd05da268ed4 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -1028,7 +1028,6 @@ export default async function build( } } } - console.error(defaultMap) return defaultMap }, trailingSlash: false, From c4e928b2e10610da1da026732dc3b66388811978 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 15 Feb 2021 12:46:51 -0600 Subject: [PATCH 7/9] update tests --- test/integration/error-in-error/pages/_error.js | 2 +- test/integration/polyfills/test/index.test.js | 6 +++--- test/integration/production/test/index.test.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/integration/error-in-error/pages/_error.js b/test/integration/error-in-error/pages/_error.js index 4c8c07002213f..825ef429db067 100644 --- a/test/integration/error-in-error/pages/_error.js +++ b/test/integration/error-in-error/pages/_error.js @@ -2,7 +2,7 @@ import React from 'react' class Error extends React.Component { static async getInitialProps({ req, res, err }) { - if (req.url !== '/404.html') { + if (!req.url.startsWith('/404') && !req.url.startsWith('/500')) { await Promise.reject(new Error('an error in error')) } const statusCode = res ? res.statusCode : err ? err.statusCode : null diff --git a/test/integration/polyfills/test/index.test.js b/test/integration/polyfills/test/index.test.js index 1a0b62a3973df..cbcaf257c4ef4 100644 --- a/test/integration/polyfills/test/index.test.js +++ b/test/integration/polyfills/test/index.test.js @@ -39,9 +39,9 @@ describe('Polyfills', () => { }) it('should contain generated page count in output', async () => { - expect(output).toContain('Generating static pages (0/3)') - expect(output).toContain('Generating static pages (3/3)') + expect(output).toContain('Generating static pages (0/4)') + expect(output).toContain('Generating static pages (4/4)') // we should only have 1 segment and the initial message logged out - expect(output.match(/Generating static pages/g).length).toBe(2) + expect(output.match(/Generating static pages/g).length).toBe(5) }) }) diff --git a/test/integration/production/test/index.test.js b/test/integration/production/test/index.test.js index 186e478c3fbb4..90ca62b5e7299 100644 --- a/test/integration/production/test/index.test.js +++ b/test/integration/production/test/index.test.js @@ -58,8 +58,8 @@ describe('Production Usage', () => { afterAll(() => stopApp(server)) it('should contain generated page count in output', async () => { - expect(output).toContain('Generating static pages (0/36)') - expect(output).toContain('Generating static pages (36/36)') + expect(output).toContain('Generating static pages (0/37)') + expect(output).toContain('Generating static pages (37/37)') // we should only have 4 segments and the initial message logged out expect(output.match(/Generating static pages/g).length).toBe(5) }) From ccdac7f30e3fa8321952c56968dde43b9d4ea633 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Fri, 19 Feb 2021 15:41:18 -0600 Subject: [PATCH 8/9] dont use static status page when page isnt present --- packages/next/build/index.ts | 14 ++++++- test/integration/500-page/pages/500.js | 5 ++- test/integration/500-page/test/index.test.js | 41 ++++++++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index c5e6e7ac663b8..9d72f526eb953 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -1094,8 +1094,20 @@ export default async function build( relativeDest ) - if (!isSsg) { + if ( + !isSsg && + !( + // don't add static status page to manifest if it's + // the default generated version e.g. no pages/500 + ( + STATIC_STATUS_PAGES.includes(page) && + !usedStaticStatusPages.includes(page) + ) + ) + ) { pagesManifest[page] = relativeDest + } else { + console.log('not adding', page) } const isNotFound = ssgNotFoundPaths.includes(page) diff --git a/test/integration/500-page/pages/500.js b/test/integration/500-page/pages/500.js index 36344491336b4..aef5a27d3360e 100644 --- a/test/integration/500-page/pages/500.js +++ b/test/integration/500-page/pages/500.js @@ -1,2 +1,5 @@ -const page = () => 'custom 500 page' +const page = () => { + console.log('rendered 500') + return 'custom 500 page' +} export default page diff --git a/test/integration/500-page/test/index.test.js b/test/integration/500-page/test/index.test.js index bd491fd453578..7dc3984d0c280 100644 --- a/test/integration/500-page/test/index.test.js +++ b/test/integration/500-page/test/index.test.js @@ -115,14 +115,31 @@ describe('500 Page Support', () => { export default page ` ) - const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + const { stderr, stdout: buildStdout, code } = await nextBuild(appDir, [], { + stderr: true, + stdout: true, + }) await fs.remove(pagesApp) expect(stderr).not.toMatch(gip500Err) + expect(buildStdout).toContain('rendered 500') expect(code).toBe(0) expect( await fs.pathExists(join(appDir, '.next/server/pages/500.html')) ).toBe(true) + + let appStdout = '' + const appPort = await findPort() + const app = await nextStart(appDir, appPort, { + onStderr(msg) { + appStdout += msg || '' + }, + }) + + await renderViaHTTP(appPort, '/err') + await killApp(app) + + expect(appStdout).not.toContain('rendered 500') }) it('builds 500 statically by default with no pages/500', async () => { @@ -146,6 +163,7 @@ describe('500 Page Support', () => { return

Error status: {statusCode}

} Error.getInitialProps = ({ res, err }) => { + console.error('called _error.getInitialProps') return { statusCode: res && res.statusCode ? res.statusCode : err ? err.statusCode : 404 } @@ -153,15 +171,30 @@ describe('500 Page Support', () => { export default Error ` ) - const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + const { stderr: buildStderr, code } = await nextBuild(appDir, [], { + stderr: true, + }) await fs.rename(`${pages500}.bak`, pages500) await fs.remove(pagesError) - console.log(stderr) - expect(stderr).not.toMatch(gip500Err) + console.log(buildStderr) + expect(buildStderr).not.toMatch(gip500Err) expect(code).toBe(0) expect( await fs.pathExists(join(appDir, '.next/server/pages/500.html')) ).toBe(true) + + let appStderr = '' + const appPort = await findPort() + const app = await nextStart(appDir, appPort, { + onStderr(msg) { + appStderr += msg || '' + }, + }) + + await renderViaHTTP(appPort, '/err') + await killApp(app) + + expect(appStderr).toContain('called _error.getInitialProps') }) it('shows error with getInitialProps in pages/500 build', async () => { From 9256f18b051e0e407640b90a338452485b53ba31 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 22 Feb 2021 10:03:21 -0600 Subject: [PATCH 9/9] remove log --- packages/next/build/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 9d72f526eb953..a0dc62462b8ac 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -1106,8 +1106,6 @@ export default async function build( ) ) { pagesManifest[page] = relativeDest - } else { - console.log('not adding', page) } const isNotFound = ssgNotFoundPaths.includes(page)