| import type { NextPageContext } from 'next' | |
| import { GenericError } from '@/frame/components/GenericError' | |
| function Error() { | |
| return <GenericError /> | |
| } | |
| Error.getInitialProps = async (ctx: NextPageContext) => { | |
| // If this getInitialProps() is called in client-side rendering, | |
| // you won't have a `.res` object. It's only present when it's | |
| // rendered Node (SSR). That's our clue to know that, we should | |
| // send this error to Failbot. | |
| // In client-side, it's undefined. In server, it's a ServerResponse object. | |
| const { err, req, res } = ctx | |
| let statusCode = 500 | |
| if (res?.statusCode) { | |
| statusCode = res.statusCode | |
| } | |
| // 'err' will by falsy if it's a 404 | |
| // But note, at the time of writing this comment, we have a dedicated | |
| // `pages/404.tsx` which takes care of 404 messages. | |
| if (err && res && req) { | |
| // This is a (necessary) hack! | |
| // You can't import `../lib/failbot.ts` here in this | |
| // file because it gets imported by webpack to be used in the | |
| // client-side JS bundle. It *could* be solved by overriding | |
| // the webpack configuration in our `next.config.ts` but this is prone | |
| // to be fragile since ignoring code can be hard to get right | |
| // and the more we override there, the harder it will become to | |
| // upgrade NextJS in the future because of moving parts. | |
| // So the solution is to essentially do what the contextualizers | |
| // do which mutate the Express request object by attaching | |
| // callables to it. This way it's only ever present in SSR executed | |
| // code and doesn't need any custom webpack configuration. | |
| const expressRequest = req as any | |
| const FailBot = expressRequest.FailBot | |
| if (FailBot) { | |
| try { | |
| // An inclusion-list of headers we're OK with sending because | |
| // they don't contain an PII. | |
| const OK_HEADER_KEYS = ['user-agent', 'referer', 'accept-encoding', 'accept-language'] | |
| const reported = FailBot.report(err, { | |
| path: req.url, | |
| request: JSON.stringify( | |
| { | |
| method: expressRequest.method, | |
| query: expressRequest.query, | |
| language: expressRequest.language, | |
| }, | |
| undefined, | |
| 2, | |
| ), | |
| headers: JSON.stringify( | |
| Object.fromEntries( | |
| Object.entries(req.headers).filter(([k]) => OK_HEADER_KEYS.includes(k)), | |
| ), | |
| undefined, | |
| 2, | |
| ), | |
| }) | |
| // Within FailBot.report() (which is our wrapper for Failbot in | |
| // the `@github/failbot` package), it might exit only because | |
| // it has no configured backends to send to. I.e. it returns undefined. | |
| // Otherwise, it should return `Array<Promise<Response | void>>`. | |
| if (!reported) { | |
| console.warn( | |
| 'The FailBot.report() returned undefined which means the error was NOT sent to Failbot.', | |
| ) | |
| } else if ( | |
| Array.isArray(reported) && | |
| reported.length && | |
| reported.every((thing) => thing instanceof Promise) | |
| ) { | |
| // Make sure we await the promises even though we don't care | |
| // about the results. This makes the code cleaner rather than | |
| // letting the eventloop take care of it which could result | |
| // in cryptic error messages if the await does fail for some | |
| // reason. | |
| try { | |
| await Promise.all(reported) | |
| } catch (error) { | |
| console.warn('Unable to await reported FailBot errors', error) | |
| } | |
| } | |
| } catch (error) { | |
| // This does not necessarily mean FailBot failed to send. It's | |
| // most likely that we failed to *send to* FailBot before it | |
| // even has a chance to use the network. This is because | |
| // `FailBot.report` returns an array of Promises which themselves | |
| // could go wrong, but that's a story for another try/catch. | |
| // Basically, this catch it just to avoid other errors that | |
| // might prevent the pretty error page to render at all. | |
| console.warn('Failed to send error to FailBot.', error) | |
| } | |
| } | |
| } | |
| return { statusCode, message: err?.message } | |
| } | |
| export default Error | |