From fe47ed6109d9dc1cc0a7de79fd12f78064bf25a7 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sat, 3 Feb 2024 14:59:29 +1100 Subject: [PATCH 1/7] fix(nuxt): apply default `NuxtLink` rel `noreferrer` to absolute urls --- docs/3.api/1.components/4.nuxt-link.md | 8 ++++---- packages/nuxt/src/app/components/nuxt-link.ts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/3.api/1.components/4.nuxt-link.md b/docs/3.api/1.components/4.nuxt-link.md index 7c2c0f6e296a..f86585b6279a 100644 --- a/docs/3.api/1.components/4.nuxt-link.md +++ b/docs/3.api/1.components/4.nuxt-link.md @@ -34,7 +34,7 @@ In this example, we use `` component to link to a website. Nuxt website - + ``` @@ -47,7 +47,7 @@ In this example, we use `` with `target`, `rel`, and `noRel` props. Nuxt Twitter - + Nuxt Discord @@ -62,7 +62,7 @@ In this example, we use `` with `target`, `rel`, and `noRel` props. Contact page opens in another tab - + ``` @@ -113,7 +113,7 @@ defineNuxtLink({ ``` - `componentName`: A name for the defined `` component. -- `externalRelAttribute`: A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable +- `externalRelAttribute`: A default `rel` attribute value applied on external links. Defaults to `"noreferrer"`. Set it to `""` to disable - `activeClass`: A default class to apply on active links. Works the same as [Vue Router's `linkActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkActiveClass). Defaults to Vue Router's default (`"router-link-active"`) - `exactActiveClass`: A default class to apply on exact active links. Works the same as [Vue Router's `linkExactActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkExactActiveClass). Defaults to Vue Router's default (`"router-link-exact-active"`) - `prefetchedClass`: A default class to apply to links that have been prefetched. diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 88a0081bc129..058ae9e4e666 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -14,7 +14,7 @@ import { nuxtLinkDefaults } from '#build/nuxt.config.mjs' const firstNonUndefined = (...args: (T | undefined)[]) => args.find(arg => arg !== undefined) -const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noopener noreferrer' +const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noreferrer' const NuxtLinkDevKeySymbol: InjectionKey = Symbol('nuxt-link-dev-key') export type NuxtLinkOptions = { @@ -181,7 +181,7 @@ 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 })) // Resolving link type const isExternal = computed(() => { @@ -200,7 +200,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { return false } - return to.value === '' || isProtocolURL.value + return to.value === '' || isAbsoluteUrl.value }) // Prefetching @@ -285,7 +285,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 @@ -297,7 +297,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { const rel = (props.noRel) ? null // converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`) - : firstNonUndefined(props.rel, options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : '') || null + : firstNonUndefined(props.rel, ...(isAbsoluteUrl.value ? [options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : ''] : [])) || null const navigate = () => navigateTo(href, { replace: props.replace }) From 84fa5e3010c801feac750b21cd2d4f0f1446f3de Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sat, 3 Feb 2024 15:49:46 +1100 Subject: [PATCH 2/7] chore: also apply to new tabs of relative links --- packages/nuxt/src/app/components/nuxt-link.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 058ae9e4e666..8fba8fa1df31 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -14,7 +14,6 @@ import { nuxtLinkDefaults } from '#build/nuxt.config.mjs' const firstNonUndefined = (...args: (T | undefined)[]) => args.find(arg => arg !== undefined) -const DEFAULT_EXTERNAL_REL_ATTRIBUTE = 'noreferrer' const NuxtLinkDevKeySymbol: InjectionKey = Symbol('nuxt-link-dev-key') export type NuxtLinkOptions = { @@ -183,6 +182,8 @@ export function defineNuxtLink (options: NuxtLinkOptions) { // Lazily check whether to.value has a protocol 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(() => { // External prop is explicitly set @@ -191,7 +192,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 } @@ -292,12 +293,18 @@ export function defineNuxtLink (options: NuxtLinkOptions) { // Resolves `target` value const target = props.target || null + /* + * A fallback rel of `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. + */ + const fallbackRel = (isAbsoluteUrl.value || hasTarget.value) ? + [options.externalRelAttribute, href ? 'noreferrer' : ''] : [] // Resolves `rel` checkPropConflicts(props, 'noRel', 'rel') const rel = (props.noRel) ? null // converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`) - : firstNonUndefined(props.rel, ...(isAbsoluteUrl.value ? [options.externalRelAttribute, href ? DEFAULT_EXTERNAL_REL_ATTRIBUTE : ''] : [])) || null + : firstNonUndefined(props.rel, ...fallbackRel) || null const navigate = () => navigateTo(href, { replace: props.replace }) From 12734ec8dcea31057d2e797384a415e072c39cae Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sat, 3 Feb 2024 15:55:20 +1100 Subject: [PATCH 3/7] chore: keep using both for time being --- packages/nuxt/src/app/components/nuxt-link.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 8fba8fa1df31..9a64b0929788 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -293,18 +293,18 @@ export function defineNuxtLink (options: NuxtLinkOptions) { // Resolves `target` value const target = props.target || null - /* - * A fallback rel of `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. - */ - const fallbackRel = (isAbsoluteUrl.value || hasTarget.value) ? - [options.externalRelAttribute, href ? 'noreferrer' : ''] : [] // Resolves `rel` checkPropConflicts(props, 'noRel', 'rel') - const rel = (props.noRel) - ? null + const rel = firstNonUndefined( // converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`) - : firstNonUndefined(props.rel, ...fallbackRel) || null + props.noRel ? '' : undefined, props.rel, + options.externalRelAttribute, + /* + * A fallback rel of `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 }) From fa8d7be217850491dc75aae2e3ab1c238a724ffb Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sat, 3 Feb 2024 16:12:01 +1100 Subject: [PATCH 4/7] doc: document why we add a `rel` --- docs/3.api/1.components/4.nuxt-link.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/3.api/1.components/4.nuxt-link.md b/docs/3.api/1.components/4.nuxt-link.md index f86585b6279a..f727170b850c 100644 --- a/docs/3.api/1.components/4.nuxt-link.md +++ b/docs/3.api/1.components/4.nuxt-link.md @@ -38,9 +38,15 @@ In this example, we use `` component to link to a website. ``` -## `target` and `rel` Attributes +## Best Practice Rel -In this example, we use `` 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] ``` @@ -119,7 +119,7 @@ defineNuxtLink({ ``` - `componentName`: A name for the defined `` component. -- `externalRelAttribute`: A default `rel` attribute value applied on external links. Defaults to `"noreferrer"`. Set it to `""` to disable +- `externalRelAttribute`: A default `rel` attribute value applied on external links. Defaults to `"noopener noreferrer"`. Set it to `""` to disable - `activeClass`: A default class to apply on active links. Works the same as [Vue Router's `linkActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkActiveClass). Defaults to Vue Router's default (`"router-link-active"`) - `exactActiveClass`: A default class to apply on exact active links. Works the same as [Vue Router's `linkExactActiveClass` option](https://router.vuejs.org/api/interfaces/RouterOptions.html#Properties-linkExactActiveClass). Defaults to Vue Router's default (`"router-link-exact-active"`) - `prefetchedClass`: A default class to apply to links that have been prefetched. From 09dd3a96983adf2b45f93c1c353e3c7d0f456007 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sun, 4 Feb 2024 21:35:48 +0000 Subject: [PATCH 6/7] chore: update docs --- docs/3.api/1.components/4.nuxt-link.md | 4 ++-- packages/nuxt/src/app/components/nuxt-link.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/3.api/1.components/4.nuxt-link.md b/docs/3.api/1.components/4.nuxt-link.md index a89dae905cbf..8ab808d05a46 100644 --- a/docs/3.api/1.components/4.nuxt-link.md +++ b/docs/3.api/1.components/4.nuxt-link.md @@ -34,11 +34,11 @@ In this example, we use `` component to link to a website. Nuxt website - + ``` -## Best Practice Rel +## `target` and `rel` Attributes 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. diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index 9a64b0929788..b160701536c6 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -300,7 +300,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { props.noRel ? '' : undefined, props.rel, options.externalRelAttribute, /* - * A fallback rel of `noreferrer` is applied for external links or links that open in a new tab. + * 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' : '' From 2e0e37d509007a2a91fdde567281be1db7a9cc06 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sun, 4 Feb 2024 21:38:13 +0000 Subject: [PATCH 7/7] perf: slightly simplify --- packages/nuxt/src/app/components/nuxt-link.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nuxt/src/app/components/nuxt-link.ts b/packages/nuxt/src/app/components/nuxt-link.ts index b160701536c6..a88cebbc6412 100644 --- a/packages/nuxt/src/app/components/nuxt-link.ts +++ b/packages/nuxt/src/app/components/nuxt-link.ts @@ -297,7 +297,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) { checkPropConflicts(props, 'noRel', 'rel') const rel = firstNonUndefined( // converts `""` to `null` to prevent the attribute from being added as empty (`rel=""`) - props.noRel ? '' : undefined, props.rel, + 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.