diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx
index e499198ecabf..ddc0972c67ff 100644
--- a/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Navbar/Content/index.tsx
@@ -6,7 +6,7 @@
*/
import React, {type ReactNode} from 'react';
-import {useThemeConfig} from '@docusaurus/theme-common';
+import {useThemeConfig, ErrorCauseBoundary} from '@docusaurus/theme-common';
import {
splitNavbarItems,
useNavbarMobileSidebar,
@@ -29,7 +29,18 @@ function NavbarItems({items}: {items: NavbarItemConfig[]}): JSX.Element {
return (
<>
{items.map((item, i) => (
-
+
+ new Error(
+ `A theme navbar item failed to render.
+Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:
+${JSON.stringify(item, null, 2)}`,
+ {cause: error},
+ )
+ }>
+
+
))}
>
);
diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json
index 228b473d2435..61ab747f4774 100644
--- a/packages/docusaurus-theme-common/package.json
+++ b/packages/docusaurus-theme-common/package.json
@@ -36,6 +36,7 @@
"@docusaurus/plugin-content-docs": "2.3.1",
"@docusaurus/plugin-content-pages": "2.3.1",
"@docusaurus/utils": "2.3.1",
+ "@docusaurus/utils-common": "2.3.1",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index 3b8e6e7df28d..d7993ab76ce5 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -93,4 +93,5 @@ export {
export {
ErrorBoundaryTryAgainButton,
ErrorBoundaryError,
+ ErrorCauseBoundary,
} from './utils/errorBoundaryUtils';
diff --git a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx
index 726946be9e6c..1d03b8dc814c 100644
--- a/packages/docusaurus-theme-common/src/utils/docsUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/docsUtils.tsx
@@ -271,8 +271,8 @@ export function useLayoutDocsSidebar(
`Can't find any sidebar with id "${sidebarId}" in version${
versions.length > 1 ? 's' : ''
} ${versions.map((version) => version.name).join(', ')}".
- Available sidebar ids are:
- - ${Object.keys(allSidebars).join('\n- ')}`,
+Available sidebar ids are:
+- ${Object.keys(allSidebars).join('\n- ')}`,
);
}
return sidebarEntry[1];
@@ -304,9 +304,9 @@ export function useLayoutDoc(
return null;
}
throw new Error(
- `DocNavbarItem: couldn't find any doc with id "${docId}" in version${
+ `Couldn't find any doc with id "${docId}" in version${
versions.length > 1 ? 's' : ''
- } ${versions.map((version) => version.name).join(', ')}".
+ } "${versions.map((version) => version.name).join(', ')}".
Available doc ids are:
- ${uniq(allDocs.map((versionDoc) => versionDoc.id)).join('\n- ')}`,
);
diff --git a/packages/docusaurus-theme-common/src/utils/errorBoundaryUtils.tsx b/packages/docusaurus-theme-common/src/utils/errorBoundaryUtils.tsx
index 193ec7cd4ff7..051dbe86eb35 100644
--- a/packages/docusaurus-theme-common/src/utils/errorBoundaryUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/errorBoundaryUtils.tsx
@@ -7,6 +7,7 @@
import React, {type ComponentProps} from 'react';
import Translate from '@docusaurus/Translate';
+import {getErrorCausalChain} from '@docusaurus/utils-common';
import styles from './errorBoundaryUtils.module.css';
export function ErrorBoundaryTryAgainButton(
@@ -22,7 +23,34 @@ export function ErrorBoundaryTryAgainButton(
);
}
-
export function ErrorBoundaryError({error}: {error: Error}): JSX.Element {
- return
{error.message}
;
+ const causalChain = getErrorCausalChain(error);
+ const fullMessage = causalChain.map((e) => e.message).join('\n\nCause:\n');
+ return {fullMessage}
;
+}
+
+/**
+ * This component is useful to wrap a low-level error into a more meaningful
+ * error with extra context, using the ES error-cause feature.
+ *
+ * new Error("extra context message",{cause: error})}
+ * >
+ *
+ *
+ */
+export class ErrorCauseBoundary extends React.Component<
+ {
+ children: React.ReactNode;
+ onError: (error: Error, errorInfo: React.ErrorInfo) => Error;
+ },
+ unknown
+> {
+ override componentDidCatch(error: Error, errorInfo: React.ErrorInfo): never {
+ throw this.props.onError(error, errorInfo);
+ }
+
+ override render(): React.ReactNode {
+ return this.props.children;
+ }
}
diff --git a/packages/docusaurus-utils-common/src/__tests__/errorUtils.test.ts b/packages/docusaurus-utils-common/src/__tests__/errorUtils.test.ts
new file mode 100644
index 000000000000..1d29f660861f
--- /dev/null
+++ b/packages/docusaurus-utils-common/src/__tests__/errorUtils.test.ts
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {getErrorCausalChain} from '../errorUtils';
+
+describe('getErrorCausalChain', () => {
+ it('works for simple error', () => {
+ const error = new Error('msg');
+ expect(getErrorCausalChain(error)).toEqual([error]);
+ });
+
+ it('works for nested errors', () => {
+ const error = new Error('msg', {
+ cause: new Error('msg', {cause: new Error('msg')}),
+ });
+ expect(getErrorCausalChain(error)).toEqual([
+ error,
+ error.cause,
+ (error.cause as Error).cause,
+ ]);
+ });
+});
diff --git a/packages/docusaurus-utils-common/src/errorUtils.ts b/packages/docusaurus-utils-common/src/errorUtils.ts
new file mode 100644
index 000000000000..5e3161dc47c2
--- /dev/null
+++ b/packages/docusaurus-utils-common/src/errorUtils.ts
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+type CausalChain = [Error, ...Error[]];
+
+export function getErrorCausalChain(error: Error): CausalChain {
+ if (error.cause) {
+ return [error, ...getErrorCausalChain(error.cause as Error)];
+ }
+ return [error];
+}
diff --git a/packages/docusaurus-utils-common/src/index.ts b/packages/docusaurus-utils-common/src/index.ts
index 11b925a28302..b1bbfb5237e1 100644
--- a/packages/docusaurus-utils-common/src/index.ts
+++ b/packages/docusaurus-utils-common/src/index.ts
@@ -10,3 +10,4 @@ export {
default as applyTrailingSlash,
type ApplyTrailingSlashParams,
} from './applyTrailingSlash';
+export {getErrorCausalChain} from './errorUtils';
diff --git a/packages/docusaurus/src/client/theme-fallback/Error/index.tsx b/packages/docusaurus/src/client/theme-fallback/Error/index.tsx
index 645bd1682bdf..79ace57d7627 100644
--- a/packages/docusaurus/src/client/theme-fallback/Error/index.tsx
+++ b/packages/docusaurus/src/client/theme-fallback/Error/index.tsx
@@ -11,6 +11,7 @@
import React from 'react';
import Head from '@docusaurus/Head';
import ErrorBoundary from '@docusaurus/ErrorBoundary';
+import {getErrorCausalChain} from '@docusaurus/utils-common';
import Layout from '@theme/Layout';
import type {Props} from '@theme/Error';
@@ -42,11 +43,17 @@ function ErrorDisplay({error, tryAgain}: Props): JSX.Element {
}}>
Try again
- {error.message}
+
);
}
+function ErrorBoundaryError({error}: {error: Error}): JSX.Element {
+ const causalChain = getErrorCausalChain(error);
+ const fullMessage = causalChain.map((e) => e.message).join('\n\nCause:\n');
+ return {fullMessage}
;
+}
+
export default function Error({error, tryAgain}: Props): JSX.Element {
// We wrap the error in its own error boundary because the layout can actually
// throw too... Only the ErrorDisplay component is simple enough to be