Skip to content

Commit 7d5a6d2

Browse files
conico974Nicolas Dorseuil
andauthored
Fix hydration error on V2 (#3272)
Co-authored-by: Nicolas Dorseuil <[email protected]>
1 parent a1da4f0 commit 7d5a6d2

File tree

4 files changed

+69
-15
lines changed

4 files changed

+69
-15
lines changed

.changeset/cold-buckets-divide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gitbook": patch
3+
---
4+
5+
fix nested a tag causing hydration error

packages/gitbook/src/components/DocumentView/Table/RecordCard.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
SiteInsightsLinkPosition,
55
} from '@gitbook/api';
66

7-
import { Link } from '@/components/primitives';
7+
import { LinkBox, LinkOverlay } from '@/components/primitives';
88
import { Image } from '@/components/utils';
99
import { resolveContentRef } from '@/lib/references';
1010
import { type ClassValue, tcls } from '@/lib/tailwind';
@@ -44,7 +44,6 @@ export async function RecordCard(
4444
<div
4545
className={tcls(
4646
'grid-area-1-1',
47-
'z-0',
4847
'relative',
4948
'grid',
5049
'bg-tint-base',
@@ -151,7 +150,6 @@ export async function RecordCard(
151150
'rounded-md',
152151
'straight-corners:rounded-none',
153152
'dark:shadow-transparent',
154-
'z-0',
155153

156154
'before:pointer-events-none',
157155
'before:grid-area-1-1',
@@ -167,19 +165,22 @@ export async function RecordCard(
167165

168166
if (target && targetRef) {
169167
return (
170-
<Link
171-
href={target.href}
172-
className={tcls(style, 'hover:before:ring-tint-12/5')}
173-
insights={{
174-
type: 'link_click',
175-
link: {
176-
target: targetRef,
177-
position: SiteInsightsLinkPosition.Content,
178-
},
179-
}}
180-
>
168+
// We don't use `Link` directly here because we could end up in a situation where
169+
// a link is rendered inside a link, which is not allowed in HTML.
170+
// It causes an hydration error in React.
171+
<LinkBox href={target.href} className={tcls(style, 'hover:before:ring-tint-12/5')}>
172+
<LinkOverlay
173+
href={target.href}
174+
insights={{
175+
type: 'link_click',
176+
link: {
177+
target: targetRef,
178+
position: SiteInsightsLinkPosition.Content,
179+
},
180+
}}
181+
/>
181182
{body}
182-
</Link>
183+
</LinkBox>
183184
);
184185
}
185186

packages/gitbook/src/components/RootLayout/globals.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@
148148
width: 100%;
149149
}
150150
}
151+
152+
.elevate-link {
153+
& a[href]:not(.link-overlay) {
154+
position: relative;
155+
z-index: 20;
156+
}
157+
}
151158
}
152159

153160
html {

packages/gitbook/src/components/primitives/Link.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import NextLink, { type LinkProps as NextLinkProps } from 'next/link';
44
import React from 'react';
55

6+
import { tcls } from '@/lib/tailwind';
67
import { type TrackEventInput, useTrackEvent } from '../Insights';
78

89
// Props from Next, which includes NextLinkProps and all the things anchor elements support.
@@ -75,6 +76,46 @@ export const Link = React.forwardRef(function Link(
7576
);
7677
});
7778

79+
/**
80+
* A box used to contain a link overlay.
81+
* It is used to create a clickable area that can contain other elements.
82+
*/
83+
export const LinkBox = React.forwardRef(function LinkBox(
84+
props: React.BaseHTMLAttributes<HTMLDivElement>,
85+
ref: React.Ref<HTMLDivElement>
86+
) {
87+
const { children, className, ...domProps } = props;
88+
return (
89+
<div ref={ref} {...domProps} className={tcls('elevate-link relative', className)}>
90+
{children}
91+
</div>
92+
);
93+
});
94+
95+
/**
96+
* A link overlay that can be used to create a clickable area on top of other elements.
97+
* It is used to create a link that covers the entire area of the element without encapsulating it in a link tag.
98+
* This is useful to avoid nesting links inside links.
99+
*/
100+
export const LinkOverlay = React.forwardRef(function LinkOverlay(
101+
props: LinkProps,
102+
ref: React.Ref<HTMLAnchorElement>
103+
) {
104+
const { children, className, ...domProps } = props;
105+
return (
106+
<Link
107+
ref={ref}
108+
{...domProps}
109+
className={tcls(
110+
'link-overlay static before:absolute before:top-0 before:left-0 before:z-10 before:h-full before:w-full',
111+
className
112+
)}
113+
>
114+
{children}
115+
</Link>
116+
);
117+
});
118+
78119
/**
79120
* Check if a link is external, compared to an origin.
80121
*/

0 commit comments

Comments
 (0)