From fe4d394fa84db2f17aaff2f0288514ca09336f35 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 23 Oct 2025 09:28:35 +0800 Subject: [PATCH 1/2] Chore/improve error handling ux for insert before and remove child errors (#39779) * Improve error handling UX for insertBefore and removeChild client exception errors * nit * nit refactor --- .../interfaces/Support/MessageField.tsx | 2 +- .../ui/AIAssistantPanel/AIAssistant.tsx | 2 +- .../ClientSideExceptionHandler.tsx | 115 ++++++++++ .../ui/{ => ErrorBoundary}/ErrorBoundary.tsx | 0 .../GlobalErrorBoundaryState.tsx | 65 ++++++ .../InsertBeforeRemoveChildErrorHandler.tsx | 89 ++++++++ .../ui/GlobalErrorBoundaryState.tsx | 200 ------------------ apps/studio/pages/_app.tsx | 3 +- 8 files changed, 273 insertions(+), 203 deletions(-) create mode 100644 apps/studio/components/ui/ErrorBoundary/ClientSideExceptionHandler.tsx rename apps/studio/components/ui/{ => ErrorBoundary}/ErrorBoundary.tsx (100%) create mode 100644 apps/studio/components/ui/ErrorBoundary/GlobalErrorBoundaryState.tsx create mode 100644 apps/studio/components/ui/ErrorBoundary/InsertBeforeRemoveChildErrorHandler.tsx delete mode 100644 apps/studio/components/ui/GlobalErrorBoundaryState.tsx diff --git a/apps/studio/components/interfaces/Support/MessageField.tsx b/apps/studio/components/interfaces/Support/MessageField.tsx index 1a0196f3aba00..7fb4208442c1e 100644 --- a/apps/studio/components/interfaces/Support/MessageField.tsx +++ b/apps/studio/components/interfaces/Support/MessageField.tsx @@ -41,7 +41,7 @@ export function MessageField({ form, originalError }: MessageFieldProps) { diff --git a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx index 9e5083fd63326..4678cf0a7af9b 100644 --- a/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx +++ b/apps/studio/components/ui/AIAssistantPanel/AIAssistant.tsx @@ -28,7 +28,7 @@ import { useSqlEditorV2StateSnapshot } from 'state/sql-editor-v2' import { Button, cn, KeyboardShortcut } from 'ui' import { Admonition } from 'ui-patterns' import { ButtonTooltip } from '../ButtonTooltip' -import { ErrorBoundary } from '../ErrorBoundary' +import { ErrorBoundary } from '../ErrorBoundary/ErrorBoundary' import type { SqlSnippet } from './AIAssistant.types' import { onErrorChat } from './AIAssistant.utils' import { AIAssistantHeader } from './AIAssistantHeader' diff --git a/apps/studio/components/ui/ErrorBoundary/ClientSideExceptionHandler.tsx b/apps/studio/components/ui/ErrorBoundary/ClientSideExceptionHandler.tsx new file mode 100644 index 0000000000000..ebfaeaa55f9a5 --- /dev/null +++ b/apps/studio/components/ui/ErrorBoundary/ClientSideExceptionHandler.tsx @@ -0,0 +1,115 @@ +import { ExternalLink } from 'lucide-react' + +import { SupportCategories } from '@supabase/shared-types/out/constants' +import { SupportLink } from 'components/interfaces/Support/SupportLink' +import { useRouter } from 'next/router' +import { Button, cn } from 'ui' +import { Admonition } from 'ui-patterns' +import CopyButton from '../CopyButton' +import { InlineLinkClassName } from '../InlineLink' + +interface ClientSideExceptionHandlerProps { + message: string + sentryIssueId: string + urlMessage: string + resetErrorBoundary: () => void +} + +export const ClientSideExceptionHandler = ({ + message, + sentryIssueId, + urlMessage, + resetErrorBoundary, +}: ClientSideExceptionHandlerProps) => { + const router = useRouter() + + const isProduction = process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' + + const handleClearStorage = () => { + try { + localStorage.clear() + sessionStorage.clear() + } catch (e) { + // ignore + } + window.location.reload() + } + + return ( + <> +
+
+

Sorry! An unexpected error occurred.

+ +
+

+ Application error: a client-side exception has occurred (see browser console for more + information) +

+

{message}

+
+ +
    +
  • + window.location.reload()} + > + Refresh + {' '} + the page +
  • +
  • + router.push('/logout')} + > + Sign out + {' '} + and sign back in +
  • +
  • + + Clear your browser storage + {' '} + to clean potentially outdated data +
  • +
  • + Disable browser extensions that might modify page content (e.g., Google Translate) +
  • +
  • If the problem persists, please contact support for assistance
  • +
