Canonical URL Plugin for Consistent Head Tags
Implementation overview
Use the canonical plugin to keep canonical links, Open Graph URLs, and language alternates aligned. This guide covers URL resolution, query filtering, custom canonical values, and duplicate prevention.
The Canonical plugin converts relative URLs in supported existing tags to absolute URLs. It does not create a canonical tag. Enable it with CanonicalPlugin({ canonicalHost: 'https://mysite.com' }) in your head configuration.
Why absolute URLs matter
The Canonical Plugin converts relative URLs to absolute URLs in supported meta and link tags. This matches the URL guidance for the two main standards it targets:
- Google Search canonicalization: Relative canonical paths are supported, but Google recommends absolute paths to avoid long-term configuration mistakes
- The Open Graph protocol: Its URL type uses
http://orhttps://, includingog:url, image, audio, and video properties
Transformed tags
The plugin resolves relative URLs to absolute URLs in all of these tags:
Meta Tags
og:url,og:image,og:image:url,og:image:secure_urlog:video,og:video:url,og:video:secure_urlog:audio,og:audio:url,og:audio:secure_urltwitter:image,twitter:image:srctwitter:player,twitter:player:stream
Link Tags
rel="canonical": Query filtering, hash stripping, and trailing slash normalizationrel="next"/rel="prev": Pagination linksrel="alternate":hreflangand feed linksrel="author",rel="license",rel="help",rel="search",rel="pingback"
<meta property="og:image" content="/images/hero.jpg">
<link rel="alternate" hreflang="es" href="/es/page">
<meta property="og:image" content="https://mysite.com/images/hero.jpg">
<link rel="alternate" hreflang="es" href="https://mysite.com/es/page">
Setup
Install the plugin in both your server and client entries:
import { CanonicalPlugin } from '#imports/plugins'
const head = createHead({
plugins: [
CanonicalPlugin({
canonicalHost: 'https://mysite.com'
})
]
})
Query parameter filtering
Tracking parameters such as utm_source, fbclid, and gclid can expose the same content at many URLs. Google recommends trimming parameters that do not change page content. The plugin strips all query parameters from canonical and og:url tags by default, so whitelist every parameter that identifies distinct content.
<link rel="canonical" href="https://mysite.com/blog?page=2&utm_source=twitter&fbclid=abc">
<link rel="canonical" href="https://mysite.com/blog">
Preserving specific parameters
If your site uses query parameters that affect content (e.g., pagination, filters), pass them via queryWhitelist:
CanonicalPlugin({
canonicalHost: 'https://mysite.com',
queryWhitelist: ['page', 'sort', 'q', 'category']
})
Common parameters you may want to whitelist:
page: Paginationsort: Sort orderfilter: Content filterssearch,q: Search queriescategory,tag: Category/tag filterslang,locale: Language variants
Disabling query filtering
Set queryWhitelist to false to keep all query parameters:
CanonicalPlugin({
canonicalHost: 'https://mysite.com',
queryWhitelist: false
})
rel="canonical" and og:url tags. Image and video URLs (og:image, twitter:image, etc.) are never filtered, since their query parameters often control dimensions and formats.Trailing slash normalization
If your server exposes /about and /about/ as separate URLs for the same content, use trailingSlash to emit one consistent canonical form:
// Always add trailing slash
CanonicalPlugin({
canonicalHost: 'https://mysite.com',
trailingSlash: true
})
// Always remove trailing slash
CanonicalPlugin({
canonicalHost: 'https://mysite.com',
trailingSlash: false
})
/ is never stripped of its trailing slash, even when trailingSlash is false.URL fragments
Hash fragments (e.g., #section) are automatically removed from canonical and og:url tags. Google generally does not support fragments in canonical URLs.
Configuration options
interface CanonicalPluginOptions {
// Your site's domain; required during SSR
canonicalHost?: string
// Optional: Custom function to transform URLs
customResolver?: (path: string) => string
// Query parameters to preserve (default: []; strips all)
// Set to false to disable filtering
queryWhitelist?: string[] | false
// Normalize trailing slashes (true = add, false = remove, undefined = leave as-is)
trailingSlash?: boolean
}
Omitting canonicalHost
- If no
canonicalHostis provided:- Client-side: Uses
window.location.origin - SSR: Throws while initializing the plugin because no valid base URL is available
- Client-side: Uses
canonicalHost explicitly for consistent behavior across environments.Custom URL resolution
Use customResolver to implement custom URL transformation logic:
CanonicalPlugin({
canonicalHost: 'https://mysite.com',
customResolver: path => /^https?:\/\//i.test(path) || path.startsWith('//')
? path
: new URL(`/cdn/${path.replace(/^\/+/, '')}`, 'https://example.com').toString()
})
Example transformation:
<meta property="og:image" content="/hero.jpg">
<meta property="og:image" content="https://example.com/cdn/hero.jpg">
CDN integration
The custom resolver receives every supported URL, including URLs that are already absolute. Preserve those before routing relative image paths to a CDN:
CanonicalPlugin({
canonicalHost: 'https://mysite.com',
customResolver: (path) => {
if (/^https?:\/\//i.test(path) || path.startsWith('//'))
return path
// Send image paths to CDN, keep other URLs on main domain
if (path.match(/\.(jpg|png|webp|gif|svg)$/i))
return new URL(path, 'https://cdn.mysite.com').toString()
return new URL(path, 'https://mysite.com').toString()
}
})
Framework Setup Guides
Register the plugin when creating your head instance:
import { createHead } from '@unhead/vue/client'
import { CanonicalPlugin } from 'unhead/plugins'
const head = createHead({
plugins: [
CanonicalPlugin({
canonicalHost: 'https://mysite.com'
})
]
})
app.use(head)
Related
- Template Params: Dynamic template parameters
- Infer SEO Meta: Auto-generate SEO tags