
Set up sitemaps, meta tags, structured data, OG images, and robots.txt in Nuxt with the official SEO module.
Reza Baar
June 17, 2026
Nuxt already gives you SSR out of the box, which is half the SEO battle. But you still need sitemaps, proper meta tags, structured data, robots.txt, and OG images. The @nuxtjs/seo module bundles six SEO modules into a single install and configures sensible defaults for all of them. In this post, we'll set it up, configure each piece, and cover the newer AI-readiness considerations.
Read more at nuxtseo.com.
One command installs the entire SEO stack:
npx nuxi module add @nuxtjs/seo
This registers @nuxtjs/seo in your config, which is an alias that installs these seven modules together:
@nuxtjs/robots for robots.txt@nuxtjs/sitemap for XML sitemapsnuxt-og-image for dynamic Open Graph imagesnuxt-schema-org for structured data (JSON-LD)nuxt-seo-utils for canonical URLs and trailing slash normalizationnuxt-link-checker for catching broken links during developmentnuxt-site-config for shared site configuration (also auto-installs with any individual SEO module)All SEO modules need your site URL. Set it once in nuxt.config.ts and every module picks it up:
// nuxt.config.ts
export default defineNuxtConfig({
site: {
url: 'https://example.com',
name: 'My Nuxt App',
description: 'A Nuxt application with full SEO setup',
defaultLocale: 'en',
},
});
This single site config feeds the sitemap, canonical URLs, OG images, and structured data. No need to repeat the URL in five different places.
The recommended way to set page-level meta tags is useSeoMeta. It's type-safe and uses a flat object instead of nested arrays:
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useAsyncData(`post-${route.params.slug}`, () =>
$fetch(`/api/posts/${route.params.slug}`)
);
useSeoMeta({
title: () => post.value?.title,
description: () => post.value?.description,
ogTitle: () => post.value?.title,
ogDescription: () => post.value?.description,
ogImage: () => post.value?.coverImage,
ogType: 'article',
twitterCard: 'summary_large_image',
});
</script>
Using getter functions (() => post.value?.title) makes the meta tags reactive. If the data updates, the tags update too.
For a global default across all pages, set it in app.vue or a layout:
<!-- app.vue -->
<script setup lang="ts">
useSeoMeta({
titleTemplate: '%s | My Nuxt App',
ogSiteName: 'My Nuxt App',
twitterCard: 'summary_large_image',
});
</script>
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
The titleTemplate inserts each page's title into the pattern. A page with title: 'About' renders as "About | My Nuxt App".
The sitemap module generates an XML sitemap at /sitemap.xml automatically from your pages/ directory and any dynamic routes you configure:
// nuxt.config.ts
export default defineNuxtConfig({
sitemap: {
sources: ['/api/__sitemap__/urls'],
},
});
For dynamic content (blog posts from a CMS), create a server route that returns the URLs:
// server/api/__sitemap__/urls.ts
export default defineSitemapEventHandler(async () => {
const posts = await $fetch('/api/posts');
return posts.map((post) => ({
loc: `/blog/${post.slug}`,
lastmod: post.updatedAt,
changefreq: 'weekly',
priority: 0.8,
}));
});
The sitemap includes your static pages automatically. The source endpoint adds the dynamic ones.
The robots module generates /robots.txt from your config:
// nuxt.config.ts
export default defineNuxtConfig({
robots: {
groups: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin', '/api'],
},
],
sitemap: 'https://example.com/sitemap.xml',
},
});
It also supports blocking specific AI crawlers if you want to control how LLMs consume your content:
// nuxt.config.ts
export default defineNuxtConfig({
robots: {
groups: [
{
userAgent: '*',
allow: '/',
},
{
userAgent: ['GPTBot', 'ChatGPT-User', 'CCBot'],
disallow: ['/'],
},
],
},
});
This lets regular search engines crawl your site while blocking AI training crawlers. Whether you want to do this depends on your content strategy.
Schema.org markup helps search engines understand your content. The nuxt-schema-org module provides composables for adding JSON-LD structured data:
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useAsyncData(`post-${route.params.slug}`, () =>
$fetch(`/api/posts/${route.params.slug}`)
);
useSchemaOrg([
defineArticle({
headline: post.value?.title,
description: post.value?.description,
datePublished: post.value?.publishedAt,
dateModified: post.value?.updatedAt,
author: {
name: post.value?.author.name,
url: post.value?.author.url,
},
image: post.value?.coverImage,
}),
]);
</script>
For your site-wide identity, add organization or person schema in app.vue:
<!-- app.vue -->
<script setup lang="ts">
useSchemaOrg([
defineOrganization({
name: 'My Company',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
sameAs: [
'https://twitter.com/mycompany',
'https://github.com/mycompany',
],
}),
defineWebSite({
name: 'My Nuxt App',
}),
]);
</script>
The module automatically nests the structured data correctly. A page-level article schema connects to the site-level organization schema without you wiring them together.
The nuxt-og-image module (v6, shipped with @nuxtjs/seo v5) generates Open Graph images on demand. Instead of creating a PNG for every page, you define a template component and it renders one dynamically:
<!-- pages/blog/[slug].vue -->
<script setup lang="ts">
defineOgImage('BlogPost', {
title: post.value?.title,
description: post.value?.description,
siteName: 'My Nuxt App',
});
</script>
Note: if you're migrating from an older version, defineOgImageComponent was deprecated in v6. The new API is defineOgImage with the component name as the first argument. Run npx nuxt-og-image migrate v6 to auto-migrate.
Create the OG image template as a Vue component in components/OgImage/:
<!-- components/OgImage/BlogPost.vue -->
<script setup lang="ts">
defineProps<{
title: string;
description: string;
siteName: string;
}>();
</script>
<template>
<div class="og-image">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<span>{{ siteName }}</span>
</div>
</template>
The module renders this component as a PNG and serves it at the URL referenced in the OG meta tags. The default renderer is Takumi (2-10x faster than the old Satori renderer) and images can be cached at the edge.
The nuxt-link-checker module scans for broken links during development. You'll see warnings in your terminal when a <NuxtLink> points to a page that doesn't exist:
[link-checker] /blog/old-post -> 404
This catches dead links before they hit production.
Here's what a fully configured nuxt.config.ts looks like with all SEO options:
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/seo'],
site: {
url: 'https://example.com',
name: 'My Nuxt App',
description: 'A Nuxt application with full SEO setup',
},
sitemap: {
sources: ['/api/__sitemap__/urls'],
},
robots: {
groups: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin'],
},
],
},
ogImage: {
enabled: true,
},
schemaOrg: {
identity: 'Organization',
},
});
Most of the actual SEO work happens in page-level composables (useSeoMeta, useSchemaOrg, defineOgImageComponent), not in the config file. The config sets the foundation, and each page adds its specifics.
@nuxtjs/seo bundles six SEO modules into one install with zero-config defaultssite config and all modules use ituseSeoMeta is the recommended way to set meta tags with full TypeScript supportuseSchemaOrg adds structured data that connects page-level and site-level entitiesWe set up the full Nuxt SEO stack: meta tags, sitemaps, structured data, OG images, robots.txt, and link checking. The module handles the tedious wiring so each page just needs a few composable calls to be fully optimized for search engines.
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

SEO in Nuxt with @nuxtjs/seo
Set up sitemaps, meta tags, structured data, OG images, and robots.txt in Nuxt with the official SEO module.
Reza Baar
Jun 17, 2026
![Whatās the untracked function? [Angular Signals]](/.netlify/images?url=https:%2F%2Fapi.certificates.dev%2Fstorage%2FZzk75tZNAVT5d3GI9TxAD2JwkIFUKavFFj8sC2BL.png)
Whatās the untracked function? [Angular Signals]
Learn how Angular's computed() function derives reactive values from signals and why it plays a key role in building high-performance, signal-based applications with cleaner and more predictable state management.
Alain Chautard
Jun 16, 2026

Events and Listeners: The Event System From the Inside Out
Laravel events are more than a way to decouple application logic. This article explores how the event system works under the hood, from dispatching and listeners to the internals that frequently appear on Laravel certification exams, helping you build a deeper understanding beyond basic usage.
Steve McDougall
Jun 11, 2026