useHead() or useSeoMeta() for titles—document.title breaks SSR%s | MySite patternPage titles appear in browser tabs and as the clickable headline in search results. Google uses your <title> tag over 80% of the time when generating title links—though studies show it rewrites 61-76% of titles to some degree.
<head>
<title>Mastering Titles in Nuxt · Nuxt SEO</title>
</head>
Page titles work out of the box in Nuxt. Use useSeoMeta() or useHead() in any component.
// Basic title
useHead({ title: 'Home' })
// With template (adds site name)
useHead({
title: 'Home',
titleTemplate: '%s | MySite'
})
// Reactive title from data
const post = ref({ title: 'Loading...' })
useHead({
title: () => post.value.title
})
// SEO-focused (includes og:title)
useSeoMeta({
title: 'Home',
ogTitle: 'Home | MySite'
})
document.title?You might try setting titles directly:
// ❌ Breaks SSR, may not be indexed
document.title = 'Home'
This fails during server-side rendering—the title won't exist in the initial HTML response. Search engines render JavaScript but may not wait for client-side updates.
Nuxt includes Unhead which handles both SSR and client-side updates automatically. For Google's official guidance, see Influencing your title links.
useHead()The useHead() composable sets titles that work in SSR and client-side navigation:
<script setup lang="ts">
useHead({
title: 'Home'
})
</script>
<head>
<title>Home</title>
</head>
Works in any component. You can set other head tags in the same call:
<script setup lang="ts">
useHead({
title: 'Home',
meta: [
{ name: 'description', content: 'Welcome to MyApp' }
]
})
</script>
Unhead accepts refs, reactive objects, and computed values. Don't destructure—pass the reactive reference:
useHead({
title: myTitle.value // ❌ Loses reactivity
})
useHead({
title: myTitle // ✅ Stays reactive
})
Computed getter syntax works for derived titles:
const post = ref({ title: 'Loading...' })
useHead({
title: () => post.value.title // Updates when post changes
})
Fetch data during SSR with useFetch() or useAsyncData(). Client-only fetches mean search engines see your loading state:
<script setup lang="ts">
const postTitle = ref('Loading...')
useHead({ title: postTitle })
// ❌ onMounted runs after SSR—Google sees "Loading..."
onMounted(async () => {
postTitle.value = (await fetchPostData()).title
})
</script>
Use Nuxt's data fetching composables instead:
<script setup lang="ts">
const { data: post } = await useFetch('/api/post')
useHead({
title: () => post.value?.title || 'Loading...'
})
</script>
Most sites append a site name to titles for brand recognition. Google recommends adding your site name with a delimiter:
<head>
<title>Home | MySite</title>
</head>
Use titleTemplate with a title template:
<script setup lang="ts">
useHead({
title: 'Home',
titleTemplate: '%s | MySite'
})
</script>
<head>
<title>Home | MySite</title>
</head>
The %s token gets replaced with your page title (or empty string if none set).
Override the template for specific pages by passing null:
<script lang="ts" setup>
useHead({
title: 'Home',
titleTemplate: null
})
</script>
<head>
<title>Home</title>
</head>
Set template params globally in nuxt.config.ts:
export default defineNuxtConfig({
app: {
head: {
titleTemplate: '%s %separator %siteName',
templateParams: {
separator: '·',
siteName: 'MySite'
}
}
}
})
<head>
<title>Home · MySite</title>
</head>
Common separators: | - — • ·
Template params work in meta tags too:
useHead({
templateParams: { siteName: 'MyApp' },
title: 'Home',
meta: [
{ name: 'description', content: 'Welcome to %siteName' },
{ property: 'og:title', content: 'Home | %siteName' }
]
})
Social platforms use og:title and twitter:title meta tags. Use useSeoMeta() to set these:

<script setup lang="ts">
useSeoMeta({
title: 'Why you should eat more broccoli',
titleTemplate: '%s | Health Tips',
// og:title doesn't use titleTemplate—set it explicitly
ogTitle: 'Health Tips: 10 reasons to eat more broccoli',
// twitter:title only needed if different from og:title
twitterTitle: 'Hey X! 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! 10 reasons to eat more broccoli" />
</head>
Twitter/X falls back to og:title if twitter:title isn't set.
Google displays roughly 50-60 characters before truncating. Studies show titles between 51-60 characters have the lowest rewrite rates (39-42%).
Longer titles still get indexed—Google just truncates the display. Front-load important keywords since users may only see the first 50 characters.
Nuxt SEO Utils handles title defaults and social sharing automatically:
Routes like /about-us automatically get "About Us" as the fallback title if no title is set. Read more in the Enhanced Title guide.
The module automatically sets og:title based on your page title (ignoring the title template):
<script lang="ts" setup>
useSeoMeta({
titleTemplate: '%s %separator Health Tips',
title: 'Home',
})
</script>
<head>
<title>Home | Health Tips</title>
<meta property="og:title" content="Home" />
</head>
Mastering Meta
Set up meta tags in Nuxt with useSeoMeta. Covers titles, descriptions, Open Graph, Twitter Cards, and Schema.org—with SSR patterns that actually get indexed.
Meta Description
Meta descriptions get rewritten by Google 70% of the time anyway. Here's how to implement them properly in Nuxt using composables and let your content do the heavy lifting.