fix(nuxt): don't set default rel for same-site external links (#25600)

This commit is contained in:
Harlan Wilton 2024-02-05 09:21:39 +11:00 committed by GitHub
parent 82173ad1a9
commit b78e1cb206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 22 additions and 9 deletions

View File

@ -57,7 +57,13 @@ In this example, we use `<NuxtLink>` component to link to a website.
## `target` and `rel` Attributes ## `target` and `rel` Attributes
In this example, we use `<NuxtLink>` with `target`, `rel`, and `noRel` props. A `rel` attribute of `noopener noreferrer` is applied by default to absolute links and links that open in new tabs.
- `noopener` solves a [security bug](https://mathiasbynens.github.io/rel-noopener/) in older browsers.
- `noreferrer` improves privacy for your users by not sending the `Referer` header to the linked site.
These defaults have no negative impact on SEO and are considered [best practice](https://developer.chrome.com/docs/lighthouse/best-practices/external-anchors-use-rel-noopener).
When you need to overwrite this behavior you can use the `rel` and `noRel` props.
```vue [app.vue] ```vue [app.vue]
<template> <template>

View File

@ -20,7 +20,6 @@ import { nuxtLinkDefaults } from '#build/nuxt.config.mjs'
const firstNonUndefined = <T> (...args: (T | undefined)[]) => args.find(arg => arg !== undefined) const firstNonUndefined = <T> (...args: (T | undefined)[]) => args.find(arg => arg !== undefined)
const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer'
const NuxtLinkDevKeySymbol: InjectionKey<boolean> = Symbol('nuxt-link-dev-key') const NuxtLinkDevKeySymbol: InjectionKey<boolean> = Symbol('nuxt-link-dev-key')
/** /**
@ -229,7 +228,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
}) })
// Lazily check whether to.value has a protocol // Lazily check whether to.value has a protocol
const isProtocolURL = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true })) const isAbsoluteUrl = computed(() => typeof to.value === 'string' && hasProtocol(to.value, { acceptRelative: true }))
const hasTarget = computed(() => props.target && props.target !== '_self')
// Resolving link type // Resolving link type
const isExternal = computed<boolean>(() => { const isExternal = computed<boolean>(() => {
@ -239,7 +240,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
} }
// When `target` prop is set, link is external // When `target` prop is set, link is external
if (props.target && props.target !== '_self') { if (hasTarget.value) {
return true return true
} }
@ -248,7 +249,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
return false return false
} }
return to.value === '' || isProtocolURL.value return to.value === '' || isAbsoluteUrl.value
}) })
// Prefetching // Prefetching
@ -333,7 +334,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// converts `""` to `null` to prevent the attribute from being added as empty (`href=""`) // converts `""` to `null` to prevent the attribute from being added as empty (`href=""`)
const href = typeof to.value === 'object' const href = typeof to.value === 'object'
? router.resolve(to.value)?.href ?? null ? router.resolve(to.value)?.href ?? null
: (to.value && !props.external && !isProtocolURL.value) : (to.value && !props.external && !isAbsoluteUrl.value)
? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string ? resolveTrailingSlashBehavior(joinURL(config.app.baseURL, to.value), router.resolve) as string
: to.value || null : to.value || null
@ -342,10 +343,16 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// Resolves `rel` // Resolves `rel`
checkPropConflicts(props, 'noRel', 'rel') checkPropConflicts(props, 'noRel', 'rel')
const rel = (props.noRel) const rel = firstNonUndefined<string | null>(
? null
// converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`) // converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`)
: firstNonUndefined<string | null>(props.rel, options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : '') || null props.noRel ? '' : props.rel,
options.externalRelAttribute,
/*
* A fallback rel of `noopener noreferrer` is applied for external links or links that open in a new tab.
* This solves a reverse tabnapping security flaw in browsers pre-2021 as well as improving privacy.
*/
(isAbsoluteUrl.value || hasTarget.value) ? 'noopener noreferrer' : ''
) || null
const navigate = () => navigateTo(href, { replace: props.replace }) const navigate = () => navigateTo(href, { replace: props.replace })