Nuxt/docs/1.getting-started/5.seo-meta.md
2023-03-08 07:32:24 -08:00

8.9 KiB

navigation.icon description
uil:file-search-alt Improve your Nuxt app's SEO with powerful head config, composables and components.

SEO and Meta

Improve your Nuxt app's SEO with powerful head config, composables and components.

App Head

Providing an app.head property in your nuxt.config.ts allows you to customize the head for your entire app.

::alert{type=info} This method does not allow you to provide reactive data, if you need global reactive data you can use useHead in app.vue. ::

Shortcuts are available to make configuration easier: charset and viewport. You can also provide any of the keys listed below in Types.

Defaults

Out-of-the-box, Nuxt provides sane defaults, which you can override if needed.

  • charset: utf-8
  • viewport: width=device-width, initial-scale=1

Example

export default defineNuxtConfig({
  app: {
    head: {
      charset: 'utf-16',
      viewport: 'width=500, initial-scale=1',
      title: 'My App',
      meta: [
        // <meta name="description" content="My amazing site">
        { name: 'description', content: 'My amazing site.' }
      ],
    }
  }
})

:ReadMore{link="/docs/api/configuration/nuxt-config/#head"}

Composable: useHead

The useHead composable function allows you to manage your head tags in a programmatic and reactive way, powered by Unhead.

As with all composables, it can only be used with a components setup and lifecycle hooks.

Example

<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>

::ReadMore{link="/docs/api/composables/use-head"} ::

Composable: useSeoMeta and useServerSeoMeta

The useSeoMeta and useServerSeoMeta composables let you define your site's SEO meta tags as a flat object with full TypeScript support.

This helps you avoid typos and common mistakes, such as using name instead of property.

In most instances, the meta does not need to be reactive as robots will only scan the initial load. So we recommend using useServerSeoMeta as a performance-focused utility that will not do anything (or return a head object) on the client.

Example

Simple

<script setup lang="ts">
useServerSeoMeta({
  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>

Reactive

When inserting tags that are reactive, for example, from an API request, you should use the computed getter syntax, the same as useHead.

<script setup lang="ts">
const data = useFetch(() => $fetch('/api/example'))

useServerSeoMeta({
  ogTitle: () => `${data.value?.title} - My Site`,
  description: () => data.value?.description,
  ogDescription: () => data.value?.description,
})
</script>

::ReadMore{link="https://unhead.harlanzw.com/guide/composables/use-seo-meta"} ::

Components

Nuxt provides <Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html> and <Head> components so that you can interact directly with your metadata within your component's template.

Because these component names match native HTML elements, it is very important that they are capitalized in the template.

<Head> and <Body> can accept nested meta tags (for aesthetic reasons) but this has no effect on where the nested meta tags are rendered in the final HTML.

Example

<script setup>
const title = ref('Hello World')
</script>

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

    <h1>{{ title }}</h1>
  </div>
</template>

Types

The below is the non-reactive types used for useHead, app.head and components.

interface MetaObject {
  title?: string
  titleTemplate?: string | ((title?: string) => string)
  templateParams?: Record<string, string | Record<string, string>>
  base?: Base
  link?: Link[]
  meta?: Meta[]
  style?: Style[]
  script?: Script[]
  noscript?: Noscript[];
  htmlAttrs?: HtmlAttributes;
  bodyAttrs?: BodyAttributes;
}

See @unhead/schema for more detailed types.

Features

Reactivity

Reactivity is supported on all properties, as computed, computed getter refs and reactive.

It's recommended to use computed getters (() => {}) over computed (computed(() => {})).

::code-group

<script setup lang="ts">
const desc = ref('My amazing site.')

useHead({
  meta: [
    { name: 'description', content: desc }
  ],
})
</script>
<script setup>
const desc = ref('My amazing site.')
</script>

<template>
  <div>
    <Meta name="description" :content="desc" />
  </div>
</template>

::

Title Templates

You can use the titleTemplate option to provide a dynamic template for customizing the title of your site. for example, by adding the name of your site to the title of every page.

The titleTemplate can either be a string, where %s is replaced with the title, or a function.

If you want to use a function (for full control), then this cannot be set in your nuxt.config, and it is recommended instead to set it within your app.vue file, where it will apply to all pages on your site:

::code-group

<script setup lang="ts">
useHead({
  titleTemplate: (titleChunk) => {
    return titleChunk ? `${titleChunk} - Site Title` : 'Site Title';
  }
})
</script>

::

Now, if you set the title to My Page with useHead on another page of your site, the title would appear as 'My Page - Site Title' in the browser tab. You could also pass null to default to the site title.

Body Tags

You can use the tagPosition: 'bodyClose' option on applicable tags to append them to the end of the <body> tag.

For example:

<script setup lang="ts">
useHead({
  script: [
    {
      src: 'https://third-party-script.com',
      tagPosition: 'bodyClose' // valid options are: 'head' | 'bodyClose' | 'bodyOpen'
    }
  ]
})
</script>

Examples

Usage With definePageMeta

Within your pages/ directory, you can use definePageMeta along with useHead to set metadata based on the current route.

For example, you can first set the current page title (this is extracted at build time via a macro, so it can't be set dynamically):

<script setup>
definePageMeta({
  title: 'Some Page'
})
</script>

And then in your layout file, you might use the route's metadata you have previously set:

<script setup>
const route = useRoute()

useHead({
  meta: [{ property: 'og:title', content: `App Name - ${route.meta.title}` }]
})
</script>

::LinkExample{link="/docs/examples/composables/use-head"} ::

:ReadMore{link="/docs/guide/directory-structure/pages/#page-metadata"}

Add Dynamic Title

In the example below, titleTemplate is set either as a string with the %s placeholder or as a function, which allows greater flexibility in setting the page title dynamically for each route of your Nuxt app:

<script setup>
useHead({
  // as a string,
  // where `%s` is replaced with the title
  titleTemplate: '%s - Site Title',
  // ... or as a function
  titleTemplate: (productCategory) => {
    return productCategory
      ? `${productCategory} - Site Title`
      : 'Site Title'
  }
})
</script>

nuxt.config is also used as an alternative way of setting the page title. However, nuxt.config does not allow the page title to be dynamic. Therefore, it is recommended to use titleTemplate in the app.vue file to add a dynamic title, which is then applied to all routes of your Nuxt app.

Add External CSS

The example below shows how you might enable Google Fonts using either the link property of the useHead composable or using the <Link> component:

::code-group

<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>
<template>
  <div>
    <Link rel="preconnect" href="https://fonts.googleapis.com" />
    <Link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" crossorigin="" />
  </div>
</template>

::