diff --git a/packages/gitbook/src/components/Header/HeaderLogo.tsx b/packages/gitbook/src/components/Header/HeaderLogo.tsx
index fc52ec5949..1218717ec0 100644
--- a/packages/gitbook/src/components/Header/HeaderLogo.tsx
+++ b/packages/gitbook/src/components/Header/HeaderLogo.tsx
@@ -20,7 +20,7 @@ export async function HeaderLogo(props: HeaderLogoProps) {
return (
{customization.header.logo ? (
diff --git a/packages/gitbook/src/components/primitives/Link.tsx b/packages/gitbook/src/components/primitives/Link.tsx
index bff7eadc28..3398f7dc00 100644
--- a/packages/gitbook/src/components/primitives/Link.tsx
+++ b/packages/gitbook/src/components/primitives/Link.tsx
@@ -40,6 +40,26 @@ export const LinkSettingsContext = React.createContext<{
externalLinksTarget: SiteExternalLinksTarget.Self,
});
+/**
+ * Get the target and rel props for a link based on the provided props and context.
+ */
+function getTargetProps(
+ props: Pick,
+ context: {
+ externalLinksTarget: SiteExternalLinksTarget;
+ isExternal: boolean;
+ }
+) {
+ const target =
+ props.target ??
+ (context.isExternal && context.externalLinksTarget === SiteExternalLinksTarget.Blank
+ ? '_blank'
+ : undefined);
+ // Automatically set rel if target is _blank, or use the specified rel.
+ const rel = props.rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined);
+ return { target, rel };
+}
+
/**
* Low-level Link component that handles navigation to external urls.
* It does not contain any styling.
@@ -52,28 +72,33 @@ export const Link = React.forwardRef(function Link(
const { externalLinksTarget } = React.useContext(LinkSettingsContext);
const trackEvent = useTrackEvent();
const forwardedClassNames = useClassnames(classNames || []);
-
- // Use a real anchor tag for external links,s and a Next.js Link for internal links.
- // If we use a NextLink for external links, Nextjs won't rerender the top-level layouts.
- const isExternal = URL.canParse ? URL.canParse(props.href) : props.href.startsWith('http');
+ const isExternal = isExternalLink(href);
+ const { target, rel } = getTargetProps(props, { externalLinksTarget, isExternal });
const onClick = (event: React.MouseEvent) => {
+ const isExternalWithOrigin = isExternalLink(href, window.location.origin);
+
if (insights) {
- trackEvent(insights, undefined, {
- immediate: isExternal,
- });
+ trackEvent(insights, undefined, { immediate: isExternalWithOrigin });
}
- if (
- isExternalLink(href, window.location.origin) &&
- // When the page is embedded in an iframe, for security reasons other urls cannot be opened.
- // In this case, we open the link in a new tab.
- (window.self !== window.top ||
- // If the site is configured to open links in a new tab
- externalLinksTarget === SiteExternalLinksTarget.Blank)
- ) {
+ const isInIframe = window.self !== window.top;
+
+ // When the page is embedded in an iframe
+ // for security reasons other urls cannot be opened.
+ if (isInIframe && isExternalWithOrigin) {
+ event.preventDefault();
+ window.open(href, '_blank', 'noopener noreferrer');
+ } else {
+ // The external logic server-side is limited
+ // so we use the client-side logic to determine the real target
+ // by default the target is "_self".
+ const { target = '_self' } = getTargetProps(props, {
+ externalLinksTarget,
+ isExternal: isExternalWithOrigin,
+ });
event.preventDefault();
- window.open(href, '_blank');
+ window.open(href, target, rel);
}
domProps.onClick?.(event);
@@ -81,7 +106,7 @@ export const Link = React.forwardRef(function Link(
// We test if the link is external, without comparing to the origin
// as this will be rendered on the server and it could result in a mismatch.
- if (isExternalLink(href)) {
+ if (isExternal) {
return (
{children}