Skip to content

Commit c24cf45

Browse files
committed
use next _error, add int test
1 parent 90d43e5 commit c24cf45

File tree

4 files changed

+89
-70
lines changed

4 files changed

+89
-70
lines changed

packages/next/client/index.tsx

Lines changed: 30 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -443,48 +443,6 @@ export async function render(renderingProps: RenderRouteInfo): Promise<void> {
443443
// `componentDidCatch`. If there's one, fallback to the simple fallback error
444444
let renderErrorResolving = false
445445
// FallbackError component shows when unexpected error occurs on custom _error page
446-
class FallbackError<P = {}> extends React.Component<P & { err: Error }> {
447-
static getInitialProps = ({ err }: { err: Error }) => ({ err })
448-
render() {
449-
const { err } = this.props
450-
return (
451-
<div style={fallbackErrorStyles.error}>
452-
<style dangerouslySetInnerHTML={{ __html: 'body { margin: 0 }' }} />
453-
<h2>
454-
{err.name}
455-
{`: `}
456-
<small>{err.message}</small>
457-
</h2>
458-
<h4>
459-
Application error: a client-side exception has occurred (
460-
<a href="https://nextjs.org/docs/messages/client-side-exception-occurred">
461-
developer guidance
462-
</a>
463-
)
464-
</h4>
465-
<pre style={fallbackErrorStyles.stack}>{err.stack}</pre>
466-
</div>
467-
)
468-
}
469-
}
470-
471-
const fallbackErrorStyles: { [k: string]: React.CSSProperties } = {
472-
error: {
473-
color: '#000',
474-
background: '#fff',
475-
fontFamily:
476-
'-apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", "Fira Sans", Avenir, "Helvetica Neue", "Lucida Grande", sans-serif',
477-
height: '100vh',
478-
textAlign: 'center',
479-
display: 'flex',
480-
flexDirection: 'column',
481-
alignItems: 'center',
482-
justifyContent: 'center',
483-
},
484-
stack: {
485-
textAlign: 'left',
486-
},
487-
}
488446

489447
// This method handles all runtime and debug errors.
490448
// 404 and 500 errors are special kind of errors
@@ -512,38 +470,40 @@ export function renderError(renderErrorProps: RenderErrorProps): Promise<any> {
512470
// Make sure we log the error to the console, otherwise users can't track down issues.
513471
console.error(err)
514472
const errorComponentPromise = renderErrorResolving
515-
? Promise.resolve({ page: FallbackError, styleSheets: [] })
473+
? import('../pages/_error').then((m) => ({
474+
page: m.default,
475+
styleSheets: [],
476+
}))
516477
: pageLoader.loadPage('/_error')
517478

518479
renderErrorResolving = true
519-
return errorComponentPromise
520-
.then(({ page: ErrorComponent, styleSheets }) => {
521-
// In production we do a normal render with the `ErrorComponent` as component.
522-
// If we've gotten here upon initial render, we can use the props from the server.
523-
// Otherwise, we need to call `getInitialProps` on `App` before mounting.
524-
const AppTree = wrapApp(App)
525-
const appCtx = {
480+
return errorComponentPromise.then(({ page: ErrorComponent, styleSheets }) => {
481+
// In production we do a normal render with the `ErrorComponent` as component.
482+
// If we've gotten here upon initial render, we can use the props from the server.
483+
// Otherwise, we need to call `getInitialProps` on `App` before mounting.
484+
const AppTree = wrapApp(App)
485+
const appCtx = {
486+
Component: ErrorComponent,
487+
AppTree,
488+
router,
489+
ctx: { err, pathname: page, query, asPath, AppTree },
490+
}
491+
return Promise.resolve(
492+
renderErrorProps.props
493+
? renderErrorProps.props
494+
: loadGetInitialProps(App, appCtx)
495+
).then((initProps) =>
496+
doRender({
497+
...renderErrorProps,
498+
err,
526499
Component: ErrorComponent,
527-
AppTree,
528-
router,
529-
ctx: { err, pathname: page, query, asPath, AppTree },
530-
}
531-
return Promise.resolve(
532-
renderErrorProps.props
533-
? renderErrorProps.props
534-
: loadGetInitialProps(App, appCtx)
535-
).then((initProps) =>
536-
doRender({
537-
...renderErrorProps,
538-
err,
539-
Component: ErrorComponent,
540-
styleSheets,
541-
props: initProps,
542-
}).then(() => {
543-
renderErrorResolving = false
544-
})
545-
)
546-
})
500+
styleSheets,
501+
props: initProps,
502+
}).then(() => {
503+
renderErrorResolving = false
504+
})
505+
)
506+
})
547507
}
548508

549509
let reactRoot: any = null
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/* eslint-disable no-unused-expressions, no-undef */
2+
let renderCount = 0
3+
4+
export default function Error() {
5+
renderCount++
6+
7+
// Guard to avoid endless loop crashing the browser tab.
8+
if (typeof window !== 'undefined' && renderCount < 3) {
9+
throw new Error('crash')
10+
}
11+
return `error threw ${renderCount} times`
12+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* eslint-disable no-unused-expressions, no-unused-vars */
2+
import React from 'react'
3+
import Link from 'next/link'
4+
5+
function page() {
6+
return (
7+
<Link href="/">
8+
<a id="nav">Client side nav</a>
9+
</Link>
10+
)
11+
}
12+
13+
page.getInitialProps = () => {
14+
if (typeof window !== 'undefined') {
15+
throw new Error('Oops from Home')
16+
}
17+
return {}
18+
}
19+
20+
export default page
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* eslint-env jest */
2+
3+
import { join } from 'path'
4+
import webdriver from 'next-webdriver'
5+
import { nextBuild, nextStart, findPort, killApp } from 'next-test-utils'
6+
7+
jest.setTimeout(1000 * 60 * 1)
8+
9+
const appDir = join(__dirname, '..')
10+
const navSel = '#nav'
11+
const errorMessage = 'Application error: a client-side exception has occurred'
12+
13+
describe('Custom error page exception', () => {
14+
it('should display captured error when it occurs on client rendering', async () => {
15+
let { stderr, code } = await nextBuild(appDir, [], { stderr: true })
16+
console.error(stderr)
17+
const appPort = await findPort()
18+
const app = await nextStart(appDir, appPort, { stderr: true })
19+
const browser = await webdriver(appPort, '/')
20+
await browser.waitForElementByCss(navSel).elementByCss(navSel).click()
21+
const text = await (await browser.elementByCss('#__next')).text()
22+
killApp(app)
23+
24+
expect(code).toBe(0)
25+
expect(text).toMatch(errorMessage)
26+
})
27+
})

0 commit comments

Comments
 (0)