+
+ +
+ + + {/* [Joshen] For local and staging, allow us to escape the error boundary */} + {/* We could actually investigate how to make this available on prod, but without being able to reliably test this, I'm not keen to do it now */} + {isProduction ? ( + + ) : ( + + )} +
+ + ) +} diff --git a/apps/studio/components/ui/ErrorBoundary.tsx b/apps/studio/components/ui/ErrorBoundary/ErrorBoundary.tsx similarity index 100% rename from apps/studio/components/ui/ErrorBoundary.tsx rename to apps/studio/components/ui/ErrorBoundary/ErrorBoundary.tsx diff --git a/apps/studio/components/ui/ErrorBoundary/GlobalErrorBoundaryState.tsx b/apps/studio/components/ui/ErrorBoundary/GlobalErrorBoundaryState.tsx new file mode 100644 index 0000000000000..4efebda3cb894 --- /dev/null +++ b/apps/studio/components/ui/ErrorBoundary/GlobalErrorBoundaryState.tsx @@ -0,0 +1,65 @@ +import { isError } from 'lodash' +import Link from 'next/link' +import { useRouter } from 'next/router' + +import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' +import { ClientSideExceptionHandler } from './ClientSideExceptionHandler' +import { InsertBeforeRemoveChildErrorHandler } from './InsertBeforeRemoveChildErrorHandler' + +export type FallbackProps = { + error: unknown + resetErrorBoundary: (...args: unknown[]) => void +} + +export const GlobalErrorBoundaryState = ({ error, resetErrorBoundary }: FallbackProps) => { + const router = useRouter() + const checkIsError = isError(error) + + const largeLogo = useIsFeatureEnabled('branding:large_logo') + + const errorMessage = checkIsError ? error.message : '' + const urlMessage = checkIsError ? `Path name: ${router.pathname}\n\n${error?.stack}` : '' + + const isRemoveChildError = checkIsError + ? errorMessage.includes("Failed to execute 'removeChild' on 'Node'") + : false + const isInsertBeforeError = checkIsError + ? errorMessage.includes("Failed to execute 'insertBefore' on 'Node'") + : false + + // Get Sentry issue ID from error if available + const sentryIssueId = (!!error && typeof error === 'object' && (error as any).sentryId) ?? '' + + return ( +
+
+ + Supabase + +
+ +
+ {isRemoveChildError || isInsertBeforeError ? ( + + ) : ( + + )} +
+
+ ) +} diff --git a/apps/studio/components/ui/ErrorBoundary/InsertBeforeRemoveChildErrorHandler.tsx b/apps/studio/components/ui/ErrorBoundary/InsertBeforeRemoveChildErrorHandler.tsx new file mode 100644 index 0000000000000..77501d94a1b36 --- /dev/null +++ b/apps/studio/components/ui/ErrorBoundary/InsertBeforeRemoveChildErrorHandler.tsx @@ -0,0 +1,89 @@ +import { SupportCategories } from '@supabase/shared-types/out/constants' +import { Blocks, ExternalLink } from 'lucide-react' +import { useRouter } from 'next/router' + +import { SupportLink } from 'components/interfaces/Support/SupportLink' +import { detectBrowser } from 'lib/helpers' +import { Button } from 'ui' + +interface InsertBeforeRemoveChildErrorHandlerProps { + message: string + sentryIssueId: string + urlMessage: string + isRemoveChildError: boolean + isInsertBeforeError?: boolean +} + +export const InsertBeforeRemoveChildErrorHandler = ({ + message, + sentryIssueId, + urlMessage, + isRemoveChildError, + isInsertBeforeError, +}: InsertBeforeRemoveChildErrorHandlerProps) => { + const router = useRouter() + const browser = detectBrowser() + + return ( + <> +
+
+

Sorry! A browser extension may have caused an error.

+ +
+ +
+

+ Browser translation tools (like Chrome's built-in Translate) or some third-party browser + extensions are known to cause errors when using the Supabase Dashboard. +

+ +

+ We highly recommend{' '} + + {browser === 'Chrome' + ? 'disabling Chrome Translate or certain browser extensions' + : 'avoiding the use of browser translation tools or disabling certain extensions'} + {' '} + while using the Supabase Dashboard to avoid running into this error. Try to refresh the + browser to see if it occurs again. +

+
+ +

Error: {message}

+
+ +
+ + +
+ + Still stuck? + + + ) +} diff --git a/apps/studio/components/ui/GlobalErrorBoundaryState.tsx b/apps/studio/components/ui/GlobalErrorBoundaryState.tsx deleted file mode 100644 index 6747f5717f491..0000000000000 --- a/apps/studio/components/ui/GlobalErrorBoundaryState.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import { isError } from 'lodash' -import { ExternalLink } from 'lucide-react' -import Link from 'next/link' -import { useRouter } from 'next/router' - -import { SupportCategories } from '@supabase/shared-types/out/constants' -import { SupportLink } from 'components/interfaces/Support/SupportLink' -import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' -import { Button, cn } from 'ui' -import { Admonition } from 'ui-patterns' -import CopyButton from './CopyButton' -import { InlineLinkClassName } from './InlineLink' - -export type FallbackProps = { - error: unknown - resetErrorBoundary: (...args: any[]) => void -} - -export const GlobalErrorBoundaryState = ({ error, resetErrorBoundary }: FallbackProps) => { - const router = useRouter() - const checkIsError = isError(error) - - const largeLogo = useIsFeatureEnabled('branding:large_logo') - - const errorMessage = checkIsError ? error.message : '' - const urlMessage = checkIsError ? `Path name: ${router.pathname}\n\n${error?.stack}` : '' - const isRemoveChildError = checkIsError - ? errorMessage.includes("Failed to execute 'removeChild' on 'Node'") - : false - const isInsertBeforeError = checkIsError - ? errorMessage.includes("Failed to execute 'insertBefore' on 'Node'") - : false - - // Get Sentry issue ID from error if available - const sentryIssueId = (!!error && typeof error === 'object' && (error as any).sentryId) ?? '' - - const handleClearStorage = () => { - try { - localStorage.clear() - sessionStorage.clear() - } catch (e) { - // ignore - } - window.location.reload() - } - - return ( -
-
- - Supabase - -
- -
-
-
-

Sorry! An unexpected error occurred.

- -
-

- Application error: a client-side exception has occurred (see browser console for more - information) -

-

{errorMessage}

-
- {isRemoveChildError || isInsertBeforeError ? ( - -

- Try to avoid using Google translate or disable certain browser extensions to avoid - running into the{' '} - - {isRemoveChildError - ? `'removeChild' on 'Node'` - : isInsertBeforeError - ? `'insertBefore' on 'Node'` - : ''} - {' '} - error.{' '} - window.location.reload()} - > - Refresh - {' '} - the browser to see if occurs again. -

- -
- ) : ( - -
    -
  • - window.location.reload()} - > - Refresh - {' '} - the page -
  • -
  • - router.push('/logout')} - > - Sign out - {' '} - and sign back in -
  • -
  • - - Clear your browser storage - {' '} - to clean potentially outdated data -
  • -
  • - Disable browser extensions that might modify page content (e.g., Google Translate) -
  • -
  • If the problem persists, please contact support for assistance
  • -
