Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes 404 no longer returned when error with code ENOENT is thrown. #11480

Closed
wants to merge 8 commits into from
16 changes: 16 additions & 0 deletions errors/getinitialprops-enoent.md
@@ -0,0 +1,16 @@
# ENOENT from getInitialProps

#### Why This Error Occurred

In one of your pages you threw an error with the code `ENOENT`, this was previously an internal feature used to trigger rendering the 404 page and should not be relied on.

#### Possible Ways to Fix It

In `getServerSideProps` or `getInitialProps` you can set the status code with the ServerResponse (`res`) object provided e.g. `res.statusCode = 404`.

In `getStaticProps` you can handle a 404 by rendering the 404 state on the current page instead of throwing and trying to render a separate page.

### Useful Links

- [Google 404 Error Guide](https://developers.google.com/search/docs/guides/fix-search-javascript)
ijjk marked this conversation as resolved.
Show resolved Hide resolved
- [404 Page Documentation](https://nextjs.org/docs/advanced-features/custom-error-page#404-page)
11 changes: 8 additions & 3 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -1245,9 +1245,14 @@ export default class Server {
}
}
} catch (err) {
this.logError(err)
res.statusCode = 500
return await this.renderErrorToHTML(err, req, res, pathname, query)
if (err && err.code === 'ENOENT') {
res.statusCode = 404
return await this.renderErrorToHTML(null, req, res, pathname, query)
} else {
this.logError(err)
res.statusCode = 500
return await this.renderErrorToHTML(err, req, res, pathname, query)
}
}

res.statusCode = 404
Expand Down
10 changes: 9 additions & 1 deletion packages/next/next-server/server/render.tsx
Expand Up @@ -42,6 +42,7 @@ import { LoadComponentsReturnType, ManifestItem } from './load-components'
import optimizeAmp from './optimize-amp'
import { UnwrapPromise } from '../../lib/coalesced-function'
import { GetStaticProps, GetServerSideProps } from '../../types'
import chalk from 'next/dist/compiled/chalk'

function noRouter() {
const message =
Expand Down Expand Up @@ -637,7 +638,14 @@ export async function renderToHTML(
;(renderOpts as any).pageData = props
}
} catch (err) {
if (isDataReq || !dev || !err) throw err
if (process.env.NODE_ENV !== 'production' && err?.code === 'ENOENT') {
console.warn(
chalk.yellow('Warning:') +
` page "${pathname}" threw an error with a code of ENOENT. This was an internal feature to render the 404 page and should not be relied on.\nSee more info here: https://err.sh/next.js/getinitialprops-enoent`
)
}

if (isDataReq || !dev || !err || err.code === 'ENOENT') throw err
ctx.err = err
renderOpts.err = err
console.error(err)
Expand Down
9 changes: 9 additions & 0 deletions test/integration/custom-error/pages/throw-404.js
@@ -0,0 +1,9 @@
const Page = () => 'hi'

Page.getInitialProps = () => {
const error = new Error('to 404 we go')
error.code = 'ENOENT'
throw error
}

export default Page
23 changes: 19 additions & 4 deletions test/integration/custom-error/test/index.test.js
Expand Up @@ -14,23 +14,36 @@ import {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
const appDir = join(__dirname, '..')
const nextConfig = join(appDir, 'next.config.js')

let stderr = ''
let appPort
let app

const runTests = () => {
const runTests = isDev => {
it('renders custom _error successfully', async () => {
const html = await renderViaHTTP(appPort, '/')
expect(html).toMatch(/Custom error/)
expect(html).toMatch(isDev ? /oof/ : /Custom error/)
})

it('renders 404 page when ENOENT error is thrown', async () => {
const html = await renderViaHTTP(appPort, '/throw-404')
expect(html).toContain('Custom error')
expect(html).not.toContain('to 404 we go')

if (isDev) {
expect(stderr).toContain(
'page "/throw-404" threw an error with a code of ENOENT. This was an internal feature to render the 404 page and should not be relied on'
)
}
})
}

const customErrNo404Match = /You have added a custom \/_error page without a custom \/404 page/

describe('Custom _error', () => {
describe('dev mode', () => {
let stderr = ''

beforeAll(async () => {
stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr(msg) {
Expand All @@ -56,6 +69,8 @@ describe('Custom _error', () => {
expect(html).toContain('An error 404 occurred on server')
expect(stderr).toMatch(customErrNo404Match)
})

runTests(true)
})

describe('production mode', () => {
Expand Down
14 changes: 14 additions & 0 deletions test/integration/prerender/pages/enoent.js
@@ -0,0 +1,14 @@
export async function getStaticProps() {
if (process.env.NODE_ENV === 'development') {
const error = new Error('oof')
error.code = 'ENOENT'
throw error
}
return {
props: {
hi: 'hi',
},
}
}

export default () => 'hi'
17 changes: 17 additions & 0 deletions test/integration/prerender/test/index.test.js
Expand Up @@ -129,6 +129,11 @@ const expectedManifestRoutes = () => ({
initialRevalidateSeconds: false,
srcRoute: null,
},
'/enoent': {
dataRoute: `/_next/data/${buildId}/enoent.json`,
initialRevalidateSeconds: false,
srcRoute: null,
},
'/something': {
dataRoute: `/_next/data/${buildId}/something.json`,
initialRevalidateSeconds: false,
Expand Down Expand Up @@ -505,6 +510,12 @@ const runTests = (dev = false, looseMode = false) => {
// )
// })

it('should handle throw ENOENT correctly', async () => {
const res = await fetchViaHTTP(appPort, '/enoent')
const html = await res.text()
expect(html).toContain('oof')
})

it('should not show warning from url prop being returned', async () => {
const urlPropPage = join(appDir, 'pages/url-prop.js')
await fs.writeFile(
Expand Down Expand Up @@ -810,6 +821,12 @@ const runTests = (dev = false, looseMode = false) => {
),
page: '/default-revalidate',
},
{
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(buildId)}\\/enoent.json$`
),
page: '/enoent',
},
{
dataRouteRegex: normalizeRegEx(
`^\\/_next\\/data\\/${escapeRegex(
Expand Down