SEO-Friendly URLs in Nuxt
- Use hyphens (not underscores), consistent casing, and concise readable slugs
- Both paths and query parameters can identify indexable content
- Canonicalize equivalent URLs; keep distinct pagination pages self-canonical
URLs appear in search results before users click. /blog/vue-seo-guide tells users what to expect; /p?id=847 doesn't. Search engines also use URLs to understand page hierarchy and relevance.
Nuxt's file-based routing generates SEO-friendly URLs automatically from your pages/ directory structure.
Quick Setup
File-based routing in pages/:
pages/
blog/
[slug].vue → /blog/vue-seo-guide
products/
[category]/
[slug].vue → /products/phones/iphone-15<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>URL Formatting Rules
Hyphens Over Underscores
Google treats hyphens as word separators. Underscores connect words into single terms.
✅ /performance-optimization → "performance" + "optimization"
❌ /performance_optimization → "performanceoptimization"Nuxt file structure:
pages/
learn-vue-router.vue ✅ Good
learn_vue_router.vue ❌ BadLowercase Only
URLs are case-sensitive. /About, /about, and /ABOUT are different pages. This creates duplicate content issues.
✅ /about
✅ /products/phones
❌ /About
❌ /products/PhonesUse kebabCase from scule to generate lowercase slugs automatically.
Keep URLs Short
Keep URLs concise enough to read and share. Google does not document a ranking advantage for a 60-character cutoff.
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)Keywords Near the Start
Google's own guidance is that a URL should describe the page's content clearly so users and crawlers can identify the concept at a glance. Front-load the important terms.
✅ /vue-router-seo-guide
✅ /seo/vue-best-practices
❌ /guides-and-tutorials-for-seo-in-vue-routerBut don't sacrifice readability:
pages/
vue-seo/
[topic].vue ✅ Natural keyword placement
vue-seo-guide-vue-router-seo-tutorial.vue ❌ Keyword stuffingAvoid Dates (Usually)
Dates in URLs can look stale when an evergreen guide changes. They do not prevent you from updating the content.
❌ /blog/2024/vue-router-guide (looks stale)
❌ /blog/2024/12/17/post-title (dated path for evergreen content)
✅ /blog/vue-router-guide (can be updated anytime)Exception: Time-sensitive content like news, events, changelogs:
pages/
changelog/
[year]/
[month]/
[slug].vue → /changelog/2025/12/new-feature
events/
2025/
[slug].vue → /events/2025/nuxt-conf
blog/
[slug].vue → /blog/vue-router-guide (evergreen)Choose the URL before publishing. If a dated URL already exists, updating the article does not require changing it.
Path Segments vs Query Parameters
Both path segments and query parameters can identify indexable pages. Choose a stable structure that describes the content and avoids unnecessary duplicate URLs.
Comparison:
| Type | Example | SEO Impact |
|---|---|---|
| Path segments | /products/phones/iphone-15 | Readable content path |
| 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=2These URLs may represent duplicates or distinct content. Pagination and filters can change the items shown. Google spends crawl time on the duplicates instead of new or updated pages. See Query Parameters for handling strategies.
Nuxt Dynamic Routes
Dynamic segments are one way to give content stable paths:
pages/
products/
[category]/
[slug].vue → /products/phones/iphone-15
blog/
[year]/
[month]/
[slug].vue → /blog/2025/12/nuxt-seo-guide
docs/
[section]/
[page].vue → /docs/getting-started/installationQuery parameters can represent sorting, filtering, or pagination. Consolidate only equivalent pages; distinct paginated pages need their own canonical URLs. See the Query Parameters guide for implementation details including canonical composables and parameter ordering.
Dynamic Routes
The data examples below cover initial page setup. Your app must provide the named endpoints and fields. Add route-change handling if a page component remains mounted for another route.
Nuxt's file-based routing creates SEO-friendly URLs automatically. Descriptive slugs help readers understand the destination; they do not guarantee higher rankings than IDs.
Basic Dynamic Segments
pages/
blog/
[slug].vue → /blog/:slug<script setup lang="ts">
const route = useRoute()
const slug = route.params.slug
// Fetch content based on slug
const { data: post, error } = await useFetch<{ title: string, excerpt: string }>(`/api/posts/${encodeURIComponent(String(slug))}`)
if (error.value)
throw error.value
if (!post.value)
throw createError({ statusCode: 404, statusMessage: 'Post not found' })
// Set meta tags
useSeoMeta({
title: () => post.value?.title,
description: () => post.value?.excerpt,
ogUrl: `https://mysite.com/blog/${slug}`
})
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/blog/${slug}`
}]
})
</script>Nested Dynamic Routes
pages/
products/
[category]/
[slug].vue → /products/:category/:slugGenerates hierarchical URLs:
/products/electronics/laptop/products/clothing/jacket
<script setup lang="ts">
const route = useRoute()
const category = route.params.category
const slug = route.params.slug
const { data: product, error } = await useFetch<{ name: string, description: string }>(
`/api/products/${encodeURIComponent(String(category))}/${encodeURIComponent(String(slug))}`
)
if (error.value)
throw error.value
if (!product.value)
throw createError({ statusCode: 404, statusMessage: 'Product not found' })
useSeoMeta({
title: () => `${product.value?.name} - ${category}`,
description: () => product.value?.description
})
</script>Catch-All Routes
pages/
search/
[...params].vue → /search/:params*Matches:
/search/vue-router/search/vue-router/recent/search/vue-router/recent/2025
Important: canonicalize catch-all variations only when they represent equivalent content. This initial-load excerpt assumes later segments do not change the page:
<script setup lang="ts">
const route = useRoute()
const query = Array.isArray(route.params.params)
? route.params.params[0]
: route.params.params
useHead({
link: [{
rel: 'canonical',
href: `https://mysite.com/search/${query}`
}]
})
</script>Slug Generation
Use kebabCase from scule (already a Nuxt dependency):
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.
URL Hierarchy and Internal Linking
Use a hierarchy that helps readers navigate. Internal links provide crawl paths; path depth alone does not establish page importance.
Use internal links to make important pages easy to reach. Use hub pages (like /features) that link to all child pages, creating a clear topic cluster.
When generating pages at scale (programmatic SEO), use URL patterns that include target keywords:
pages/
compare/
[slug].vue → /compare/pocketui-vs-css-peeper
alternatives/
[slug].vue → /alternatives/colorzilla
features/
[slug].vue → /features/color-extractionEach pattern targets a different keyword cluster. Keep slugs descriptive: /compare/pocketui-vs-css-peeper rather than /compare/1, so both users and crawlers know what the page compares before they open it.
Testing URL Structure
View in search results:
# Test how Google displays your URLs
site:yoursite.com "nuxt seo"Check canonicalization:
Use Google Search Console URL Inspection to verify:
- User-declared canonical matches your intent
- Google-selected canonical agrees with your preference
- No conflicting signals from redirects or alternates
Checklist
- URLs use hyphens, not underscores, to separate words
- All paths are lowercase
- Indexed content has stable paths or query URLs
- Canonical URLs distinguish equivalent variations from distinct content
- Consider dated paths before publishing; retain established URLs when updating
- Slugs are descriptive (
laptop-pro-15, not84792)
Pre-launch setup, post-launch verification, and ongoing monitoring. Interactive checklist with links to every guide.