@@ -40,6 +40,26 @@ export const LinkSettingsContext = React.createContext<{
40
40
externalLinksTarget : SiteExternalLinksTarget . Self ,
41
41
} ) ;
42
42
43
+ /**
44
+ * Get the target and rel props for a link based on the provided props and context.
45
+ */
46
+ function getTargetProps (
47
+ props : Pick < LinkProps , 'href' | 'rel' | 'target' > ,
48
+ context : {
49
+ externalLinksTarget : SiteExternalLinksTarget ;
50
+ isExternal : boolean ;
51
+ }
52
+ ) {
53
+ const target =
54
+ props . target ??
55
+ ( context . isExternal && context . externalLinksTarget === SiteExternalLinksTarget . Blank
56
+ ? '_blank'
57
+ : undefined ) ;
58
+ // Automatically set rel if target is _blank, or use the specified rel.
59
+ const rel = props . rel ?? ( target === '_blank' ? 'noopener noreferrer' : undefined ) ;
60
+ return { target, rel } ;
61
+ }
62
+
43
63
/**
44
64
* Low-level Link component that handles navigation to external urls.
45
65
* It does not contain any styling.
@@ -52,46 +72,50 @@ export const Link = React.forwardRef(function Link(
52
72
const { externalLinksTarget } = React . useContext ( LinkSettingsContext ) ;
53
73
const trackEvent = useTrackEvent ( ) ;
54
74
const forwardedClassNames = useClassnames ( classNames || [ ] ) ;
55
-
56
- // Use a real anchor tag for external links,s and a Next.js Link for internal links.
57
- // If we use a NextLink for external links, Nextjs won't rerender the top-level layouts.
58
- const isExternal = URL . canParse ? URL . canParse ( props . href ) : props . href . startsWith ( 'http' ) ;
75
+ const isExternal = isExternalLink ( href ) ;
76
+ const { target, rel } = getTargetProps ( props , { externalLinksTarget, isExternal } ) ;
59
77
60
78
const onClick = ( event : React . MouseEvent < HTMLAnchorElement > ) => {
79
+ const isExternal = isExternalLink ( href , window . location . origin ) ;
80
+
61
81
if ( insights ) {
62
- trackEvent ( insights , undefined , {
63
- immediate : isExternal ,
64
- } ) ;
82
+ trackEvent ( insights , undefined , { immediate : isExternal } ) ;
65
83
}
66
84
67
- if (
68
- isExternalLink ( href , window . location . origin ) &&
69
- // When the page is embedded in an iframe, for security reasons other urls cannot be opened.
70
- // In this case, we open the link in a new tab.
71
- ( window . self !== window . top ||
72
- // If the site is configured to open links in a new tab
73
- externalLinksTarget === SiteExternalLinksTarget . Blank )
74
- ) {
85
+ const isInIframe = window . self !== window . top ;
86
+
87
+ // When the page is embedded in an iframe
88
+ // for security reasons other urls cannot be opened.
89
+ if ( isInIframe && isExternal ) {
90
+ event . preventDefault ( ) ;
91
+ window . open ( href , '_blank' , 'noopener noreferrer' ) ;
92
+ } else {
93
+ // The external logic server-side is limited
94
+ // so we use the client-side logic to determine the real target
95
+ // by default the target is "_self".
96
+ const { target = '_self' } = getTargetProps ( props , {
97
+ externalLinksTarget,
98
+ isExternal,
99
+ } ) ;
75
100
event . preventDefault ( ) ;
76
- window . open ( href , '_blank' ) ;
101
+ window . open ( href , target , rel ) ;
77
102
}
78
103
79
104
domProps . onClick ?.( event ) ;
80
105
} ;
81
106
82
107
// We test if the link is external, without comparing to the origin
83
108
// as this will be rendered on the server and it could result in a mismatch.
84
- if ( isExternalLink ( href ) ) {
109
+ if ( isExternal ) {
85
110
return (
86
111
< a
87
112
ref = { ref }
88
113
className = { tcls ( ...forwardedClassNames , className ) }
89
114
{ ...domProps }
90
115
href = { href }
91
116
onClick = { onClick }
92
- { ...( externalLinksTarget === SiteExternalLinksTarget . Blank && ! domProps . target
93
- ? { target : '_blank' , rel : 'noopener noreferrer' }
94
- : { } ) }
117
+ target = { target }
118
+ rel = { rel }
95
119
>
96
120
{ children }
97
121
</ a >
0 commit comments