From a3d7bcd22e254e76175d733a0955b5cd9e9fd2f3 Mon Sep 17 00:00:00 2001 From: Benny Joo Date: Sun, 12 Jan 2025 08:12:02 -0500 Subject: [PATCH] feat: render custom error page for unexpected sever error + remove` pages/_error` (#18606) * remove page/_error * refactor app/error --- apps/web/app/error.tsx | 91 ++++++++---------- apps/web/components/error/error-page.tsx | 41 +++++--- apps/web/pages/_error.tsx | 113 ----------------------- 3 files changed, 70 insertions(+), 175 deletions(-) delete mode 100644 apps/web/pages/_error.tsx diff --git a/apps/web/app/error.tsx b/apps/web/app/error.tsx index b804d677ac..750972ecad 100644 --- a/apps/web/app/error.tsx +++ b/apps/web/app/error.tsx @@ -1,64 +1,55 @@ "use client"; -/** - * Typescript class based component for custom-error - * @link https://nextjs.org/docs/advanced-features/custom-error-page - */ -import type { NextPage } from "next"; -import type { ErrorProps } from "next/error"; import React from "react"; +import { getErrorFromUnknown } from "@calcom/lib/errors"; import { HttpError } from "@calcom/lib/http-error"; import logger from "@calcom/lib/logger"; import { redactError } from "@calcom/lib/redactError"; import { ErrorPage } from "@components/error/error-page"; -type NextError = Error & { digest?: string }; - -// Ref: https://nextjs.org/docs/app/api-reference/file-conventions/error#props -export type DefaultErrorProps = { - error: NextError; - reset: () => void; // A function to reset the error boundary -}; - -type AugmentedError = NextError | HttpError | null; - -type CustomErrorProps = { - err?: AugmentedError; - statusCode?: number; - message?: string; -} & Omit; - const log = logger.getSubLogger({ prefix: ["[error]"] }); -const CustomError: NextPage = (props) => { - const { error } = props; - let errorObject: CustomErrorProps = { - message: error.message, - err: error, - }; - - if (error instanceof HttpError) { - const redactedError = redactError(error); - errorObject = { - statusCode: error.statusCode, - title: redactedError.name, - message: redactedError.message, - err: { - ...redactedError, - ...error, - }, - }; - } - - // `error.digest` property contains an automatically generated hash of the error that can be used to match the corresponding error in server-side logs - log.debug(`${error?.toString() ?? JSON.stringify(error)}`); - log.info("errorObject: ", errorObject); - - return ( - - ); +type ErrorProps = { + error: Error; + reset: () => void; }; -export default CustomError; +export default function Error({ error, reset }: ErrorProps) { + React.useEffect(() => { + log.error(error); + }, [error]); + + const processedError = React.useMemo(() => { + const err = getErrorFromUnknown(error); + + if (err instanceof HttpError) { + const redactedError = redactError(err); + return { + statusCode: err.statusCode, + title: redactedError.name, + name: redactedError.name, + message: redactedError.message, + url: err.url, + method: err.method, + cause: err.cause, + }; + } + + return { + statusCode: 500, + title: "Internal Server Error", + name: "Internal Server Error", + message: "An unexpected error occurred.", + }; + }, [error]); + + return ( + + ); +} diff --git a/apps/web/components/error/error-page.tsx b/apps/web/components/error/error-page.tsx index 112202ffa5..0990a2aac4 100644 --- a/apps/web/components/error/error-page.tsx +++ b/apps/web/components/error/error-page.tsx @@ -1,6 +1,9 @@ +"use client"; + import React from "react"; import { HttpError } from "@calcom/lib/http-error"; +import { Button } from "@calcom/ui"; type Props = { statusCode?: number | null; @@ -53,21 +56,35 @@ export const ErrorPage: React.FC = (props) => { return ( <> -
-
-
-

{statusCode}

-

+
+
+

{statusCode}

+

It's not you, it's us.

+

+ Something went wrong on our end. Get in touch with our support team, and we’ll get it fixed right + away for you. +

+ +
+

+ Please provide the following text when contacting support to better help you: +

+
               {message}
-            

+
-
- {displayDebug && ( -
- -
- )} + + + +
+ {displayDebug && ( +
+ +
+ )} ); }; diff --git a/apps/web/pages/_error.tsx b/apps/web/pages/_error.tsx deleted file mode 100644 index 50dce26a41..0000000000 --- a/apps/web/pages/_error.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Typescript class based component for custom-error - * @link https://nextjs.org/docs/advanced-features/custom-error-page - */ -import type { NextPage, NextPageContext } from "next"; -import type { ErrorProps } from "next/error"; -import NextError from "next/error"; -import React from "react"; - -import { getErrorFromUnknown } from "@calcom/lib/errors"; -import { HttpError } from "@calcom/lib/http-error"; -import logger from "@calcom/lib/logger"; -import { redactError } from "@calcom/lib/redactError"; - -import { ErrorPage } from "@components/error/error-page"; - -// Adds HttpException to the list of possible error types. -type AugmentedError = (NonNullable & HttpError) | null; -type CustomErrorProps = { - err?: AugmentedError; - message?: string; - hasGetInitialPropsRun?: boolean; -} & Omit; - -type AugmentedNextPageContext = Omit & { - err: AugmentedError; -}; - -const log = logger.getSubLogger({ prefix: ["[error]"] }); - -const CustomError: NextPage = (props) => { - const { statusCode, err, message, hasGetInitialPropsRun } = props; - - if (!hasGetInitialPropsRun && err) { - // getInitialProps is not called in case of - // https://github.com/vercel/next.js/issues/8592. As a workaround, we pass - // err via _app.tsx so it can be captured - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const e = getErrorFromUnknown(err); - // can be captured here - // e.g. Sentry.captureException(e); - } - return ; -}; - -/** - * Partially adapted from the example in - * https://github.com/vercel/next.js/tree/canary/examples/with-sentry - */ -CustomError.getInitialProps = async (ctx: AugmentedNextPageContext) => { - const { res, err, asPath } = ctx; - const errorInitialProps = (await NextError.getInitialProps({ - res, - err, - } as NextPageContext)) as CustomErrorProps; - - // Workaround for https://github.com/vercel/next.js/issues/8592, mark when - // getInitialProps has run - errorInitialProps.hasGetInitialPropsRun = true; - - // If a HttpError message, let's override defaults - if (err instanceof HttpError) { - const redactedError = redactError(err); - errorInitialProps.statusCode = err.statusCode; - errorInitialProps.title = redactedError.name; - errorInitialProps.message = redactedError.message; - errorInitialProps.err = { - ...redactedError, - url: err.url, - statusCode: err.statusCode, - cause: err.cause, - method: err.method, - }; - } - - if (res) { - // Running on the server, the response object is available. - // - // Next.js will pass an err on the server if a page's `getInitialProps` - // threw or returned a Promise that rejected - - // Overrides http status code if present in errorInitialProps - res.statusCode = errorInitialProps.statusCode; - - log.debug(`server side logged this: ${err?.toString() ?? JSON.stringify(err)}`); - log.info("return props, ", errorInitialProps); - - return errorInitialProps; - } else { - // Running on the client (browser). - // - // Next.js will provide an err if: - // - // - a page's `getInitialProps` threw or returned a Promise that rejected - // - an exception was thrown somewhere in the React lifecycle (render, - // componentDidMount, etc) that was caught by Next.js's React Error - // Boundary. Read more about what types of exceptions are caught by Error - // Boundaries: https://reactjs.org/docs/error-boundaries.html - if (err) { - log.info("client side logged this", err); - return errorInitialProps; - } - } - - // If this point is reached, getInitialProps was called without any - // information about what the error might be. This is unexpected and may - // indicate a bug introduced in Next.js - new Error(`_error.tsx getInitialProps missing data at path: ${asPath}`); - - return errorInitialProps; -}; - -export default CustomError;