Skip to content

Commit 0bdd321

Browse files
arunodarauchg
authored andcommitted
Introduce better debug error handling (vercel#1590)
With this we are rendering runtime and debug errors inside a it's own error root. That gives us better error handling and control. Also, now we are patching React core to capture runtime errors.
1 parent b176f33 commit 0bdd321

File tree

4 files changed

+58
-20
lines changed

4 files changed

+58
-20
lines changed

client/index.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createRouter } from '../lib/router'
66
import App from '../lib/app'
77
import evalScript from '../lib/eval-script'
88
import { loadGetInitialProps, getURL } from '../lib/utils'
9+
import ErrorDebugComponent from '../lib/error-debug'
910

1011
// Polyfill Promise globally
1112
// This is needed because Webpack2's dynamic loading(common chunks) code
@@ -39,33 +40,57 @@ export const router = createRouter(pathname, query, getURL(), {
3940
})
4041

4142
const headManager = new HeadManager()
42-
const container = document.getElementById('__next')
43+
const appContainer = document.getElementById('__next')
44+
const errorContainer = document.getElementById('__next-error')
4345

44-
export default (onError) => {
46+
export default () => {
4547
const emitter = mitt()
4648

4749
router.subscribe(({ Component, props, hash, err }) => {
48-
render({ Component, props, err, hash, emitter }, onError)
50+
render({ Component, props, err, hash, emitter })
4951
})
5052

5153
const hash = location.hash.substring(1)
52-
render({ Component, props, hash, err, emitter }, onError)
54+
render({ Component, props, hash, err, emitter })
5355

5456
return emitter
5557
}
5658

57-
export async function render (props, onError = renderErrorComponent) {
59+
export async function render (props) {
60+
if (props.err) {
61+
await renderError(props.err)
62+
return
63+
}
64+
5865
try {
5966
await doRender(props)
6067
} catch (err) {
61-
await onError(err)
68+
if (err.abort) return
69+
await renderError(err)
6270
}
6371
}
6472

65-
async function renderErrorComponent (err) {
66-
const { pathname, query } = router
67-
const props = await loadGetInitialProps(ErrorComponent, { err, pathname, query })
68-
await doRender({ Component: ErrorComponent, props, err })
73+
// This method handles all runtime and debug errors.
74+
// 404 and 500 errors are special kind of errors
75+
// and they are still handle via the main render method.
76+
export async function renderError (error) {
77+
const prod = process.env.NODE_ENV === 'production'
78+
// We need to unmount the current app component because it's
79+
// in the inconsistant state.
80+
// Otherwise, we need to face issues when the issue is fixed and
81+
// it's get notified via HMR
82+
ReactDOM.unmountComponentAtNode(appContainer)
83+
84+
const errorMessage = `${error.message}\n${error.stack}`
85+
console.error(errorMessage)
86+
87+
if (prod) {
88+
const initProps = { err: error, pathname, query }
89+
const props = await loadGetInitialProps(ErrorComponent, initProps)
90+
ReactDOM.render(createElement(ErrorComponent, props), errorContainer)
91+
} else {
92+
ReactDOM.render(createElement(ErrorDebugComponent, { error }), errorContainer)
93+
}
6994
}
7095

7196
async function doRender ({ Component, props, hash, err, emitter }) {
@@ -88,7 +113,9 @@ async function doRender ({ Component, props, hash, err, emitter }) {
88113
// lastAppProps has to be set before ReactDom.render to account for ReactDom throwing an error.
89114
lastAppProps = appProps
90115

91-
ReactDOM.render(createElement(App, appProps), container)
116+
// We need to clear any existing runtime error messages
117+
ReactDOM.unmountComponentAtNode(errorContainer)
118+
ReactDOM.render(createElement(App, appProps), appContainer)
92119

93120
if (emitter) {
94121
emitter.emit('after-reactdom-render', { Component })

client/next-dev.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import evalScript from '../lib/eval-script'
2+
import ReactReconciler from 'react-dom/lib/ReactReconciler'
23

34
const { __NEXT_DATA__: { errorComponent } } = window
45
const ErrorComponent = evalScript(errorComponent).default
@@ -7,12 +8,18 @@ require('react-hot-loader/patch')
78

89
const next = window.next = require('./')
910

10-
const emitter = next.default(onError)
11-
12-
function onError (err) {
13-
// just show the debug screen but don't render ErrorComponent
14-
// so that the current component doesn't lose props
15-
next.render({ err, emitter })
11+
const emitter = next.default()
12+
13+
// This is a patch to catch most of the errors throw inside React components.
14+
const originalMountComponent = ReactReconciler.mountComponent
15+
ReactReconciler.mountComponent = function (...args) {
16+
try {
17+
return originalMountComponent(...args)
18+
} catch (err) {
19+
next.renderError(err)
20+
err.abort = true
21+
throw err
22+
}
1623
}
1724

1825
let lastScroll

lib/app.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default class App extends Component {
1717
}
1818

1919
render () {
20-
const { Component, props, hash, err, router } = this.props
20+
const { Component, props, hash, router } = this.props
2121
const url = createUrl(router)
2222
// If there no component exported we can't proceed.
2323
// We'll tackle that here.
@@ -28,7 +28,6 @@ export default class App extends Component {
2828

2929
return <div>
3030
<Container {...containerProps} />
31-
{ErrorDebug && err ? <ErrorDebug error={err} /> : null}
3231
</div>
3332
}
3433
}

server/document.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,12 @@ export class Main extends Component {
5050

5151
render () {
5252
const { html } = this.context._documentProps
53-
return <div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
53+
return (
54+
<div>
55+
<div id='__next' dangerouslySetInnerHTML={{ __html: html }} />
56+
<div id='__next-error' />
57+
</div>
58+
)
5459
}
5560
}
5661

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy