Good Website SEO increases website search appearance in search engines and website traffic. For this nuxt 3 framework provides out of the box SEO manipulation using many techniques which we will check in this article.
Nuxt Config & Meta
The first technique of handling SEO meta tags in Nuxt 3 in nuxt.config in app.head section. The meta tags added this way will be available globally in all pages.
export default defineNuxtConfig({
app: {
head: {
title: "Awesome Website | Home",
charset: "utf-8",
viewport: "width=device-width, initial-scale=1, maximum-scale=1",
meta: [
{ name: 'description', content: 'Awesome Website description' },
{ name: 'keywords', content: 'awesome, website' },
{ name: 'og:title', content: 'Awesome Website' },
{ name: 'og:type', content: 'blog' },
{ name: 'og:image', content: "http://<url to image>" },
{ name: 'og:description', content: 'Awesome Website description' },
{name: 'twitter:card', content: 'summary' },
{name: 'twitter:title', content: 'Awesome Website' },
{name: 'twitter:description', content: 'Awesome Website description' },
]
}
}
})
Inside the head section, you can add any valid meta tag as shown in this code. I added the title, charset, viewport. Any meta data usually added inside the meta array, each meta item represent an object with keys {name, content}. For example the above code will be in the final html:
<meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>Awesome Website</title> <meta name="description" content="Awesome Website description"> <meta name="keywords" content="awesome, website"> <meta name="og:title" content="Awesome Website"> ....
useHead
The second method to add meta tags is by using the useHead composable provided with Nuxt3. The useHead composables takes same meta like the app.head section in nuxt.config.
app/pages/index.vue:
<script setup>
const title = ref('Home')
const description = ref('Home page description')
useHead({
title,
meta: [
{name: "description", content: description}
]
})
</script>
<template>
<div>
<h2>Home</h2>
</div>
</template>
app/pages/about.vue:
<script setup>
const title = ref('About us')
const description = ref('About us description')
useHead({
title,
meta: [
{name: "description", content: description}
]
})
</script>
<template>
<div>
<h2>About us</h2>
</div>
</template>
The advantage of using useHead over app.head is that you can provide dynamic data coming through API:
pages/post/[slug.vue]
<script setup>
const route = useRoute()
const { data } = await useFetch(`/api/post/${route.params.slug}`)
useHead({
title: data.value?.post?.title,
meta: [
{name: "description", content: data.value?.post?.description},
{ name: 'og:title', content: data.value?.post?.title },
{ name: 'og:url', content: data.value?.post?.url },
{ name: 'og:image', content: data.value?.post?.image },
{name: 'twitter:title', content: data.value?.post?.title },
{name: 'twitter:description', content: data.value?.post?.description },
]
})
</script>
<template>
<div>
Post detail <span>{{data.post?.title}}</span>
</div>
</template>
In this page, the post detail displayed from a server API using the post slug or depending on your use case, for this we declared the file as [slug].vue to indicate that this is dynamic route. And then we populate the useHead meta data from the post response as show:
useHead({
title: data.value?.post?.title,
...
})
Note that the meta declared in the pages/ will be merged with the meta declared in nuxt.config, so if there is missing meta in specific page, it will take that meta from nuxt.config.
Now there is a slight issue, the page title shown when using useHead() doesn’t include the site title, for example for home it show like this “Home“, for about page “About us“
If we need to include the site name in page title like “<Site Title> | Home”, we should declare useHead in app.vue, and utilize the titleTemplate key.
Here is an example:
app.vue
<script setup>
useHead({
titleTemplate: '%s | Awesome Website',
})
</script>
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
In this code the titleTemplate represent the full title template shown on browser tab, and the %s placeholder will be replaced by the page title for each page, for example if we navigate to home and there is useHead declared in home page then the title will become “Home | Awesome Website”Â
The titleTemplate also can accept a function instead of a string like so:
useHead({
titleTemplate: (titleChunk) => {
return titleChunk ? `${titleChunk} | Awesome Website` : 'Awesome Website'
}
})
useSeoMeta
The third method is by using useSeoMeta() composable, the useSeoMeta()Â let’s us define the page meta as flat object:
app/pages/contact.vue:
<script setup>
useSeoMeta({
title: 'Contact us',
description: 'Contact us description',
ogTitle: 'Contact us',
ogDescription: 'Contact us description',
ogImage: 'http://<image url>',
})
</script>
<template>
<div>
<h2>Contact us</h2>
</div>
</template>
useSeoMeta() has full typescript support and allows to add each meta key directly in flat object form. In this example i passed the title, description meta, for social meta tags like “og:title” you have to use the camelCase version like ogTitle, twitterCard and so on.
Components
In addition to the above components, Nuxt 3 provides dedicated components that can be used directly in the <template> like <Title>, <Meta>, <Head>
app/pages/post/[slug].vue
<script setup>
const route = useRoute()
const { data } = await useFetch(`/api/post/${route.params.slug}`)
</script>
<template>
<div>
<Head>
<Title>{{data?.post?.title}}</Title>
<Meta name="description" :content="data?.post?.description" />
<Meta name="og:title" :content="data?.post?.title" />
<Meta name="og:description" :content="data?.post?.description" />
<Meta name="og:url" :content="data?.post?.url" />
<Meta name="twitter:title" :content="data?.post?.title" />
</Head>
<article class="post">
<h2>Post detail <span>{{data.post?.title}}</span></h2>
</article>
</div>
</template>
The meta components such as <Title> <Meta> <Link> must be wrapped inside the <Head> component, and Nuxt will compile these components if exist and add them properly to the document <head>. You can check extra details in Nuxt seo meta docs.


