diff --git a/.changeset/cold-buckets-divide.md b/.changeset/cold-buckets-divide.md new file mode 100644 index 000000000..1e528bad1 --- /dev/null +++ b/.changeset/cold-buckets-divide.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +fix nested a tag causing hydration error diff --git a/packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx b/packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx index 2a246d13c..4218f760e 100644 --- a/packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx +++ b/packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx @@ -4,7 +4,7 @@ import { SiteInsightsLinkPosition, } from '@gitbook/api'; -import { Link } from '@/components/primitives'; +import { LinkBox, LinkOverlay } from '@/components/primitives'; import { Image } from '@/components/utils'; import { resolveContentRef } from '@/lib/references'; import { type ClassValue, tcls } from '@/lib/tailwind'; @@ -44,7 +44,6 @@ export async function RecordCard(
+ // We don't use `Link` directly here because we could end up in a situation where + // a link is rendered inside a link, which is not allowed in HTML. + // It causes an hydration error in React. + + {body} - + ); } diff --git a/packages/gitbook/src/components/RootLayout/globals.css b/packages/gitbook/src/components/RootLayout/globals.css index 15c09614e..2bb3e1a22 100644 --- a/packages/gitbook/src/components/RootLayout/globals.css +++ b/packages/gitbook/src/components/RootLayout/globals.css @@ -148,6 +148,13 @@ width: 100%; } } + + .elevate-link { + & a[href]:not(.link-overlay) { + position: relative; + z-index: 20; + } + } } html { diff --git a/packages/gitbook/src/components/primitives/Link.tsx b/packages/gitbook/src/components/primitives/Link.tsx index 5cb026d3a..cf9d2c24c 100644 --- a/packages/gitbook/src/components/primitives/Link.tsx +++ b/packages/gitbook/src/components/primitives/Link.tsx @@ -3,6 +3,7 @@ import NextLink, { type LinkProps as NextLinkProps } from 'next/link'; import React from 'react'; +import { tcls } from '@/lib/tailwind'; import { type TrackEventInput, useTrackEvent } from '../Insights'; // Props from Next, which includes NextLinkProps and all the things anchor elements support. @@ -75,6 +76,46 @@ export const Link = React.forwardRef(function Link( ); }); +/** + * A box used to contain a link overlay. + * It is used to create a clickable area that can contain other elements. + */ +export const LinkBox = React.forwardRef(function LinkBox( + props: React.BaseHTMLAttributes, + ref: React.Ref +) { + const { children, className, ...domProps } = props; + return ( +
+ {children} +
+ ); +}); + +/** + * A link overlay that can be used to create a clickable area on top of other elements. + * It is used to create a link that covers the entire area of the element without encapsulating it in a link tag. + * This is useful to avoid nesting links inside links. + */ +export const LinkOverlay = React.forwardRef(function LinkOverlay( + props: LinkProps, + ref: React.Ref +) { + const { children, className, ...domProps } = props; + return ( + + {children} + + ); +}); + /** * Check if a link is external, compared to an origin. */