Nuxt SEO and Meta Tags — Search Engine Optimization in Nuxt 3
Learn how to optimize Nuxt 3 for search engines using useHead, definePageMeta, Open Graph tags, and structured data for better rankings.
In this lesson, you'll understand how to manage SEO in Nuxt 3, create reusable SEO composables, and implement JSON-LD structured data.
What You'll Learn
How to use useHead for page metadata, definePageMeta for static SEO, create a reusable SEO composable, and add structured data.
Why It Matters
SEO directly impacts organic traffic. Nuxt 3 provides excellent head management with SSR support, ensuring search engines see complete meta tags.
flowchart LR
A[Nuxt SEO] --> B[useHead Dynamic]
A --> C[definePageMeta Static]
A --> D[Structured Data]
B --> E[Page Title]
B --> F[Meta Tags]
B --> G[OG Tags]
D --> H[JSON-LD Schema]
style A fill:#00dc82,color:#fff
Basic useHead
<script setup>
useHead({
title: 'About Us',
titleTemplate: '%s | DodaTech Tutorials',
meta: [
{ name: 'description', content: 'Learn about DodaTech and our mission.' },
{ name: 'author', content: 'DodaTech' }
]
});
</script>
Output: The page title becomes "About Us | DodaTech Tutorials". Search engines see the meta description.
definePageMeta
Static SEO data per page:
<script setup>
definePageMeta({
title: 'Blog',
description: 'Read the latest tutorials and articles.',
ogImage: '/images/blog-og.jpg'
});
</script>
definePageMeta is compile-time and can't use reactive values. For dynamic SEO, use useHead.
Reusable SEO Composable
// composables/useSEO.ts
export const useSEO = (options: {
title: string;
description: string;
image?: string;
path?: string;
type?: 'article' | 'website';
publishedTime?: string;
tags?: string[];
}) => {
const { siteUrl, siteName } = useRuntimeConfig().public;
const route = useRoute();
const url = `${siteUrl}${options.path || route.path}`;
const image = options.image ? `${siteUrl}${options.image}` : `${siteUrl}/og-default.jpg`;
useHead({
title: options.title,
titleTemplate: `%s | ${siteName}`,
meta: [
{ name: 'description', content: options.description },
// Open Graph
{ property: 'og:title', content: options.title },
{ property: 'og:description', content: options.description },
{ property: 'og:url', content: url },
{ property: 'og:type', content: options.type || 'website' },
{ property: 'og:image', content: image },
{ property: 'og:site_name', content: siteName },
// Twitter
{ name: 'twitter:card', content: 'summary_large_image' },
{ name: 'twitter:title', content: options.title },
{ name: 'twitter:description', content: options.description },
{ name: 'twitter:image', content: image },
// Article-specific
...(options.publishedTime ? [{ property: 'article:published_time', content: options.publishedTime }] : []),
...(options.tags ? options.tags.map(tag => ({ property: 'article:tag', content: tag })) : [])
],
link: [
{ rel: 'canonical', href: url }
]
});
};
Usage:
<script setup>
useSEO({
title: 'Getting Started with Nuxt 3',
description: 'Learn how to build modern web applications with Nuxt 3 framework.',
path: '/blog/getting-started-nuxt',
type: 'article',
publishedTime: '2026-06-28',
tags: ['nuxt', 'vue', 'tutorial']
});
</script>
Structured Data (JSON-LD)
<script setup>
const { siteUrl } = useRuntimeConfig().public;
const route = useRoute();
const articleSchema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: 'Getting Started with Nuxt 3',
description: 'Learn how to build modern web applications with Nuxt 3.',
image: `${siteUrl}/images/nuxt-guide.jpg`,
author: {
'@type': 'Person',
name: 'DodaTech'
},
datePublished: '2026-06-28',
dateModified: '2026-06-28'
};
useHead({
script: [
{
type: 'application/ld+json',
children: JSON.stringify(articleSchema)
}
]
});
</script>
Output: Search engines see the structured data and may display rich results with author, date, and image.
Nuxt Config SEO
Set global SEO defaults:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
titleTemplate: '%s | DodaTech',
meta: [
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
htmlAttrs: {
lang: 'en'
}
}
}
});
Common Mistakes
- Not setting
titleTemplate: Without it, all pages show the same title. UsetitleTemplate: '%s | Site Name'. - Using relative URLs for OG images: Open Graph requires absolute URLs. Prepend the
siteUrl. - Duplicate canonical URLs: Every page should have a canonical URL to prevent duplicate content issues.
- Not generating a sitemap: Install
@nuxtjs/sitemapmodule for automatic sitemap generation. - Missing
langattribute: SethtmlAttrs: { lang: 'en' }for Accessibility and SEO.
Practice Questions
What is the difference between
useHeadanddefinePageMeta? Answer:useHeadis for dynamic, reactive head management.definePageMetais compile-time static metadata.How do you set a title template that appends the site name? Answer: Use
titleTemplate: '%s | Site Name'inuseHead. The%sis replaced by the page title.Why should OG image URLs be absolute? Answer: Social media crawlers fetch images from absolute URLs. Relative URLs don't resolve correctly from the crawler's perspective.
What module generates a sitemap in Nuxt 3? Answer:
@nuxtjs/sitemap. Install and configure it innuxt.config.ts.
Challenge
Create a reusable useSEO composable that accepts: title, description, image, path, type (article/website), publishedTime, tags, and custom meta. Generate JSON-LD for Article and FAQPage schemas.
Mini Project
Implement full SEO across a Nuxt 3 blog: global defaults in nuxt.config.ts, static SEO on pages with definePageMeta, dynamic SEO on blog posts with useHead, JSON-LD schema per post, Open Graph images, Twitter cards, and sitemap generation.
FAQ
What's Next
Learn about Nuxt Static Generation to generate a fully static site with nuxt generate.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro