Skip to content

Commit

Permalink
fix(nuxt): don't set default rel for same-site external links (#25600)
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Feb 4, 2024
1 parent 82173ad commit b78e1cb
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 9 deletions.
8 changes: 7 additions & 1 deletion docs/3.api/1.components/4.nuxt-link.md
Expand Up @@ -57,7 +57,13 @@ In this example, we use `<NuxtLink>` component to link to a website.

## `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]
<template>
Expand Down
23 changes: 15 additions & 8 deletions packages/nuxt/src/app/components/nuxt-link.ts
Expand Up @@ -20,7 +20,6 @@ import { nuxtLinkDefaults } from '#build/nuxt.config.mjs'

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')

/**
Expand Down Expand Up @@ -229,7 +228,9 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
})

// 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
const isExternal = computed<boolean>(() => {
Expand All @@ -239,7 +240,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
}

// When `target` prop is set, link is external
if (props.target && props.target !== '_self') {
if (hasTarget.value) {
return true
}

Expand All @@ -248,7 +249,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
return false
}

return to.value === '' || isProtocolURL.value
return to.value === '' || isAbsoluteUrl.value
})

// Prefetching
Expand Down Expand Up @@ -333,7 +334,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
// converts `""` to `null` to prevent the attribute from being added as empty (`href=""`)
const href = typeof to.value === 'object'
? 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
: to.value || null

Expand All @@ -342,10 +343,16 @@ export function defineNuxtLink (options: NuxtLinkOptions) {

// Resolves `rel`
checkPropConflicts(props, 'noRel', 'rel')
const rel = (props.noRel)
? null
const rel = firstNonUndefined<string | null>(
// 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 })

Expand Down

0 comments on commit b78e1cb

Please sign in to comment.