Page Titles in Vue & Nuxt
Introduction
Page titles are crucial for SEO. They're your primary call-to-action in search results and help users understand your page's content and context.
<head>
<title>Mastering Titles in Nuxt · Nuxt SEO<</title>
</head>
While setting page titles in Vue is straightforward, certain scenarios can be tricky. Let's start with the essential patterns you should follow.
Vue Title Best Practices
Here are the key practices for handling page titles in Vue & Nuxt:
1. Use a title template
useHead({
title: 'Home',
titleTemplate: '%s %separator %siteName',
templateParams: {
separator: '·', // choose a seperator, i like using this one
siteName: 'MyApp' // set a site name
}
})
export default defineNuxtConfig({
app: {
head: {
titleTemplate: '%s %separator %siteName',
templateParams: {
seperator: '—', // choose a seperator
siteName: 'MyApp' // set a site name
}
}
}
})
2. Always use reactive data with
Makes sure our page titles are always reactive, I'd personally recommend using computed getter syntax which is the VueUse way.
const title = ref('Loading...')
useHead({
// computed getter syntax
title: () => `${title.value} - hold up!`
})
3. Use
useSeoMeta({
title: 'Home',
description: 'Welcome to MyApp',
})
4. Leverage template params for consistency
One of my favourite ways of using template params is to make sure we always have an
You can use template params in any meta tag, not just the title.
// will always use the page title
useSeoMeta({ ogTitle: '%s' })
- Use Nuxt SEO Utils for advanced features (Nuxt only)
If you're not using Nuxt, or you're not using the Nuxt SEO Utils module, then you're adding a lot of extra work for yourself.
Understanding the Title Tag in Vue
The
In Vue, you might be tempted to set titles directly:
// ❌ Careful! This won't work in SSR
document.title = 'Home'
This approach has two major issues:
It breaks during Server-Side Rendering (SSR) Search engines may not properly index your titles
Instead, use Vue's head manager Unhead (already included in Nuxt). New to SEO titles? Check out Google's guide on Influencing your title links in search results.
useHead() Dynamic Page Titles with
Now that we understand why direct title manipulation won't work, let's use Unhead's
<script setup lang="ts">
useHead({
title: 'Home'
})
</script>
<head>
<title>Home</title>
</head>
This single line creates an SSR-friendly title that search engines can read. The composable handles all the complexity of managing your document head in both client and server environments.
You can use this in any component and set any
<script setup lang="ts">
useHead({
title: 'Home',
meta: [
{ name: 'description', content: 'Welcome to MyApp' }
]
})
useHead() Reactivity with
As we're using Vue, we may be dealing with dynamic data that contains the page title, meaning we can only set the title once the data has been fetched.
Unhead has first-party Vue support, supporting any input as a ref, reactive or computed value.
A red flag you may come across in your code is if you find yourself destructing reactive data before passing it to
useHead({
title: myTitle.value // ❌ Avoid destructuring reactive data
})
Instead, pass the reactive data directly to
useHead({
title: myTitle // ✅ Pass in reactive data, don't destruct it
})
This is important when we may have data that gets refreshed or updated, such as a blog post title or user profile name.
<script setup lang="ts">
const { data } = await useAsyncData(() => fetchPostData())
const title = computed(() => data.value?.title)
useHead({
title,
})
</script>
SEO Concerns
Always set page titles during server-side rendering (SSR) to ensure search engines can read them. While SPA pages may have their titles honored by search engines, it's not guaranteed.
<script setup lang="ts">
const postTitle = ref('Loading...')
useHead({
title: postTitle
})
// ❌ Avoid fetching data onMounted, it won't be available in SSR
onMounted(async () => {
const data = await fetchPostData()
postTitle.value = data.title
})
</script>
<head>
<title>Loading...</title>
</head>
Setting Up Title Templates in Vue
You may notice that most people set up their titles with a site name and a separator, this is seen as a best practice as it can help with brand recognition and SEO.
<head>
<title>Home | MySite</title>
</head>
Creating your own title like this is simple using
<script setup lang="ts">
useHead({
title: 'Home',
titleTemplate: '%s %seperator MySite'
})
</script>
<head>
<title>Home | MySite</title>
</head>
Template params like
Template Params
You may ask why we don't just use a function for the title template, and while this is supported, it can create issues with SSR and hydration.
Instead, it's recommended to use the params. Out-of-the-box, Unhead provides:
Token | Description |
---|---|
The current page title. | |
The separator, defaults to a pipe character |. |
The
Define custom template params to maintain consistent formatting:
<script setup lang="ts">
useHead({
title: 'Home',
titleTemplate: '%s %seperator %siteName',
templateParams: {
seperator: '—',
siteName: 'MySite'
}
})
</script>
<head>
<title>Home — MySite</title>
</head>
As we'll likely only ever have one root title template, you can set this globally in your
export default defineNuxtConfig({
app: {
head: {
titleTemplate: '%s %separator %siteName',
}
}
})
I'd suggest choosing your own separator as the
type Seperator = '-' | '—' | '•' | '·' | '❤️'
You can use template params in other head tags too, such as meta descriptions and open graph tags.
useHead({
templateParams: {
siteName: 'MyApp'
},
title: 'Home',
meta: [
{ name: 'description', content: 'Welcome to %siteName - where we make awesome happen' },
{ property: 'og:title', content: 'Home | %siteName' },
{ property: 'og:description', content: 'Check out %siteName today!' }
]
})
Resetting the Title Template
If you need to reset the title template for a specific page, you can pass
<script lang="ts" setup>
useHead({
title: 'Home',
titleTemplate: null
})
</script>
<head>
<title>Home</title>
</head>
Social Share Titles in Vue
Social platforms use different meta tags for sharing titles.
In the above we can see the title "Nuxt: The Intuitive Vue Framework".
This title is set using the
Remembering how to use the meta tags can be annoying, so we can use the
<script setup lang="ts">
useSeoMeta({
titleTemplate: '%s %seperator Health Tips',
title: 'Why you should eat more broccoli',
// og title is not effected by titleTemplate, we can use template params here if we need
ogTitle: 'Hey! Health Tips %seperator 10 reasons to eat more broccoli.',
// explicit twitter title is only needed when we want to display something just for X
twitterTitle: 'Hey X! Health Tips %seperator 10 reasons to eat more broccoli.',
})
</script>
<head>
<title>Why you should eat more broccoli | Health Tips</title>
<meta property="og:title" content="Health Tips: 10 reasons to eat more broccoli." />
<meta name="twitter:title" content="Hey X! Health Tips - 10 reasons to eat more broccoli." />
</head>
Nuxt SEO Utils
Nuxt SEO Utils is a powerful module that helps you manage your technical SEO. If you're using the Nuxt SEO module it's already included for you.
It has a number of features that can help you manage your titles, such as:
Fallback Titles
Usually we map our routes in a way that the page title and be inferred. For example, consider we have a route called
With Nuxt SEO Utils, this will in fact be the default behavior. If you don't set a title, it will use the last slug segment as the title.
You can read more about it in the Enahcned Title guide.
Automatic Social Share Titles
Most of the time our
It's easy to forget to set the
<script lang="ts" setup>
useSeoMeta({
titleTemplate: '%s %seperator Health Tips',
title: 'Home',
})
</script>
<head>
<title>Home | Health Tips</title>
<meta property="og:title" content="Home" />
</head>