Next.jsを実践投入するには、エラーハンドリングまわりも考えないといけません。関心事は、サイト独自のエラーページデザインであったり、Sentryなどでのエラー通知だと思います。
まずはNext.jsが用意しているエラーページを見ていきましょう。
本記事は公式ドキュメントのエラーハンドリングの項目を一部翻訳しています。
https://github.com/zeit/next.js#custom-error-handling
エラーハンドリングのカスタマイズ
Next.jsではデフォルトのエラーハンドリングページが用意されています。ステータスコードが404もしくは500のエラーをキャッチし、クライアントとサーバーサイドの両方でハンドリングされます。
もしデフォルトのエラーコンポーネントをオーバーライドしたいのなら、_error.js
というファイルをpagesフォルダーの中に定義しましょう:
import React from 'react'
export default class Error extends React.Component {
static getInitialProps({ res, err }) {
const statusCode = res ? res.statusCode : err ? err.statusCode : null;
return { statusCode }
}
render() {
return (
<p>
{`${this.props.statusCode}エラーが起こりました。`}
</p>
)
}
}
エラーハンドリングは、独自の_error
コンポーネントをインポートして行います。
import React from 'react'
import Error from './_error'
import fetch from 'isomorphic-unfetch'
export default class Page extends React.Component {
static async getInitialProps() {
const res = await fetch('https://api.github.com/repos/zeit/next.js')
const statusCode = res.statusCode > 200 ? res.statusCode : false
const json = await res.json()
return { statusCode, stars: json.stargazers_count }
}
render() {
if (this.props.statusCode) {
return <Error statusCode={this.props.statusCode} />
}
return (
<div>
Next stars: {this.props.stars}
</div>
)
}
}
ここで少し注意したいのは、_error.js
のgetInitialProps()
はサーバから直接レンダリングされた場合のみ機能します。例えば存在しないページhttp://localhost:3000/hogefugaにアクセスしたときはgetInitialProps()
を通過し、statusCodeを取得して404ページがレンダリングされます。逆に、pages/index.js
の例のように、ページの処理途中にエラーが起こった場合は_error.js
は普通のReactコンポーネントと同じ扱いで呼び出されるため、getInitialProps()
は実行されません。
また、Next.jsが用意するエラーページを再利用したい場合は'./_error'
ではなく'next/error'
から同様にインポートしましょう。
Sentryの導入
前日の_app.jsについての記事でも触れましたが、<App>
コンポーネントは全てのページにおいて、初期化時に利用されます。
ですので、このコンポーネントのライフサイクルcomponentDidCatch
でエラーハンドリングを行えば、全エラーの捕捉が可能です。
公式の例より
import App from 'next/app'
import * as Sentry from '@sentry/browser'
const SENTRY_PUBLIC_DSN = ''
export default class MyApp extends App {
constructor (...args) {
super(...args)
Sentry.init({dsn: SENTRY_PUBLIC_DSN})
}
componentDidCatch (error, errorInfo) {
Sentry.configureScope(scope => {
Object.keys(errorInfo).forEach(key => {
scope.setExtra(key, errorInfo[key])
})
})
Sentry.captureException(error)
// This is needed to render errors correctly in development / production
super.componentDidCatch(error, errorInfo)
}
}
しかし、上記例の場合だと実はサーバーサイドのエラーもクライアントと同じように拾ってしまいます。'@sentry/browser'
はサーバーサイドでも一応動くようです。
一旦入れる場合にはよいかもしれませんが、サーバーとクライアントを区別してエラーハンドリングを行う方法を、まさにIssueで議論が行われています。注視していきたいです。
Improve with-sentry example #5727