@honeybadger-io/nextjs 5.5.0 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,13 +31,17 @@ This version is considered suitable for preview.
31
31
 
32
32
  The following limitations are known to exist and will be tackled in future releases:
33
33
 
34
- - [Issue link](https://github.com/honeybadger-io/honeybadger-js/issues/1055): A custom `_error.js` component is used to report uncaught exceptions to Honeybadger.
34
+ - [Issue link](https://github.com/honeybadger-io/honeybadger-js/issues/1055): A custom error component is used to report uncaught exceptions to Honeybadger.
35
35
  This is necessary because Next.js does not provide a way to hook into the error handler.
36
- This is not a catch-all errors solution. There are some caveats to this approach, as reported [here](https://nextjs.org/docs/advanced-features/custom-error-page#caveats).
36
+ This is not a catch-all errors solution.
37
+ If you are using the _Pages Router_, there are some caveats to this approach, as reported [here](https://nextjs.org/docs/advanced-features/custom-error-page#caveats).
37
38
  This is a limitation of Next.js, not Honeybadger's Next.js integration.
38
- Errors thrown in middlewares or API routes will not be reported to Honeybadger, since when they reach _error.js, the response status code is 404 and no error information is available.
39
- Additionally, there is an open [issue](https://github.com/vercel/next.js/issues/45535) about 404 being reported with Next.js apps deployed on Vercel, when they should be reported as 500.
40
- - [Issue link](https://github.com/honeybadger-io/honeybadger-js/issues/1056):Source maps for the [Edge runtime](https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime) are not supported yet.
39
+ Errors thrown in middlewares or API routes will not be reported to Honeybadger, since when they reach the error component, the response status code is 404 and no error information is available.
40
+ Additionally, there is an open [issue](https://github.com/vercel/next.js/issues/45535) about 404 being reported with Next.js apps deployed on Vercel, when they should be reported as 500.
41
+ If you are using the _App Router_, these limitations do not apply, because errors thrown in middlewares or API routes do not reach the custom error component
42
+ but are caught by the global `window.onerror` handler. However, some other server errors (i.e. from data fetching methods) will be reported with minimal information,
43
+ since Next.js will send a [generic error message](https://nextjs.org/docs/app/building-your-application/routing/error-handling#handling-server-errors) to this component for better security.
44
+ - [Issue link](https://github.com/honeybadger-io/honeybadger-js/issues/1056): Source maps for the [Edge runtime](https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime) are not supported yet.
41
45
 
42
46
  ## Example app
43
47
 
@@ -2,5 +2,12 @@
2
2
  declare const path: any;
3
3
  declare const fs: any;
4
4
  declare const debug: boolean;
5
- declare function copyErrorJs(): Promise<any>;
5
+ declare function usesTypescript(): any;
6
+ declare function usesPagesRouter(): any;
7
+ declare function usesAppRouter(): any;
8
+ declare function getTargetPath(isAppRouter?: boolean, isGlobalErrorComponent?: boolean): any;
9
+ declare function getTemplate(isAppRouter?: boolean, isGlobalErrorComponent?: boolean): any;
10
+ declare function copyErrorJs(isAppRouter?: boolean): Promise<any>;
11
+ declare function copyGlobalErrorJs(): Promise<any>;
12
+ declare function copyFileWithBackup(sourcePath: any, targetPath: any): Promise<any>;
6
13
  declare function copyConfigFiles(): Promise<void>;
@@ -3,16 +3,61 @@
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
5
  const debug = process.env.HONEYBADGER_DEBUG === 'true';
6
- async function copyErrorJs() {
7
- const targetPath = path.join('pages', '_error.js');
8
- const errorJsAlreadyExists = fs.existsSync(targetPath);
9
- if (errorJsAlreadyExists) {
10
- // Don't overwrite an existing _error.js file.
11
- // Create a _error.js.bak file instead.
12
- const backupPath = path.join('pages', '_error.js.bak');
6
+ function usesTypescript() {
7
+ return fs.existsSync('tsconfig.json');
8
+ }
9
+ function usesPagesRouter() {
10
+ return fs.existsSync('pages');
11
+ }
12
+ function usesAppRouter() {
13
+ return fs.existsSync('app');
14
+ }
15
+ function getTargetPath(isAppRouter = false, isGlobalErrorComponent = false) {
16
+ if (!isAppRouter && isGlobalErrorComponent) {
17
+ throw new Error('invalid arguments: isGlobalErrorComponent can only be true when isAppRouter is true');
18
+ }
19
+ const extension = usesTypescript() ? 'tsx' : 'js';
20
+ const srcFolder = isAppRouter ? 'app' : 'pages';
21
+ let fileName = '';
22
+ if (isAppRouter) {
23
+ fileName = isGlobalErrorComponent ? 'global-error' : 'error';
24
+ }
25
+ else {
26
+ fileName = '_error';
27
+ }
28
+ return path.join(srcFolder, fileName + '.' + extension);
29
+ }
30
+ function getTemplate(isAppRouter = false, isGlobalErrorComponent = false) {
31
+ if (!isAppRouter && isGlobalErrorComponent) {
32
+ throw new Error('invalid arguments: isGlobalErrorComponent can only be true when isAppRouter is true');
33
+ }
34
+ const extension = isGlobalErrorComponent ? 'tsx' : 'js';
35
+ const templateName = isAppRouter ? '_error_app_router' : '_error';
36
+ return path.resolve(__dirname, '../templates', templateName + '.' + extension);
37
+ }
38
+ async function copyErrorJs(isAppRouter = false) {
39
+ const sourcePath = getTemplate(isAppRouter);
40
+ const targetPath = getTargetPath(isAppRouter);
41
+ return copyFileWithBackup(sourcePath, targetPath);
42
+ }
43
+ function copyGlobalErrorJs() {
44
+ const sourcePath = getTemplate(true, true);
45
+ const targetPath = getTargetPath(true, true);
46
+ return copyFileWithBackup(sourcePath, targetPath);
47
+ }
48
+ async function copyFileWithBackup(sourcePath, targetPath) {
49
+ const fileAlreadyExists = fs.existsSync(targetPath);
50
+ if (fileAlreadyExists) {
51
+ // Don't overwrite an existing file without creating a backup first
52
+ const backupPath = targetPath + '.bak';
53
+ if (debug) {
54
+ console.debug('backing up', targetPath, 'to', backupPath);
55
+ }
13
56
  await fs.promises.copyFile(targetPath, backupPath);
14
57
  }
15
- const sourcePath = path.resolve(__dirname, '../templates/_error.js');
58
+ if (debug) {
59
+ console.debug('copying', sourcePath, 'to', targetPath);
60
+ }
16
61
  return fs.promises.copyFile(sourcePath, targetPath);
17
62
  }
18
63
  async function copyConfigFiles() {
@@ -20,13 +65,24 @@ async function copyConfigFiles() {
20
65
  console.debug('cwd', process.cwd());
21
66
  }
22
67
  const templateDir = path.resolve(__dirname, '../templates');
23
- const files = await fs.promises.readdir(templateDir);
24
- const copyPromises = files.map((file) => {
68
+ const configFiles = [
69
+ 'honeybadger.browser.config.js',
70
+ 'honeybadger.edge.config.js',
71
+ 'honeybadger.server.config.js',
72
+ ];
73
+ const copyPromises = configFiles.map((file) => {
25
74
  if (debug) {
26
75
  console.debug('copying', file);
27
76
  }
28
- return file === '_error.js' ? copyErrorJs() : fs.promises.copyFile(path.join(templateDir, file), file);
77
+ return fs.promises.copyFile(path.join(templateDir, file), file);
29
78
  });
79
+ if (usesPagesRouter()) {
80
+ copyPromises.push(copyErrorJs(false));
81
+ }
82
+ if (usesAppRouter()) {
83
+ copyPromises.push(copyErrorJs(true));
84
+ copyPromises.push(copyGlobalErrorJs());
85
+ }
30
86
  await Promise.all(copyPromises);
31
87
  console.log('Done copying config files.');
32
88
  }
@@ -1 +1 @@
1
- {"version":3,"file":"copy-config-files.js","sourceRoot":"","sources":["../src/copy-config-files.ts"],"names":[],"mappings":";;AAEA,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAExB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,KAAK,MAAM,CAAA;AAEtD,KAAK,UAAU,WAAW;IACxB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;IAClD,MAAM,oBAAoB,GAAG,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAA;IACtD,IAAI,oBAAoB,EAAE;QACxB,8CAA8C;QAC9C,uCAAuC;QACvC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAA;QACtD,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;KACnD;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAA;IAEpE,OAAO,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;AACrD,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,IAAI,KAAK,EAAE;QACT,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;KACpC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;IAC3D,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IACpD,MAAM,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACtC,IAAI,KAAK,EAAE;YACT,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;SAC/B;QAED,OAAO,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;IACxG,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAA;AAC3C,CAAC;AAED,eAAe,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\n\nconst path = require('path')\nconst fs = require('fs')\n\nconst debug = process.env.HONEYBADGER_DEBUG === 'true'\n\nasync function copyErrorJs() {\n const targetPath = path.join('pages', '_error.js')\n const errorJsAlreadyExists = fs.existsSync(targetPath)\n if (errorJsAlreadyExists) {\n // Don't overwrite an existing _error.js file.\n // Create a _error.js.bak file instead.\n const backupPath = path.join('pages', '_error.js.bak')\n await fs.promises.copyFile(targetPath, backupPath)\n }\n\n const sourcePath = path.resolve(__dirname, '../templates/_error.js')\n\n return fs.promises.copyFile(sourcePath, targetPath)\n}\n\nasync function copyConfigFiles() {\n if (debug) {\n console.debug('cwd', process.cwd())\n }\n\n const templateDir = path.resolve(__dirname, '../templates')\n const files = await fs.promises.readdir(templateDir)\n const copyPromises = files.map((file) => {\n if (debug) {\n console.debug('copying', file)\n }\n\n return file === '_error.js' ? copyErrorJs() : fs.promises.copyFile(path.join(templateDir, file), file)\n });\n await Promise.all(copyPromises);\n console.log('Done copying config files.')\n}\n\ncopyConfigFiles().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"]}
1
+ {"version":3,"file":"copy-config-files.js","sourceRoot":"","sources":["../src/copy-config-files.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nconst path = require('path')\nconst fs = require('fs')\n\nconst debug = process.env.HONEYBADGER_DEBUG === 'true'\n\nfunction usesTypescript() {\n return fs.existsSync('tsconfig.json')\n}\n\nfunction usesPagesRouter() {\n return fs.existsSync('pages')\n}\n\nfunction usesAppRouter() {\n return fs.existsSync('app')\n}\n\nfunction getTargetPath(isAppRouter = false, isGlobalErrorComponent = false) {\n if (!isAppRouter && isGlobalErrorComponent) {\n throw new Error('invalid arguments: isGlobalErrorComponent can only be true when isAppRouter is true')\n }\n\n const extension = usesTypescript() ? 'tsx' : 'js'\n const srcFolder = isAppRouter ? 'app' : 'pages'\n\n let fileName = ''\n if (isAppRouter) {\n fileName = isGlobalErrorComponent ? 'global-error' : 'error'\n }\n else {\n fileName = '_error'\n }\n\n return path.join(srcFolder, fileName + '.' + extension)\n}\n\nfunction getTemplate(isAppRouter = false, isGlobalErrorComponent = false) {\n if (!isAppRouter && isGlobalErrorComponent) {\n throw new Error('invalid arguments: isGlobalErrorComponent can only be true when isAppRouter is true')\n }\n\n const extension = isGlobalErrorComponent ? 'tsx' : 'js'\n const templateName = isAppRouter ? '_error_app_router' : '_error'\n\n return path.resolve(__dirname, '../templates', templateName + '.' + extension)\n}\n\nasync function copyErrorJs(isAppRouter = false) {\n const sourcePath = getTemplate(isAppRouter)\n const targetPath = getTargetPath(isAppRouter)\n\n return copyFileWithBackup(sourcePath, targetPath)\n}\n\nfunction copyGlobalErrorJs() {\n const sourcePath = getTemplate(true, true)\n const targetPath = getTargetPath(true, true)\n\n return copyFileWithBackup(sourcePath, targetPath)\n}\n\nasync function copyFileWithBackup(sourcePath, targetPath) {\n const fileAlreadyExists = fs.existsSync(targetPath)\n if (fileAlreadyExists) {\n // Don't overwrite an existing file without creating a backup first\n const backupPath = targetPath + '.bak'\n if (debug) {\n console.debug('backing up', targetPath, 'to', backupPath)\n }\n await fs.promises.copyFile(targetPath, backupPath)\n }\n\n if (debug) {\n console.debug('copying', sourcePath, 'to', targetPath)\n }\n\n return fs.promises.copyFile(sourcePath, targetPath)\n}\n\nasync function copyConfigFiles() {\n if (debug) {\n console.debug('cwd', process.cwd())\n }\n\n const templateDir = path.resolve(__dirname, '../templates')\n const configFiles = [\n 'honeybadger.browser.config.js',\n 'honeybadger.edge.config.js',\n 'honeybadger.server.config.js',\n ]\n\n const copyPromises = configFiles.map((file) => {\n if (debug) {\n console.debug('copying', file)\n }\n return fs.promises.copyFile(path.join(templateDir, file), file)\n })\n\n if (usesPagesRouter()) {\n copyPromises.push(copyErrorJs(false))\n }\n\n if (usesAppRouter()) {\n copyPromises.push(copyErrorJs(true))\n copyPromises.push(copyGlobalErrorJs())\n }\n\n await Promise.all(copyPromises);\n\n console.log('Done copying config files.')\n}\n\ncopyConfigFiles().catch((err) => {\n console.error(err)\n process.exit(1)\n})\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@honeybadger-io/nextjs",
3
- "version": "5.5.0",
3
+ "version": "5.6.0",
4
4
  "description": "Next.js integration for Honeybadger",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -40,7 +40,7 @@
40
40
  "next": "13.x"
41
41
  },
42
42
  "dependencies": {
43
- "@honeybadger-io/js": "^6.0.0",
43
+ "@honeybadger-io/js": "^6.1.1",
44
44
  "@honeybadger-io/webpack": "5.1.7"
45
45
  },
46
46
  "devDependencies": {
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "904a4489f3b1708e035eed5bf55c6f4e241658f8"
60
+ "gitHead": "171888777c56def8d429703139a7003a266b4af0"
61
61
  }
@@ -5,17 +5,76 @@ const fs = require('fs')
5
5
 
6
6
  const debug = process.env.HONEYBADGER_DEBUG === 'true'
7
7
 
8
- async function copyErrorJs() {
9
- const targetPath = path.join('pages', '_error.js')
10
- const errorJsAlreadyExists = fs.existsSync(targetPath)
11
- if (errorJsAlreadyExists) {
12
- // Don't overwrite an existing _error.js file.
13
- // Create a _error.js.bak file instead.
14
- const backupPath = path.join('pages', '_error.js.bak')
8
+ function usesTypescript() {
9
+ return fs.existsSync('tsconfig.json')
10
+ }
11
+
12
+ function usesPagesRouter() {
13
+ return fs.existsSync('pages')
14
+ }
15
+
16
+ function usesAppRouter() {
17
+ return fs.existsSync('app')
18
+ }
19
+
20
+ function getTargetPath(isAppRouter = false, isGlobalErrorComponent = false) {
21
+ if (!isAppRouter && isGlobalErrorComponent) {
22
+ throw new Error('invalid arguments: isGlobalErrorComponent can only be true when isAppRouter is true')
23
+ }
24
+
25
+ const extension = usesTypescript() ? 'tsx' : 'js'
26
+ const srcFolder = isAppRouter ? 'app' : 'pages'
27
+
28
+ let fileName = ''
29
+ if (isAppRouter) {
30
+ fileName = isGlobalErrorComponent ? 'global-error' : 'error'
31
+ }
32
+ else {
33
+ fileName = '_error'
34
+ }
35
+
36
+ return path.join(srcFolder, fileName + '.' + extension)
37
+ }
38
+
39
+ function getTemplate(isAppRouter = false, isGlobalErrorComponent = false) {
40
+ if (!isAppRouter && isGlobalErrorComponent) {
41
+ throw new Error('invalid arguments: isGlobalErrorComponent can only be true when isAppRouter is true')
42
+ }
43
+
44
+ const extension = isGlobalErrorComponent ? 'tsx' : 'js'
45
+ const templateName = isAppRouter ? '_error_app_router' : '_error'
46
+
47
+ return path.resolve(__dirname, '../templates', templateName + '.' + extension)
48
+ }
49
+
50
+ async function copyErrorJs(isAppRouter = false) {
51
+ const sourcePath = getTemplate(isAppRouter)
52
+ const targetPath = getTargetPath(isAppRouter)
53
+
54
+ return copyFileWithBackup(sourcePath, targetPath)
55
+ }
56
+
57
+ function copyGlobalErrorJs() {
58
+ const sourcePath = getTemplate(true, true)
59
+ const targetPath = getTargetPath(true, true)
60
+
61
+ return copyFileWithBackup(sourcePath, targetPath)
62
+ }
63
+
64
+ async function copyFileWithBackup(sourcePath, targetPath) {
65
+ const fileAlreadyExists = fs.existsSync(targetPath)
66
+ if (fileAlreadyExists) {
67
+ // Don't overwrite an existing file without creating a backup first
68
+ const backupPath = targetPath + '.bak'
69
+ if (debug) {
70
+ console.debug('backing up', targetPath, 'to', backupPath)
71
+ }
15
72
  await fs.promises.copyFile(targetPath, backupPath)
16
73
  }
17
74
 
18
- const sourcePath = path.resolve(__dirname, '../templates/_error.js')
75
+ if (debug) {
76
+ console.debug('copying', sourcePath, 'to', targetPath)
77
+ }
19
78
 
20
79
  return fs.promises.copyFile(sourcePath, targetPath)
21
80
  }
@@ -26,15 +85,30 @@ async function copyConfigFiles() {
26
85
  }
27
86
 
28
87
  const templateDir = path.resolve(__dirname, '../templates')
29
- const files = await fs.promises.readdir(templateDir)
30
- const copyPromises = files.map((file) => {
88
+ const configFiles = [
89
+ 'honeybadger.browser.config.js',
90
+ 'honeybadger.edge.config.js',
91
+ 'honeybadger.server.config.js',
92
+ ]
93
+
94
+ const copyPromises = configFiles.map((file) => {
31
95
  if (debug) {
32
96
  console.debug('copying', file)
33
97
  }
98
+ return fs.promises.copyFile(path.join(templateDir, file), file)
99
+ })
100
+
101
+ if (usesPagesRouter()) {
102
+ copyPromises.push(copyErrorJs(false))
103
+ }
104
+
105
+ if (usesAppRouter()) {
106
+ copyPromises.push(copyErrorJs(true))
107
+ copyPromises.push(copyGlobalErrorJs())
108
+ }
34
109
 
35
- return file === '_error.js' ? copyErrorJs() : fs.promises.copyFile(path.join(templateDir, file), file)
36
- });
37
110
  await Promise.all(copyPromises);
111
+
38
112
  console.log('Done copying config files.')
39
113
  }
40
114
 
@@ -0,0 +1,38 @@
1
+ 'use client'; // Error components must be Client Components
2
+
3
+ // eslint-disable-next-line import/no-unresolved
4
+ import { useEffect } from 'react'
5
+ import { Honeybadger } from '@honeybadger-io/react'
6
+
7
+ /**
8
+ * error.[js|tsx]: https://nextjs.org/docs/app/building-your-application/routing/error-handling
9
+ * global-error.[js|tsx]: https://nextjs.org/docs/app/building-your-application/routing/error-handling#handling-errors-in-layouts
10
+ *
11
+ * This component is called when:
12
+ * - on the server, when data fetching methods throw or reject
13
+ * - on the client, when getInitialProps throws or rejects
14
+ * - on the client, when a React lifecycle method (render, componentDidMount, etc) throws or rejects
15
+ * and was caught by the built-in Next.js error boundary
16
+ */
17
+ export default function Error({
18
+ error,
19
+ reset,
20
+ }) {
21
+ useEffect(() => {
22
+ Honeybadger.notify(error)
23
+ }, [error])
24
+
25
+ return (
26
+ <div>
27
+ <h2>Something went wrong!</h2>
28
+ <button
29
+ onClick={
30
+ // Attempt to recover by trying to re-render the segment
31
+ () => reset()
32
+ }
33
+ >
34
+ Try again
35
+ </button>
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,41 @@
1
+ 'use client'; // Error components must be Client Components
2
+
3
+ // eslint-disable-next-line import/no-unresolved
4
+ import { useEffect } from 'react'
5
+ import { Honeybadger } from '@honeybadger-io/react'
6
+
7
+ /**
8
+ * error.[js|tsx]: https://nextjs.org/docs/app/building-your-application/routing/error-handling
9
+ * global-error.[js|tsx]: https://nextjs.org/docs/app/building-your-application/routing/error-handling#handling-errors-in-layouts
10
+ *
11
+ * This component is called when:
12
+ * - on the server, when data fetching methods throw or reject
13
+ * - on the client, when getInitialProps throws or rejects
14
+ * - on the client, when a React lifecycle method (render, componentDidMount, etc) throws or rejects
15
+ * and was caught by the built-in Next.js error boundary
16
+ */
17
+ export default function Error({
18
+ error,
19
+ reset,
20
+ }: {
21
+ error: Error;
22
+ reset: () => void;
23
+ }) {
24
+ useEffect(() => {
25
+ Honeybadger.notify(error)
26
+ }, [error])
27
+
28
+ return (
29
+ <div>
30
+ <h2>Something went wrong!</h2>
31
+ <button
32
+ onClick={
33
+ // Attempt to recover by trying to re-render the segment
34
+ () => reset()
35
+ }
36
+ >
37
+ Try again
38
+ </button>
39
+ </div>
40
+ )
41
+ }