---
title: "Canonical URLs in Vue · Nuxt SEO"
canonical_url: "https://nuxtseo.com/learn-seo/vue/controlling-crawlers/canonical-urls"
last_updated: "2026-07-16T12:00:00.000Z"
meta:
  author: "Harlan Wilton"
  description: "Add canonical URLs in Vue with @unhead/vue and Vue Router so duplicate pages don't split your rankings across builds."
  "og:description": "Add canonical URLs in Vue with @unhead/vue and Vue Router so duplicate pages don't split your rankings across builds."
  "og:title": "Canonical URLs in Vue · Nuxt SEO"
---

Nuxt SEO on GitHub

# **Canonical URLs in Vue**

Add canonical URLs in Vue with @unhead/vue and Vue Router so duplicate pages don't split your rankings across builds.

[Harlan Wilton](https://x.com/harlan-zw)10 mins read Published **Nov 3, 2024** Updated **Jul 16, 2026**

**What you'll learn**

- 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**`, not `**/page**`)
- Self-referencing canonicals are recommended even for unique pages

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.

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

## Quick Setup

Add canonical URLs to your Vue pages using Unhead composables:

Basic Usage

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

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

With Query Params

```vue
<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>
```

Cross Domain

```vue
<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 does not require any canonicalization method, and a site can rank fine without specifying a preference at all. Google weighs your signal alongside other factors when it decides which URL to show in 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.

### Important Notes

[**~~Self-referencing canonicals are best practice~~**](https://searchengineland.com/canonicalization-seo-448161) even for pages with a single, unique source of content: they establish a clear preferred URL and prevent search engines from guessing when tracking parameters or alternate URL formats appear.

- 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)
- Best served in the initial HTML: AI crawlers like GPTBot and ClaudeBot [**~~never execute JavaScript~~**](https://vercel.com/blog/the-rise-of-the-ai-crawler), so a canonical injected client-side is invisible to them
- Include canonical pages in [**~~sitemaps~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps), exclude non-canonical pages
- Don't combine a canonical with [**~~noindex meta robots~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags): they send conflicting signals

Canonicals only work when the target page has substantially similar content to the source. Pointing a canonical at a different page or your homepage doesn't consolidate rankings; Google is likely to ignore it.

## Common Patterns

### Filter and Sort Parameters

pages/products/\[category].vue

```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>
```

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

### Mobile/Desktop Versions

If you serve separate mobile URLs (`**m.domain.com**`), [**~~the desktop URL is always the canonical~~**](https://developers.google.com/search/docs/crawling-indexing/mobile/mobile-sites-mobile-first-indexing): point `**rel=canonical**` on the mobile page to the desktop URL, and `**rel=alternate**` on the desktop page to the mobile URL.

pages/products/\[id].vue

```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>
```

Responsive design with a single URL avoids needing this canonical/alternate pairing at all, and is what Google recommends for new sites.

### 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; a canonical tag doesn't reliably stop a syndicated copy from outranking the original. Have syndication partners block indexing with noindex meta robots instead.

pages/article/\[slug].vue

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

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

pages/original-article.vue

```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>
```

## Testing

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~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/duplicate-content#finding-duplicate-content).

- 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~~**](https://nuxtseo.com/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~~**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects) rather than canonicals:

```ts
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()
})
```

### Dynamic Canonicals

For dynamic routes, ensure canonical URLs are consistent:

composables/useCanonical.ts

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

## Checklist

**Checklist**

- Every route has a self-referencing canonical, even pages with no duplicates
- Canonical URLs are absolute ( `**https://...**`), never relative
- Only one canonical tag exists per page
- Canonicals ship in the server-rendered HTML, not injected client-side
- Paginated pages each get their own canonical, not all pointing to page 1
- Canonical and noindex are never combined on the same page

If you're using Nuxt, check out [**~~Nuxt SEO~~**](https://nuxtseo.com/docs/nuxt-seo/getting-started/introduction) which handles much of this automatically: [**~~canonical URLs in Nuxt →~~**](https://nuxtseo.com/learn-seo/nuxt/controlling-crawlers/canonical-urls)

[**The 2026 SEO Checklist for Nuxt & Vue ** Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.](https://nuxtseo.com/learn-seo/checklist) [Haven't launched yet? Start with the **Pre-Launch Warmup**](https://nuxtseo.com/learn-seo/pre-launch-warmup)

---

### **Related **

[**Controlling Crawlers**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers)

[**Sitemaps**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/sitemaps)

[**Meta Robots**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags)

[**HTTP Redirects**](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects)

[**Social Sharing Meta Tags**](https://nuxtseo.com/learn-seo/vue/mastering-meta/social-sharing)

[**Robot Meta Tag** Set noindex, nofollow, and AI Overview opt-outs with useSeoMeta in Vue, and avoid the mistakes that silently deindex working pages.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/meta-tags) [**HTTP Redirects** Implement server-side 301 redirects in Vue SSR (Express, Vite, H3) so migrated pages keep their rankings and avoid redirect chains.](https://nuxtseo.com/learn-seo/vue/controlling-crawlers/redirects)

**On this page**

- [Quick Setup](#quick-setup)
- [Understanding Canonical URLs](#understanding-canonical-urls)
- [Common Patterns](#common-patterns)
- [Testing](#testing)
- [Handling Edge Cases](#handling-edge-cases)
- [Checklist](#checklist)