SEO and meta tags: useHead and useSeoMeta
Search engines and social networks read the <title> tag and the meta tags in the page <head>: they build the search result title and the link card from them. Nuxt renders these tags on the server, so even programs that do not run JavaScript see them. Tags are set with the useHead and useSeoMeta functions.
The page title
<!-- app/pages/about.vue -->
<script setup lang="ts">
useHead({
title: 'About the blog'
})
</script>Open /about and the browser tab shows "About the blog". The page source contains <title>About the blog</title>.
A title template
To add the site name to every title, set a template in app/app.vue:
<!-- app/app.vue -->
<script setup lang="ts">
useHead({
titleTemplate: (title) => (title ? `${title} — My blog` : 'My blog')
})
</script>Now the tab shows "About the blog — My blog", and pages without their own title show just "My blog".
Meta tags for search and social networks
useSeoMeta sets the description and Open Graph tags, which social networks and messengers use to build a link card:
<!-- app/pages/about.vue -->
<script setup lang="ts">
useSeoMeta({
title: 'About the blog',
description: 'Anna and Oleg write about web development.',
ogTitle: 'About the blog',
ogDescription: 'Anna and Oleg write about web development.',
ogImage: 'https://example.com/cover.png'
})
</script>Each property becomes the right tag, for example ogTitle becomes <meta property="og:title">. The editor suggests property names, so you cannot misspell them.
Tags from loaded data
On the post page the title comes from the API response. Pass a function so the tag updates together with the data:
<!-- app/pages/posts/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useFetch(() => `/api/posts/${route.params.id}`)
useSeoMeta({
title: () => post.value?.title,
description: () => post.value?.text.slice(0, 150)
})
</script>Open /posts/1 and the tab shows "First post — My blog".
Tags for the whole site
Shared tags, such as the page language, are set in nuxt.config.ts:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
htmlAttrs: { lang: 'en' },
link: [{ rel: 'icon', href: '/favicon.ico' }]
}
}
})| Where | What to set |
|---|---|
nuxt.config.ts, app.head | Language, icon, shared tags |
app/app.vue | The title template |
| A page | Title, description, Open Graph |
Common mistake
Writing title: post.value?.title without a function. The value is read once when the component is created. If the user moves from one post to another, the tab title stays the same. For data that can change, pass a function: title: () => post.value?.title.