URLs appear in search results before users click. /blog/vue-seo-guide tells users what to expect. /p?id=847 doesn't. Search engines use URLs to understand page hierarchy and relevance. Well-structured URLs improve click-through rates by up to 15%.
For Vue applications requiring SSR, use Unhead for meta tags and configure Vue Router with proper slug patterns.
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/blog/:slug',
component: BlogPost
},
{
path: '/products/:category/:slug',
component: ProductPage
}
]
})
<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug
// Set canonical URL
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/blog/${slug}`
}]
})
</script>
For Vue applications, you'll need to install Unhead manually.
Google treats hyphens as word separators. Underscores connect words into single terms.
✅ /performance-optimization → "performance" + "optimization"
❌ /performance_optimization → "performanceoptimization"
Google's Matt Cutts confirmed in 2011: "We use the words in a URL as a very lightweight factor... we can't easily segment at underscores."
Vue Router implementation:
// ✅ Good
{ path: '/learn-vue-router', component: Guide }
// ❌ Bad
{ path: '/learn_vue_router', component: Guide }
URLs are case-sensitive. /About, /about, and /ABOUT are different pages. This creates duplicate content issues.
✅ /about
✅ /products/phones
❌ /About
❌ /products/Phones
Use kebabCase from scule to generate lowercase slugs.
URLs under 60 characters perform better in search results. Longer URLs get truncated with ellipsis.
Length comparison:
| URL | Length | Result |
|---|---|---|
/blog/vue-seo | 14 chars | ✅ Displays fully |
/blog/comprehensive-guide-to-vue-seo-optimization | 50 chars | ⚠️ Works but verbose |
/blog/a-comprehensive-guide-to-vue-server-side-rendering-seo-optimization-best-practices | 98 chars | ❌ Truncated in results |
When longer URLs make sense:
✅ /docs/getting-started/installation-guide (clear hierarchy)
✅ /blog/2025/fixing-vue-hydration-mismatch (date + topic)
❌ /the-ultimate-comprehensive-complete-guide (keyword stuffing)
Including keywords in URLs provides a lightweight ranking boost. Front-load important terms.
✅ /vue-router-seo-guide
✅ /seo/vue-best-practices
❌ /guides-and-tutorials-for-seo-in-vue-router
But don't sacrifice readability:
// ✅ Natural keyword placement
{ path: '/vue-seo/:topic', component: Guide }
// ❌ Keyword stuffing
{ path: '/vue-seo-guide-vue-router-seo-tutorial', component: Guide }
Dates in URLs prevent content updates. /blog/2024/vue-guide becomes outdated when you refresh it in 2025.
❌ /blog/2024/vue-router-guide (looks stale)
❌ /blog/2024/12/17/post-title (prevents evergreen updates)
✅ /blog/vue-router-guide (can be updated anytime)
Exception: Time-sensitive content like news, events, changelogs:
const routes = [
// News/events - dates make sense
{ path: '/changelog/:year/:month/:slug', component: Changelog },
{ path: '/events/2025/:slug', component: Event },
// Evergreen content - skip dates
{ path: '/blog/:slug', component: BlogPost },
{ path: '/guides/:slug', component: Guide }
]
Removing dates allows republishing old posts with new content without changing URLs. a strong SEO strategy.
Search engines prefer path segments over query parameters. Path segments are indexed and ranked. Query parameters often cause duplicate content.
Comparison:
| Type | Example | SEO Impact |
|---|---|---|
| Path segments | /products/phones/iphone-15 | ✅ Clean, indexed, ranks well |
| Query parameters | /products?category=phones&id=15 | ⚠️ Duplicate content risk |
| Mixed | /products/phones?sort=price | ✅ Path for content, query for filters |
Problems with query parameters:
/products
/products?sort=price
/products?sort=date
/products?page=2
/products?sort=price&page=2
Five URLs, same content. Google sees duplicate content and wastes crawl budget.
Use dynamic segments for content that should be indexed:
const routes = [
// ✅ Path segments for SEO
{
path: '/products/:category/:slug',
component: Product
},
{
path: '/blog/:year/:month/:slug',
component: BlogPost
},
{
path: '/docs/:section/:page',
component: Documentation
}
]
This generates clean URLs:
/products/phones/iphone-15/blog/2025/12/vue-seo-guide/docs/getting-started/installationUse query parameters for sorting, filtering, pagination. features that modify display without changing core content:
<script setup lang="ts">
const route = useRoute()
const category = route.params.category
const sort = route.query.sort || 'popular'
const page = route.query.page || '1'
// Canonical URL excludes query params
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/products/${category}`
}]
})
</script>
<template>
<div>
<!-- URL: /products/phones?sort=price&page=2 -->
<!-- Canonical: /products/phones -->
</div>
</template>
Set canonical URLs to consolidate ranking signals:
// Filter/sort variations point to base URL
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/products/${category}`
}]
})
Vue Router's dynamic routes create SEO-friendly URLs. Use descriptive slugs rather than database IDs—/products/laptop-pro-15 ranks better than /products/84792.
const routes = [
{
path: '/blog/:slug',
component: BlogPost,
props: true
}
]
<script setup lang="ts">
const props = defineProps<{ slug: string }>()
// Fetch content based on slug
const { data: post } = await useFetch(`/api/posts/${props.slug}`)
// Set meta tags
useSeoMeta({
title: post.value.title,
description: post.value.excerpt,
ogUrl: `https://mysite.com/blog/${props.slug}`
})
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/blog/${props.slug}`
}]
})
</script>
const routes = [
{
path: '/products/:category/:slug',
component: Product,
props: true
}
]
Generates hierarchical URLs:
/products/electronics/laptop/products/clothing/jacket<script setup lang="ts">
const props = defineProps<{
category: string
slug: string
}>()
const { data: product } = await useFetch(
`/api/products/${props.category}/${props.slug}`
)
useSeoMeta({
title: `${product.value.name} - ${props.category}`,
description: product.value.description
})
</script>
const routes = [
{
path: '/search/:query/:filters?',
component: SearchResults
}
]
Matches both:
/search/vue-router/search/vue-router/recentImportant: Optional parameters create multiple URLs for similar content. Use canonical tags:
const route = useRoute()
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/search/${route.params.query}`
}]
})
Use kebabCase from scule:
import { kebabCase } from 'scule'
kebabCase('Vue Router Guide') // "vue-router-guide"
kebabCase('50% Off Sale!') // "50-off-sale"
For international content, Google supports UTF-8 URLs but they must be percent-encoded when sent over HTTP. Consider whether your audience expects localized slugs or ASCII-only.
Vue Router's History mode requires server configuration. All routes must serve index.html:
location / {
try_files $uri $uri/ /index.html;
}
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import express from 'express'
const app = express()
const __dirname = dirname(fileURLToPath(import.meta.url))
// Serve static files
app.use(express.static(join(__dirname, 'dist')))
// All routes return index.html
app.get('*', (req, res) => {
res.sendFile(join(__dirname, 'dist', 'index.html'))
})
app.listen(3000)
import { join } from 'node:path'
import { defineEventHandler, readFile, sendRedirect } from 'h3'
export default defineEventHandler(async (event) => {
const path = event.node.req.url
// Try to serve static file
try {
const file = await readFile(join('./dist', path))
return file
}
catch {
// Fall back to index.html for client-side routing
return await readFile('./dist/index.html')
}
})
Without this configuration, direct URLs like /blog/vue-seo return 404 errors.
View in search results:
# Test how Google displays your URLs
site:yoursite.com "vue router"
Check canonicalization:
Use Google Search Console URL Inspection to verify:
If you're using Nuxt, file-based routing generates URLs automatically. The Nuxt SEO module handles canonical URLs, sitemaps, and route rules.