Skip to content

Commit 16d8e18

Browse files
divineBobbieGoede
andauthored
feat: opt-in alternate link consistency (#3320)
Co-authored-by: Bobbie Goede <bobbiegoede@gmail.com>
1 parent 9f47117 commit 16d8e18

File tree

18 files changed

+186
-47
lines changed

18 files changed

+186
-47
lines changed

docs/content/docs/4.api/0.options.md

+6
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,12 @@ This feature relies on [Nuxt's `experimental.typedRoutes`](https://nuxt.com/docs
511511
Changing this will also change the paths in `locales` returned by `useI18n()`{lang="ts"}.
512512
::
513513

514+
### `alternateLinkCanonicalQueries`
515+
516+
- type: `boolean`{lang="ts-type"}
517+
- default: `false`{lang="ts"}
518+
- Whether to remove non-canonical query parameters from alternate link meta tags
519+
514520
## customBlocks
515521

516522
Configure the `i18n` custom blocks of SFC.

specs/basic_usage.spec.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ describe('basic usage', async () => {
2828
i18n: {
2929
baseUrl: 'http://localhost:3000',
3030
skipSettingLocaleOnNavigate: undefined,
31-
detectBrowserLanguage: undefined
31+
detectBrowserLanguage: undefined,
32+
experimental: {
33+
alternateLinkCanonicalQueries: false
34+
}
3235
}
3336
}
3437
}
@@ -415,13 +418,35 @@ describe('basic usage', async () => {
415418
}
416419
})
417420

418-
const html = await $fetch('/?noncanonical')
421+
const html = await $fetch('/?noncanonical&canonical')
419422
const dom = getDom(html)
420423
await assertLocaleHeadWithDom(dom, '#home-use-locale-head')
421424

422425
const links = getDataFromDom(dom, '#home-use-locale-head').link
423426
const i18nCan = links.find(x => x.id === 'i18n-can')
424427
expect(i18nCan.href).toContain(configDomain)
428+
expect(dom.querySelector('#i18n-alt-fr').href).toEqual(
429+
'https://runtime-config-domain.com/fr?noncanonical&canonical'
430+
)
431+
432+
await restore()
433+
})
434+
435+
test('render seo tags with `experimental.alternateLinkCanonicalQueries`', async () => {
436+
const restore = await startServerWithRuntimeConfig({
437+
public: {
438+
i18n: {
439+
experimental: {
440+
alternateLinkCanonicalQueries: true
441+
}
442+
}
443+
}
444+
})
445+
446+
// head tags - alt links are updated server side
447+
const html = await $fetch('/?noncanonical&canonical')
448+
const dom = getDom(html)
449+
expect(dom.querySelector('#i18n-alt-fr').href).toEqual('http://localhost:3000/fr?canonical=')
425450

426451
await restore()
427452
})
@@ -468,8 +493,10 @@ describe('basic usage', async () => {
468493

469494
// Translated params are not lost on query changes
470495
await page.locator('#params-add-query').click()
471-
await waitForURL(page, '/nl/products/rode-mok?test=123')
472-
expect(await page.locator('#nuxt-locale-link-en').getAttribute('href')).toEqual('/products/red-mug?test=123')
496+
await waitForURL(page, '/nl/products/rode-mok?test=123&canonical=123')
497+
expect(await page.locator('#nuxt-locale-link-en').getAttribute('href')).toEqual(
498+
'/products/red-mug?test=123&canonical=123'
499+
)
473500

474501
await page.locator('#params-remove-query').click()
475502
await waitForURL(page, '/nl/products/rode-mok')

specs/basic_usage_compat_4.spec.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ describe('basic usage - compatibilityVersion: 4', async () => {
2828
i18n: {
2929
baseUrl: 'http://localhost:3000',
3030
skipSettingLocaleOnNavigate: undefined,
31-
detectBrowserLanguage: undefined
31+
detectBrowserLanguage: undefined,
32+
experimental: {
33+
alternateLinkCanonicalQueries: false
34+
}
3235
}
3336
}
3437
}
@@ -415,13 +418,35 @@ describe('basic usage - compatibilityVersion: 4', async () => {
415418
}
416419
})
417420

418-
const html = await $fetch('/?noncanonical')
421+
const html = await $fetch('/?noncanonical&canonical')
419422
const dom = getDom(html)
420423
await assertLocaleHeadWithDom(dom, '#home-use-locale-head')
421424

422425
const links = getDataFromDom(dom, '#home-use-locale-head').link
423426
const i18nCan = links.find(x => x.id === 'i18n-can')
424427
expect(i18nCan.href).toContain(configDomain)
428+
expect(dom.querySelector('#i18n-alt-fr').href).toEqual(
429+
'https://runtime-config-domain.com/fr?noncanonical&canonical'
430+
)
431+
432+
await restore()
433+
})
434+
435+
test('render seo tags with `experimental.alternateLinkCanonicalQueries`', async () => {
436+
const restore = await startServerWithRuntimeConfig({
437+
public: {
438+
i18n: {
439+
experimental: {
440+
alternateLinkCanonicalQueries: true
441+
}
442+
}
443+
}
444+
})
445+
446+
// head tags - alt links are updated server side
447+
const html = await $fetch('/?noncanonical&canonical')
448+
const dom = getDom(html)
449+
expect(dom.querySelector('#i18n-alt-fr').href).toEqual('http://localhost:3000/fr?canonical=')
425450

426451
await restore()
427452
})
@@ -468,8 +493,10 @@ describe('basic usage - compatibilityVersion: 4', async () => {
468493

469494
// Translated params are not lost on query changes
470495
await page.locator('#params-add-query').click()
471-
await waitForURL(page, '/nl/products/rode-mok?test=123')
472-
expect(await page.locator('#nuxt-locale-link-en').getAttribute('href')).toEqual('/products/red-mug?test=123')
496+
await waitForURL(page, '/nl/products/rode-mok?test=123&canonical=123')
497+
expect(await page.locator('#nuxt-locale-link-en').getAttribute('href')).toEqual(
498+
'/products/red-mug?test=123&canonical=123'
499+
)
473500

474501
await page.locator('#params-remove-query').click()
475502
await waitForURL(page, '/nl/products/rode-mok')

specs/experimental/switch_locale_path_link_ssr.spec.ts

+38-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, expect, describe } from 'vitest'
22
import { fileURLToPath } from 'node:url'
33
import { $fetch, setup } from '../utils'
4-
import { getDom, gotoPath, renderPage, waitForURL } from '../helper'
4+
import { getDom, gotoPath, renderPage, startServerWithRuntimeConfig, waitForURL } from '../helper'
55

66
await setup({
77
rootDir: fileURLToPath(new URL(`../fixtures/basic_usage`, import.meta.url)),
@@ -12,13 +12,15 @@ await setup({
1212
runtimeConfig: {
1313
public: {
1414
i18n: {
15-
baseUrl: ''
15+
baseUrl: '',
16+
alternateLinkCanonicalQueries: false
1617
}
1718
}
1819
},
1920
i18n: {
2021
experimental: {
21-
switchLocalePathLinkSSR: true
22+
switchLocalePathLinkSSR: true,
23+
alternateLinkCanonicalQueries: false
2224
}
2325
}
2426
}
@@ -35,14 +37,45 @@ describe('experimental.switchLocalePathLinkSSR', async () => {
3537

3638
// Translated params are not lost on query changes
3739
await page.locator('#params-add-query').click()
38-
await waitForURL(page, '/nl/products/rode-mok?test=123')
39-
expect(await page.locator('#switch-locale-path-link-en').getAttribute('href')).toEqual('/products/red-mug?test=123')
40+
await waitForURL(page, '/nl/products/rode-mok?test=123&canonical=123')
41+
expect(await page.locator('#switch-locale-path-link-en').getAttribute('href')).toEqual(
42+
'/products/red-mug?test=123&canonical=123'
43+
)
4044

4145
await page.locator('#params-remove-query').click()
4246
await waitForURL(page, '/nl/products/rode-mok')
4347
expect(await page.locator('#switch-locale-path-link-en').getAttribute('href')).toEqual('/products/red-mug')
4448
})
4549

50+
test('respects `experimental.alternateLinkCanonicalQueries`', async () => {
51+
const restore = await startServerWithRuntimeConfig({
52+
public: {
53+
i18n: {
54+
experimental: {
55+
switchLocalePathLinkSSR: true,
56+
alternateLinkCanonicalQueries: true
57+
}
58+
}
59+
}
60+
})
61+
62+
// head tags - alt links are updated server side
63+
const product1Html = await $fetch('/products/big-chair?test=123&canonical=123')
64+
const product1Dom = getDom(product1Html)
65+
expect(product1Dom.querySelector('#i18n-alt-nl').href).toEqual('/nl/products/grote-stoel?canonical=123')
66+
expect(product1Dom.querySelector('#switch-locale-path-link-nl').href).toEqual(
67+
'/nl/products/grote-stoel?test=123&canonical=123'
68+
)
69+
70+
const product2Html = await $fetch('/nl/products/rode-mok?test=123&canonical=123')
71+
const product2dom = getDom(product2Html)
72+
expect(product2dom.querySelector('#i18n-alt-en').href).toEqual('/products/red-mug?canonical=123')
73+
expect(product2dom.querySelector('#switch-locale-path-link-en').href).toEqual(
74+
'/products/red-mug?test=123&canonical=123'
75+
)
76+
await restore()
77+
})
78+
4679
test('dynamic parameters rendered correctly during SSR', async () => {
4780
// head tags - alt links are updated server side
4881
const product1Html = await $fetch('/products/big-chair')

specs/fixtures/basic_usage/layouts/default.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useI18n, useLocaleHead } from '#i18n'
55
66
const route = useRoute()
77
const { t } = useI18n()
8-
const head = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page'] } })
8+
const head = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page', 'canonical'] } })
99
const title = computed(() => `Page - ${t(route.meta?.title ?? '')}`)
1010
</script>
1111

specs/fixtures/basic_usage/nuxt.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default defineNuxtConfig({
2222
locales: ['en', 'fr'],
2323
defaultLocale: 'en',
2424
experimental: {
25+
alternateLinkCanonicalQueries: false,
2526
autoImportTranslationFunctions: true
2627
}
2728
// debug: true,

specs/fixtures/basic_usage/pages/index.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ definePageMeta({
6161
alias: ['/aliased-home-path']
6262
})
6363
64-
const i18nHead = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page'] } })
64+
const i18nHead = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page', 'canonical'] } })
6565
useHead(() => ({
6666
htmlAttrs: {
6767
lang: i18nHead.value.htmlAttrs!.lang

specs/fixtures/basic_usage/pages/products.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ onMounted(async () => {
1616
<LangSwitcher />
1717
<ul>
1818
<li>
19-
<NuxtLink id="params-add-query" :to="localePath({ query: { test: '123' } })">Add query</NuxtLink>
19+
<NuxtLink id="params-add-query" :to="localePath({ query: { test: '123', canonical: '123' } })"
20+
>Add query</NuxtLink
21+
>
2022
</li>
2123
<li>
2224
<NuxtLink id="params-remove-query" :to="localePath({ query: undefined })">Remove query</NuxtLink>

specs/fixtures/basic_usage/pages/products/[slug].vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const product = ref()
66
const { locale } = useI18n()
77
const route = useRoute()
88
9-
const setI18nParams = useSetI18nParams()
9+
const setI18nParams = useSetI18nParams({ canonicalQueries: ['canonical'] })
1010
product.value = await $fetch(`/api/products/${route.params.slug}`)
1111
if (product.value != null) {
1212
const availableLocales = Object.keys(product.value.slugs)

specs/fixtures/basic_usage_compat_4/app/layouts/default.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useI18n, useLocaleHead } from '#i18n'
55
66
const route = useRoute()
77
const { t } = useI18n()
8-
const head = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page'] } })
8+
const head = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page', 'canonical'] } })
99
const title = computed(() => `Page - ${t(route.meta?.title ?? '')}`)
1010
</script>
1111

specs/fixtures/basic_usage_compat_4/app/pages/index.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ definePageMeta({
6161
alias: ['/aliased-home-path']
6262
})
6363
64-
const i18nHead = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page'] } })
64+
const i18nHead = useLocaleHead({ key: 'id', seo: { canonicalQueries: ['page', 'canonical'] } })
6565
useHead(() => ({
6666
htmlAttrs: {
6767
lang: i18nHead.value.htmlAttrs!.lang

specs/fixtures/basic_usage_compat_4/app/pages/products.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ onMounted(async () => {
1616
<LangSwitcher />
1717
<ul>
1818
<li>
19-
<NuxtLink id="params-add-query" :to="localePath({ query: { test: '123' } })">Add query</NuxtLink>
19+
<NuxtLink id="params-add-query" :to="localePath({ query: { test: '123', canonical: '123' } })"
20+
>Add query</NuxtLink
21+
>
2022
</li>
2123
<li>
2224
<NuxtLink id="params-remove-query" :to="localePath({ query: undefined })">Remove query</NuxtLink>

specs/fixtures/basic_usage_compat_4/app/pages/products/[slug].vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const product = ref()
66
const { locale } = useI18n()
77
const route = useRoute()
88
9-
const setI18nParams = useSetI18nParams()
9+
const setI18nParams = useSetI18nParams({ canonicalQueries: ['canonical'] })
1010
product.value = await $fetch(`/api/products/${route.params.slug}`)
1111
if (product.value != null) {
1212
const availableLocales = Object.keys(product.value.slugs)

specs/fixtures/basic_usage_compat_4/nuxt.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default defineNuxtConfig({
2323
vueI18n: './config/i18n.config.ts',
2424
defaultLocale: 'en',
2525
experimental: {
26+
alternateLinkCanonicalQueries: false,
2627
autoImportTranslationFunctions: true,
2728
localeDetector: './localeDetector.ts'
2829
},

src/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export const DEFAULT_OPTIONS = {
3232
autoImportTranslationFunctions: false,
3333
typedPages: true,
3434
typedOptionsAndMessages: false,
35-
generatedLocaleFilePathFormat: 'absolute'
35+
generatedLocaleFilePathFormat: 'absolute',
36+
alternateLinkCanonicalQueries: false
3637
},
3738
bundle: {
3839
compositionOnly: true,

src/runtime/composables/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export function useSetI18nParams(seo?: SeoAttributesOptions): SetI18nParamsFunct
9393

9494
// prettier-ignore
9595
metaObject.link.push(
96-
...getHreflangLinks(common, locales, key),
96+
...getHreflangLinks(common, locales, key, seo),
9797
...getCanonicalLink(common, key, seo)
9898
)
9999

0 commit comments

Comments
 (0)