-
- )} -
- {!isRemoveChildError && !isInsertBeforeError && ( - - )} - - {/* [Joshen] For local and staging, allow us to escape the error boundary */} - {/* We could actually investigate how to make this available on prod, but without being able to reliably test this, I'm not keen to do it now */} - {process.env.NEXT_PUBLIC_ENVIRONMENT !== 'prod' ? ( - - ) : ( - - )} - - {(isRemoveChildError || isInsertBeforeError) && ( - - Still stuck? - - )} -
-
-
- ) -} diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx index 04aac87207aa9..fb4ae48c14c00 100644 --- a/apps/studio/pages/_app.tsx +++ b/apps/studio/pages/_app.tsx @@ -48,7 +48,7 @@ import { StudioCommandMenu } from 'components/interfaces/App/CommandMenu' import { FeaturePreviewContextProvider } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import FeaturePreviewModal from 'components/interfaces/App/FeaturePreview/FeaturePreviewModal' import { MonacoThemeProvider } from 'components/interfaces/App/MonacoThemeProvider' -import { GlobalErrorBoundaryState } from 'components/ui/GlobalErrorBoundaryState' +import { GlobalErrorBoundaryState } from 'components/ui/ErrorBoundary/GlobalErrorBoundaryState' import { useRootQueryClient } from 'data/query-client' import { customFont, sourceCodePro } from 'fonts' import { useCustomContent } from 'hooks/custom-content/useCustomContent' @@ -143,6 +143,7 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) { {appTitle ?? 'Supabase'} + {/* [Alaister]: This has to be an inline style tag here and not a separate component due to next/font */}