---
title: "Canonical URLs in Vue"
description: "Canonical URLs tell search engines which version of a page to index when duplicate content exists. Here's how to set them up in Vue."
canonical_url: "https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls"
last_updated: "2026-01-29"
---

<key-takeaways>

- Vue Router doesn't set canonicals automatically; use `@unhead/vue` to add them per route
- Canonical tags are hints, not directives. Google may choose a different canonical
- Always use absolute URLs ([https://mysite.com/page](https://mysite.com/page), not /page)
- Self-referencing canonicals are recommended even for unique pages

</key-takeaways>

In Vue applications, canonical URLs require manual setup using `@unhead/vue` and Vue Router. Unlike Nuxt, which handles canonicals automatically through site config, Vue developers must explicitly set the `rel="canonical"` link tag for every route. [67.6% of websites have duplicate content issues](https://www.womenintechseo.com/knowledge/dealing-with-duplicate-content-canonicalization-in-detail/) due to poor canonicalization.

Use canonicals for URLs with query parameters (filters, sorting), same content on multiple paths, paginated sequences, and cross-domain syndication. For choosing between canonicals and redirects, see [Duplicate Content](/learn-seo/vue/controlling-crawlers/duplicate-content). For redirecting users, use [HTTP redirects](/learn-seo/vue/controlling-crawlers/redirects) instead. For blocking pages from search, use [meta robots](/learn-seo/vue/controlling-crawlers/meta-tags) with noindex.

## Quick Setup

Add canonical URLs to your Vue pages using Unhead composables:

```vue [Basic Usage]
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  link: [
    {
      rel: 'canonical',
      href: 'https://mysite.com/products/phone'
    }
  ]
})
</script>
```

```vue [With Query Params]
<script setup lang="ts">
import { useHead } from '@unhead/vue'

const sort = 'price'

// Keep sort parameter in canonical
useHead({
  link: [
    {
      rel: 'canonical',
      href: `https://mysite.com/products?sort=${sort}`
    }
  ]
})
</script>
```

```vue [Cross Domain]
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  link: [
    {
      rel: 'canonical',
      href: 'https://otherdomain.com/original-article'
    }
  ]
})
</script>
```

For Vue applications, you'll need to [install Unhead manually](https://unhead.unjs.io/guide/getting-started/installation).

## Understanding Canonical URLs

A canonical URL is implemented as a link tag in your page's head:

```html
<link rel="canonical" href="https://mysite.com/page">
```

### Canonical Tags Are Hints, Not Directives

Google treats canonicals as [strong signals, not mandatory rules](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls). Google's Joachim Kupke: "It's a hint that we honor strongly. We'll take your preference into account, in conjunction with other signals, when calculating the most relevant page to display in search results."

Google may choose a different canonical than you specify when:

- Content differs significantly between URLs
- Multiple conflicting canonical declarations exist
- Google believes a different page is more authoritative

[Google uses the canonical page](https://developers.google.com/search/docs/crawling-indexing/canonicalization) as the main source to evaluate content and quality. Non-canonical URLs may still be crawled but usually won't appear in search results.

### Self-Referencing Canonicals

[Self-referencing canonicals are recommended](https://searchengineland.com/canonicalization-seo-448161) even for unique pages. They establish a clear preferred URL and prevent search engines from guessing when tracking parameters or alternate URL formats appear.

Google's John Mueller: "I recommend <span>

using a

</span>

 self-referential canonical because it makes it clear to us which page you want to have indexed, or what the URL should be when it is indexed."

### Important Notes

- Must use absolute URLs ([Google documentation](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls))
- Only one canonical per page (multiple declarations cause Google to ignore all hints)
- Must be server-side rendered (crawlers don't run JavaScript)
- Include canonical pages in [sitemaps](/learn-seo/vue/controlling-crawlers/sitemaps), exclude non-canonical pages
- Don't combine canonical with noindex [meta robots](/learn-seo/vue/controlling-crawlers/meta-tags). sends conflicting signals

<warning>

Google may ignore your canonical if the target page's content differs significantly from the source. Canonicals should point to pages with substantially similar content. not a different page or your homepage.

</warning>

## Common Patterns

### Filter and Sort Parameters

```vue [pages/products/[category].vue]
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const { sort } = route.query
const category = route.params.category

