Edited at
Next.jsDay 5

Next.jsのエラーハンドリング

Next.jsを実践投入するには、エラーハンドリングまわりも考えないといけません。関心事は、サイト独自のエラーページデザインであったり、Sentryなどでのエラー通知だと思います。

まずはNext.jsが用意しているエラーページを見ていきましょう。

本記事は公式ドキュメントのエラーハンドリングの項目を一部翻訳しています。

https://github.com/zeit/next.js#custom-error-handling


エラーハンドリングのカスタマイズ

Next.jsではデフォルトのエラーハンドリングページが用意されています。ステータスコードが404もしくは500のエラーをキャッチし、クライアントとサーバーサイドの両方でハンドリングされます。

もしデフォルトのエラーコンポーネントをオーバーライドしたいのなら、_error.jsというファイルをpagesフォルダーの中に定義しましょう:


pages/_error.js

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コンポーネントをインポートして行います。


pages/index.js

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.jsgetInitialProps()はサーバから直接レンダリングされた場合のみ機能します。例えば存在しないページhttp://localhost:3000/hogefugaにアクセスしたときはgetInitialProps()を通過し、statusCodeを取得して404ページがレンダリングされます。逆に、pages/index.jsの例のように、ページの処理途中にエラーが起こった場合は_error.jsは普通のReactコンポーネントと同じ扱いで呼び出されるため、getInitialProps()は実行されません。

また、Next.jsが用意するエラーページを再利用したい場合は'./_error'ではなく'next/error'から同様にインポートしましょう。


Sentryの導入

前日の_app.jsについての記事でも触れましたが、<App>コンポーネントは全てのページにおいて、初期化時に利用されます。

ですので、このコンポーネントのライフサイクルcomponentDidCatchでエラーハンドリングを行えば、全エラーの捕捉が可能です。

公式の例より


pages/_app.js

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