Skip to main content
Nuxt provides comprehensive SEO and meta tag management powered by Unhead. You can configure sensible defaults and use powerful composables to manage your app’s head tags dynamically.

Nuxt Config

Set static head tags for your entire app using the app.head property in your nuxt.config.ts:
export default defineNuxtConfig({
  app: {
    head: {
      title: 'Nuxt', // default fallback title
      htmlAttrs: {
        lang: 'en',
      },
      link: [
        { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      ],
    },
  },
})
This method does not support reactive data. Use useHead() in app.vue for reactive meta tags.
Set tags here that won’t change, such as your site title default, language, and favicon.

Default Tags

Nuxt provides these default tags to ensure your website works well out of the box:
  • viewport: width=device-width, initial-scale=1
  • charset: utf-8
You can override these defaults using keyed shortcuts:
export default defineNuxtConfig({
  app: {
    head: {
      charset: 'utf-16',
      viewport: 'width=device-width, initial-scale=1, maximum-scale=1',
    },
  },
})

useHead Composable

Use the useHead composable to manage head tags programmatically with full reactive support:
<script setup lang="ts">
useHead({
  title: 'My App',
  meta: [
    { name: 'description', content: 'My amazing site.' },
  ],
  bodyAttrs: {
    class: 'test',
  },
  script: [{ innerHTML: 'console.log(\'Hello world\')' }],
})
</script>
For more control, explore useHead and useHeadSafe composables in the API documentation.

useSeoMeta Composable

Use useSeoMeta to define SEO meta tags as a type-safe object, helping you avoid typos and common mistakes:
<script setup lang="ts">
useSeoMeta({
  title: 'My Amazing Site',
  ogTitle: 'My Amazing Site',
  description: 'This is my amazing site, let me tell you all about it.',
  ogDescription: 'This is my amazing site, let me tell you all about it.',
  ogImage: 'https://example.com/image.png',
  twitterCard: 'summary_large_image',
})
</script>
This composable prevents common errors like using name instead of property for Open Graph tags.

Meta Components

If you prefer defining head tags in your template, Nuxt provides these components: <Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html>, and <Head>.
<script setup lang="ts">
const title = ref('Hello World')
</script>

<template>
  <div>
    <Head>
      <Title>{{ title }}</Title>
      <Meta
        name="description"
        :content="title"
      />
      <Style>
        body { background-color: green; }
      </Style>
    </Head>

    <h1>{{ title }}</h1>
  </div>
</template>
Note the capitalization of these components to avoid conflicts with native HTML tags.
Wrap your components in <Head> or <Html> for more intuitive tag deduplication.

Reactivity

All head properties support reactivity through computed values, getters, or reactive objects:
<script setup lang="ts">
const description = ref('My amazing site.')

useHead({
  meta: [
    { name: 'description', content: description },
  ],
})
</script>

Title Templates

Use titleTemplate to provide a dynamic template for customizing page titles. The template can be a string (where %s is replaced with the title) or a function:
<script setup lang="ts">
useHead({
  titleTemplate: (titleChunk) => {
    return titleChunk ? `${titleChunk} - Site Title` : 'Site Title'
  },
})
</script>
Set titleTemplate in your app.vue file to apply it across all pages. You cannot use function-based templates in nuxt.config.
When you set the title to My Page on another page, it appears as “My Page - Site Title” in the browser tab.

Template Parameters

Use templateParams for additional placeholders beyond the default %s:
<script setup lang="ts">
useHead({
  titleTemplate: (titleChunk) => {
    return titleChunk ? `${titleChunk} %separator %siteName` : '%siteName'
  },
  templateParams: {
    siteName: 'Site Title',
    separator: '-',
  },
})
</script>

Body Tags

Use tagPosition: 'bodyClose' to append tags to the end of the <body> tag:
<script setup lang="ts">
useHead({
  script: [
    {
      src: 'https://third-party-script.com',
      tagPosition: 'bodyClose',
    },
  ],
})
</script>
Valid options are: 'head', 'bodyClose', or 'bodyOpen'.

Practical Examples

Page-Specific Meta with definePageMeta

Set metadata based on the current route using definePageMeta with useHead:
<script setup lang="ts">
definePageMeta({
  title: 'Some Page',
})
</script>

Dynamic Titles

Set dynamic page titles using titleTemplate:
<script setup lang="ts">
useHead({
  titleTemplate: '%s - Site Title',
})
</script>

External CSS and Fonts

Enable Google Fonts using the link property:
<script setup lang="ts">
useHead({
  link: [
    {
      rel: 'preconnect',
      href: 'https://fonts.googleapis.com',
    },
    {
      rel: 'stylesheet',
      href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
      crossorigin: '',
    },
  ],
})
</script>

Best Practices

  1. Use useSeoMeta for SEO: Leverage type safety to avoid common meta tag mistakes
  2. Set defaults in app.vue: Apply site-wide settings using useHead in app.vue
  3. Override per page: Use definePageMeta and useHead in pages for page-specific meta
  4. Keep it reactive: Use refs and computed values to update meta tags dynamically
  5. Follow Open Graph standards: Ensure proper og: and twitter: tags for social sharing