useHead({
  link: [{
    rel: 'canonical',
    // Only include sort in canonical, remove filter and pagination
    href: sort
      ? `https://mysite.com/products/${category}?sort=${sort}`
      : `https://mysite.com/products/${category}`
  }]
})
</script>
```

### Pagination

Paginated pages need self-referencing canonicals. [Don't point them all to page 1](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading), because each page in the sequence has unique content and should be indexed separately. See the [Pagination SEO guide](/learn-seo/vue/routes-and-rendering/pagination) for implementation details.

### Mobile/Desktop Versions

If you have separate mobile URLs (m.domain.com), [keep the desktop URL as canonical](https://www.lumar.io/office-hours/separate-mobile/) even with mobile-first indexing. Google uses canonicals to understand which pages belong together, then internally selects the mobile version for indexing.

```vue [pages/products/[id].vue]
<script setup lang="ts">
import { useHead } from '@unhead/vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const id = route.params.id

useHead({
  link: [{
    rel: 'canonical',
    // Mobile site (m.mysite.com) points to desktop
    href: `https://mysite.com/products/${id}`
  }]
})
</script>
```

[Don't switch canonicals from desktop to mobile URLs](https://www.seroundtable.com/google-m-dot-urls-the-canonical-37809.html). Google advises against this. Use responsive design with a single URL instead.

### Cross-Domain Syndication

[Google no longer recommends cross-domain canonicals](https://searchengineland.com/google-no-longer-recommends-canonical-tags-for-syndicated-content-406491) for syndicated content. Instead, syndication partners should use noindex meta robots to block indexing.

```vue [pages/article/[slug].vue]
<script setup lang="ts">
import { useSeoMeta } from '@unhead/vue'

// If syndicating TO other sites, have them use noindex
useSeoMeta({
  robots: 'noindex, follow'
})
</script>
```

```vue [pages/original-article.vue]
<script setup lang="ts">
import { useHead } from '@unhead/vue'

// If this IS the original, use self-referencing canonical
useHead({
  link: [{
    rel: 'canonical',
    href: 'https://mysite.com/articles/original-slug'
  }]
})
</script>
```

Exception: News publishers syndicating to Google News should still use cross-domain canonicals per [Google's news publisher guidance](https://developers.google.com/search/blog/2009/12/handling-legitimate-cross-domain).

## Testing

### Verifying Canonicals

Use [Google Search Console's URL Inspection tool](https://support.google.com/webmasters/answer/9012289) to compare your declared canonical with Google's selected canonical. For a full detection workflow, see [Duplicate Content](/learn-seo/vue/controlling-crawlers/duplicate-content#finding-duplicate-content).

### Important Checks

- Validate absolute URL format
- Check for canonical chains (A → B → C)
- Verify SSR implementation (view page source, not inspected HTML)
- Test with and without parameters
- Don't combine canonical with noindex [meta robots](/learn-seo/vue/controlling-crawlers/meta-tags)

## Handling Edge Cases

### Multiple Language Versions

For multilingual sites, combine canonicals with hreflang:

```vue
<script setup lang="ts">
import { useHead } from '@unhead/vue'

useHead({
  link: [
    {
      rel: 'canonical',
      href: 'https://mysite.com/en/page'
    },
    {
      rel: 'alternate',
      hreflang: 'fr',
      href: 'https://mysite.com/fr/page'
    }
  ]
})
</script>
```

### Protocol/WWW Variations

Handle through [server redirects](/learn-seo/vue/controlling-crawlers/redirects) rather than canonicals:

<code-group>

```ts [Express]
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')
  if (!host.startsWith('www.')) {
    return res.redirect(301, `https://www.${host}${req.path}`)
  }
  next()
})
```

```ts [Vite]
// server.js for Vite SSR
import express from 'express'

const app = express()

app.use((req, res, next) => {
  const host = req.get('host')
  if (!host.startsWith('www.')) {
    return res.redirect(301, `https://www.${host}${req.path}`)
  }
  next()
})
```

```ts [H3]
import { defineEventHandler, getRequestHost, sendRedirect } from 'h3'

export default defineEventHandler((event) => {
  const host = getRequestHost(event)
  if (!host.startsWith('www.')) {
    return sendRedirect(event, `https://www.${host}${event.path}`, 301)
  }
})
```

</code-group>

### Dynamic Canonicals

For dynamic routes, ensure canonical URLs are consistent:

```ts [composables/useCanonical.ts]
export function getCanonical(path: string) {
  const siteUrl = import.meta.env.VITE_SITE_URL
  return {
    link: [{
      rel: 'canonical',
      href: `${siteUrl}${path}`
    }]
  }
}
```

## Using Nuxt?

If you're using Nuxt, check out [Nuxt SEO](/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically.

[Learn more about canonical URLs in Nuxt →](/learn-seo/nuxt/controlling-crawlers/canonical-urls)
