---
title: "How to Set Page Titles in Nuxt"
description: "Set dynamic page titles in Nuxt with useHead. Learn title templates, reactive titles, and SSR patterns that include titles in the initial HTML."
canonical_url: "https://nuxtseo.com/learn-seo/nuxt/mastering-meta/titles"
last_updated: "2026-10-08"
---

::key-takeaways
- Use `useHead()`{lang="ts"} or `useSeoMeta()`{lang="ts"} for titles; `document.title` breaks SSR
- Title templates append your site name with the `%s | MySite` pattern
- Write concise, descriptive titles; Google truncates title links to fit the available display width
::

Page titles appear in browser tabs and can supply the clickable headline in search results. [Google can use other page content for title links](https://developers.google.com/search/docs/appearance/title-link), so write a title that matches the page.

```html
<head>
  <title>Mastering Titles in Nuxt · Nuxt SEO</title>
</head>
```

Page titles work by default in Nuxt. Use `useSeoMeta()`{lang="ts"} or `useHead()`{lang="ts"} in any component.

## Quick Reference

```ts
// 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'
})
```

## Why Not `document.title`?

You might try setting titles directly:

```ts
// ❌ Breaks SSR, may not be indexed
document.title = 'Home'
```

This accesses a browser-only API and throws during server rendering. Search engines render JavaScript but [may not wait](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) for client-side updates.

Nuxt includes [Unhead](https://unhead.unjs.io/) which handles both SSR and client-side updates automatically. For Google's official guidance, see [Influencing your title links](https://developers.google.com/search/docs/appearance/title-link).

## Setting Titles with `useHead()`{lang="ts"}

The [`useHead()`{lang="ts"}](https://unhead.unjs.io/docs/head/api/composables/use-head) composable sets titles that work in SSR and client-side navigation:

```vue twoslash [input.vue]
<script setup lang="ts">
useHead({
  title: 'Home'
})
</script>
```

```html [output.html]
<head>
  <title>Home</title>
</head>
```

Works in any component. You can set other head tags in the same call:

```vue
<script setup lang="ts">
useHead({
  title: 'Home',
  meta: [
    { name: 'description', content: 'Welcome to MyApp' }
  ]
})
</script>
```

### Reactive Titles

Unhead accepts refs, reactive objects, and computed values. Don't destructure; pass the reactive reference:

```ts twoslash
const myTitle = ref('Home')

useHead({
  title: myTitle.value // ❌ Loses reactivity
})

useHead({
  title: myTitle // ✅ Stays reactive
})
```

Computed getter syntax works for derived titles:

```ts
const post = ref({ title: 'Loading...' })

useHead({
  title: () => post.value.title // Updates when post changes
})
```

### SSR and SEO

Fetch data during SSR with `useFetch()`{lang="ts"} or `useAsyncData()`{lang="ts"}. Client-only fetches leave the loading state in the initial HTML:

```vue
<script setup lang="ts">
const postTitle = ref('Loading...')
useHead({ title: postTitle })

// onMounted runs after SSR. The initial HTML contains "Loading..."
onMounted(() => {
  (async () => {
    postTitle.value = (await fetchPostData()).title
  })()
})
</script>
```

Use Nuxt's data fetching composables instead:

```vue
<script setup lang="ts">
const { data: post } = await useFetch('/api/post')

useHead({
  title: () => post.value?.title || 'Loading...'
})
</script>
```

## Title Templates

Most sites append a site name to titles for brand recognition. [Google recommends](https://developers.google.com/search/docs/appearance/title-link#page-titles) adding your site name with a delimiter:

```html
<head>
  <title>Home | MySite</title>
</head>
```

Use `titleTemplate` with a [title template](https://unhead.unjs.io/docs/head/guides/core-concepts/titles):

```vue twoslash [input.vue]
<script setup lang="ts">
useHead({
  title: 'Home',
  titleTemplate: '%s | MySite'
})
</script>
```

```html [output.html]
<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`:

```vue [input.vue]
<script lang="ts" setup>
useHead({
  title: 'Home',
  titleTemplate: null
})
</script>
```

```html [output.html]
<head>
  <title>Home</title>
</head>
```

## Template Params

Set template params globally in `nuxt.config.ts`:

```ts
export default defineNuxtConfig({
  app: {
    head: {
      titleTemplate: '%s %separator %siteName',
      templateParams: {
        separator: '·',
        siteName: 'MySite'
      }
    }
  }
})
```

```html [output.html]
<head>
  <title>Home · MySite</title>
</head>
```

Common separators: `|` `-` `.` `•` `·`

Template params work in meta tags too:

```ts
useHead({
  templateParams: { siteName: 'MyApp' },
  title: 'Home',
  meta: [
    { name: 'description', content: 'Welcome to %siteName' },
    { property: 'og:title', content: 'Home | %siteName' }
  ]
})
```

## Social Share Titles

Social platforms use `og:title` and `twitter:title` meta tags. Use [`useSeoMeta()`{lang="ts"}](https://unhead.unjs.io/docs/head/api/composables/use-seo-meta) to set these:

::figure-image{src="/nuxt-x-share.png" alt="Nuxt X Share" lazy="true"}
::

```vue [input.vue]
<script setup lang="ts">
useSeoMeta({
  title: 'Why you should eat more broccoli',
  titleTemplate: '%s | Health Tips',
  // og:title ignores titleTemplate, set it explicitly
  ogTitle: 'Health Tips: 10 reasons to eat more broccoli',
  // Set an explicit X title
  twitterTitle: 'Hey X! 10 reasons to eat more broccoli',
})
</script>
```

```html [output.html]
<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>
```

Set `twitterTitle` explicitly when you want a separate X title.

## Title Length

[Google does not set a fixed title length](https://developers.google.com/search/docs/appearance/title-link). It truncates title links to fit the device width. Put the page's main subject first.

A long title can still describe the page, but users may see only part of it. Avoid unnecessary boilerplate.

## Nuxt SEO Module

The Nuxt SEO module handles title defaults, social sharing, and more:

::module-card{slug="nuxt-seo" .w-1/2}
::

Routes like `/about-us` automatically get "About Us" as the fallback title if no title is set. Read more in the [Enhanced Title](/docs/seo-utils/guides/fallback-title) guide.

Title inference depends on the module configuration. A title template can appear in an inferred `og:title`. Set `ogTitle` explicitly when the social title must differ from the composed page title:

```ts
useSeoMeta({
  title: 'Home',
  ogTitle: 'Home',
})
```

After deploying a title edit, check the live HTML and Google's last crawl separately. [Reported title updates](https://www.seroundtable.com/google-crawling-indexing-serving-data-42225.html) typically took 1 to 2 days at the serving stage, with slow cases lasting weeks or months.

Recrawling and reprocessing add separate waits. Google can choose another title. See [Google Search timelines](/learn-seo/google-search-timelines).

## Checklist

::checklist{#nuxt-titles}
- Use `useHead()`{lang="ts"} or `useSeoMeta()`{lang="ts"} instead of `document.title`
- Set up a title template with your site name
- Keep titles concise and descriptive
- Set `og:title` for social sharing
- Fetch data with `useFetch()`{lang="ts"} or `useAsyncData()`{lang="ts"} so titles render server-side
- Test titles render correctly in SSR ("View Source")
::

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
