diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index 7d0325545fe06..78964da74458e 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -184,7 +184,7 @@ export default class Server { this.nextConfig = loadConfig(phase, this.dir, conf) this.distDir = join(this.dir, this.nextConfig.distDir) this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH) - this.hasStaticDir = fs.existsSync(join(this.dir, 'static')) + this.hasStaticDir = !minimalMode && fs.existsSync(join(this.dir, 'static')) // Only serverRuntimeConfig needs the default // publicRuntimeConfig gets it's default in client/index.js @@ -567,6 +567,9 @@ export default class Server { try { return await this.run(req, res, parsedUrl) } catch (err) { + if (this.minimalMode) { + throw err + } this.logError(err) res.statusCode = 500 res.end('Internal Server Error') @@ -1835,14 +1838,19 @@ export default class Server { } } } catch (err) { - this.logError(err) - if (err && err.code === 'DECODE_FAILED') { + this.logError(err) res.statusCode = 400 return await this.renderErrorToHTML(err, req, res, pathname, query) } res.statusCode = 500 - return await this.renderErrorToHTML(err, req, res, pathname, query) + const html = await this.renderErrorToHTML(err, req, res, pathname, query) + + if (this.minimalMode) { + throw err + } + this.logError(err) + return html } res.statusCode = 404 return await this.renderErrorToHTML(null, req, res, pathname, query) @@ -1863,6 +1871,10 @@ export default class Server { ) } const html = await this.renderErrorToHTML(err, req, res, pathname, query) + + if (this.minimalMode && res.statusCode === 500) { + throw err + } if (html === null) { return } @@ -2007,9 +2019,11 @@ export default class Server { } let nextFilesStatic: string[] = [] - nextFilesStatic = recursiveReadDirSync( - join(this.distDir, 'static') - ).map((f) => join('.', relative(this.dir, this.distDir), 'static', f)) + nextFilesStatic = !this.minimalMode + ? recursiveReadDirSync(join(this.distDir, 'static')).map((f) => + join('.', relative(this.dir, this.distDir), 'static', f) + ) + : [] return (this._validFilesystemPathSet = new Set([ ...nextFilesStatic, diff --git a/test/integration/required-server-files/pages/errors/gip.js b/test/integration/required-server-files/pages/errors/gip.js new file mode 100644 index 0000000000000..c673c7de83b48 --- /dev/null +++ b/test/integration/required-server-files/pages/errors/gip.js @@ -0,0 +1,14 @@ +function Page(props) { + return

here comes an error

+} + +Page.getInitialProps = ({ query }) => { + if (query.crash) { + throw new Error('gip hit an oops') + } + return { + hello: 'world', + } +} + +export default Page diff --git a/test/integration/required-server-files/pages/errors/gsp/[post].js b/test/integration/required-server-files/pages/errors/gsp/[post].js new file mode 100644 index 0000000000000..b696ff821aa7b --- /dev/null +++ b/test/integration/required-server-files/pages/errors/gsp/[post].js @@ -0,0 +1,28 @@ +import { useRouter } from 'next/router' + +function Page(props) { + if (useRouter().isFallback) { + return

loading...

+ } + return

here comes an error

+} + +export const getStaticPaths = () => { + return { + paths: [], + fallback: true, + } +} + +export const getStaticProps = ({ params }) => { + if (params.post === 'crash') { + throw new Error('gsp hit an oops') + } + return { + props: { + hello: 'world', + }, + } +} + +export default Page diff --git a/test/integration/required-server-files/pages/errors/gssp.js b/test/integration/required-server-files/pages/errors/gssp.js new file mode 100644 index 0000000000000..4e1adfa9575fd --- /dev/null +++ b/test/integration/required-server-files/pages/errors/gssp.js @@ -0,0 +1,16 @@ +function Page(props) { + return

here comes an error

+} + +export const getServerSideProps = ({ query }) => { + if (query.crash) { + throw new Error('gssp hit an oops') + } + return { + props: { + hello: 'world', + }, + } +} + +export default Page diff --git a/test/integration/required-server-files/test/index.test.js b/test/integration/required-server-files/test/index.test.js index 97a1fc7e12d8f..8ae560b690081 100644 --- a/test/integration/required-server-files/test/index.test.js +++ b/test/integration/required-server-files/test/index.test.js @@ -20,6 +20,7 @@ let nextApp let appPort let buildId let requiredFilesManifest +let errors = [] describe('Required Server Files', () => { beforeAll(async () => { @@ -58,7 +59,8 @@ describe('Required Server Files', () => { try { await nextApp.getRequestHandler()(req, res) } catch (err) { - console.error(err) + console.error('top-level', err) + errors.push(err) res.statusCode = 500 res.end('error') } @@ -419,4 +421,31 @@ describe('Required Server Files', () => { path: ['hello', 'world'], }) }) + + it('should bubble error correctly for gip page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/errors/gip', { crash: '1' }) + expect(res.status).toBe(500) + expect(await res.text()).toBe('error') + expect(errors.length).toBe(1) + expect(errors[0].message).toContain('gip hit an oops') + }) + + it('should bubble error correctly for gssp page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/errors/gssp', { crash: '1' }) + expect(res.status).toBe(500) + expect(await res.text()).toBe('error') + expect(errors.length).toBe(1) + expect(errors[0].message).toContain('gssp hit an oops') + }) + + it('should bubble error correctly for gsp page', async () => { + errors = [] + const res = await fetchViaHTTP(appPort, '/errors/gsp/crash') + expect(res.status).toBe(500) + expect(await res.text()).toBe('error') + expect(errors.length).toBe(1) + expect(errors[0].message).toContain('gsp hit an oops') + }) })