diff --git a/docs/content/0.nuxt-seo/2.guides/2.default-meta.md b/docs/content/0.nuxt-seo/2.guides/2.default-meta.md index aefb8126..1f743500 100644 --- a/docs/content/0.nuxt-seo/2.guides/2.default-meta.md +++ b/docs/content/0.nuxt-seo/2.guides/2.default-meta.md @@ -6,15 +6,36 @@ description: The default meta tags Nuxt SEO sets for you. To ensure your site is SEO friendly, Nuxt SEO sets some default meta tags for you based on your [site config](/nuxt-seo/guides/configuring-modules). -## Canonical +## Canonical URL -Ensuring a canonical URL is set helps avoid [duplicate content issues](https://support.google.com/webmasters/answer/66359?hl=en) -when you have multiple domains or subdomains pointing to the same content. +Ensuring a canonical URL is set helps avoid [duplicate content issues](https://support.google.com/webmasters/answer/66359?hl=en). -It can also occur when you have multiple URLs for the same page, such as when you don't redirect -[trailing slashes](/nuxt-seo/guides/trailing-slashes). +This can be an issue when you have multiple domains or subdomains pointing to the same content, +[trailing slashes](/nuxt-seo/guides/trailing-slashes) and non-trailing slashes showing the same content +and when you have query parameters that don't change the content. -The canonical will be set based on your site config `url` and the current route. +The canonical URL is generated from your site config `url`, the current route and the `canonicalQueryWhitelist`. + +### canonicalQueryWhitelist + +By default, the `canonicalQueryWhitelist` includes a number of common query parameters that will modify the page content: + +- `page` +- `sort` +- `filter` +- `search` +- `q` +- `query` + +You can override this by providing your own list of query parameters that should be included in the canonical URL. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + seo: { + canonicalQueryWhitelist: ['myCustomQuery'] + } +}) +``` ## I18n diff --git a/src/module.ts b/src/module.ts index 217de1c9..70f20ed4 100644 --- a/src/module.ts +++ b/src/module.ts @@ -26,6 +26,10 @@ export interface ModuleOptions { * @default true */ automaticDefaults?: boolean + /** + * When enabled, it will whitelist the query parameters that are allowed in the canonical URL. + */ + canonicalQueryWhitelist?: string[] /** * When enabled, it will redirect any request to the canonical domain (site url) using a 301 redirect on non-dev environments. * @@ -105,6 +109,18 @@ export default defineNuxtModule({ for (const module of Modules) await installModule(await resolvePath(module)) + nuxt.options.runtimeConfig.public['nuxt-seo'] = { + canonicalQueryWhitelist: config.canonicalQueryWhitelist || [ + 'page', + 'sort', + 'filter', + 'search', + 'q', + 'category', + 'tag', + ], + } + if (config.automaticDefaults) { // i18n complicates things, we need to run the server plugin at the right time, client is fine if (hasNuxtModule('@nuxtjs/i18n')) { diff --git a/src/runtime/nuxt/logic/applyDefaults.ts b/src/runtime/nuxt/logic/applyDefaults.ts index 689b6742..8ec21a4c 100644 --- a/src/runtime/nuxt/logic/applyDefaults.ts +++ b/src/runtime/nuxt/logic/applyDefaults.ts @@ -1,9 +1,12 @@ import type { UseHeadOptions, UseSeoMetaInput } from '@unhead/vue' +import type { QueryObject } from 'ufo' +import { stringifyQuery } from 'ufo' import { computed, createSitePathResolver, useHead, useRoute, + useRuntimeConfig, useSeoMeta, useServerHead, useSiteConfig, @@ -11,10 +14,21 @@ import { export function applyDefaults() { // get the head instance + const { canonicalQueryWhitelist } = useRuntimeConfig().public['nuxt-seo'] const siteConfig = useSiteConfig() const route = useRoute() const resolveUrl = createSitePathResolver({ withBase: true, absolute: true }) - const canonicalUrl = computed(() => resolveUrl(route.path || '/').value || route.path) + const canonicalUrl = computed(() => { + const { query } = route + const url = resolveUrl(route.path || '/').value || route.path + // apply canonicalQueryWhitelist to query + const filteredQuery = Object.fromEntries( + Object.entries(query).filter(([key]) => canonicalQueryWhitelist.includes(key)), + ) as QueryObject + return Object.keys(filteredQuery).length + ? `${url}?${stringifyQuery(filteredQuery)}` + : url + }) const minimalPriority: UseHeadOptions = { // give nuxt.config values higher priority @@ -24,7 +38,7 @@ export function applyDefaults() { // needs higher priority useHead({ link: [{ rel: 'canonical', href: () => canonicalUrl.value }], - }) + }, minimalPriority) const locale = siteConfig.currentLocale || siteConfig.defaultLocale if (locale) { useServerHead({