Nuxt SEO
Quick answer
- 01What is it?
- SEO configuration (site URL, name, indexability) Robots.txt and sitemap.xml generation Dynamic OG image generation JSON-LD structured data (schema. The value is a focused slice of search and SEO workflows judgment, useful when several similar skills cover the same ground.
- 02Inputs
- Context for search and SEO workflows: your goals, audience, constraints, and any source material the skill asks for.
- 03Output
- A ready-to-use result for search and SEO workflows: the analysis, copy, or recommendations the agent produces.
Add this skill
Install as a package
Installs this one skill package for your coding agent, including any supporting files that skill ships with — not every skill in the repository. Read the tutorial.
$ npx skills add onmax/nuxt-skills --skill nuxt-seoSkill instructions
The instruction file for this skill. The skill also includes other files you need to install to use it.
Nuxt SEO
npx nuxi module add @nuxtjs/seo
When to Use
Working with:
- SEO configuration (site URL, name, indexability)
- Robots.txt and sitemap.xml generation
- Dynamic OG image generation
- JSON-LD structured data (schema.org)
- Breadcrumbs and canonical URLs
Loading Files
Consider loading these reference files based on your task:
- references/site-config.md - if configuring site URL, name, or SEO foundation
- references/crawlability.md - if setting up robots.txt or sitemap.xml
- references/og-image.md - if generating dynamic OG images
- references/schema-org.md - if adding JSON-LD structured data
- references/utilities.md - if working with breadcrumbs, canonical URLs, or link checking
DO NOT load all files at once. Load only what's relevant to your current task.
Site Config
Foundation for all SEO modules. Configure site in nuxt.config.ts, access via useSiteConfig(). See references/site-config.md for full options.
Module Overview
| Module | Purpose | Key API |
|---|---|---|
| nuxt-site-config | Shared config | useSiteConfig() |
| @nuxtjs/robots | robots.txt | useRobotsRule() |
| @nuxtjs/sitemap | sitemap.xml | defineSitemapEventHandler() |
| nuxt-og-image | OG images | defineOgImage() |
| nuxt-schema-org | JSON-LD | useSchemaOrg() |
| nuxt-seo-utils | Meta utilities | useBreadcrumbItems() |
| nuxt-link-checker | Link validation | Build-time checks |
Nuxt Content v3
Use asSeoCollection() for automatic sitemap, og-image, and schema-org from frontmatter:
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { asSeoCollection } from '@nuxtjs/seo/content'
export default defineContentConfig({
collections: {
posts: defineCollection(asSeoCollection({ type: 'page', source: 'posts/**' }))
}
})
Important: Load @nuxtjs/seo before @nuxt/content in modules array:
export default defineNuxtConfig({
modules: ['@nuxtjs/seo', '@nuxt/content']
})
Frontmatter fields: ogImage, sitemap, robots, schemaOrg.
Related Skills
- nuxt-content (../nuxt-content/SKILL.md) - For MDC rendering with SEO frontmatter
Links
Token Efficiency
Main skill: ~250 tokens. Each sub-file: ~400-600 tokens. Only load files relevant to current task.
Supporting file: references/crawlability.md
Crawlability: Robots & Sitemap
Robots.txt
Auto-generated at /robots.txt. Respects site.indexable setting.
Configuration
// nuxt.config.ts
export default defineNuxtConfig({
robots: {
// Block AI crawlers
blockAiBots: true,
// Block non-SEO bots (reduces server load)
blockNonSeoBots: true,
// Custom rules
groups: [
{ userAgent: '*', disallow: ['/admin'] }
]
}
})
Per-Page Control
// Disable indexing
useRobotsRule('noindex, nofollow')
// Object syntax with AI directives
useRobotsRule({
noindex: true,
nofollow: true,
noai: true, // Block AI training
noimageai: true, // Block AI image training
'max-snippet': 150, // Preview controls
'max-image-preview': 'large'
})
Route rules:
export default defineNuxtConfig({
routeRules: {
'/admin/**': { robots: 'noindex, nofollow' },
'/hidden': { robots: false }
}
})
Nuxt Content Frontmatter
---
robots: noindex, nofollow
# Or structured:
robots:
noindex: true
nofollow: true
---
Sitemap.xml
Auto-generated at /sitemap.xml from app routes.
Configuration
// nuxt.config.ts
export default defineNuxtConfig({
sitemap: {
sources: ['/api/__sitemap__/urls'],
exclude: ['/admin/**', '/secret'],
// For static sites - no runtime generation
zeroRuntime: true
}
})
Dynamic URLs via API
// server/api/__sitemap__/urls.ts
import { defineSitemapEventHandler } from '#imports'
import type { SitemapUrlInput } from '#sitemap/types'
export default defineSitemapEventHandler(async () => {
const posts = await $fetch('/api/posts')
return posts.map(post => ({
loc: post.path,
lastmod: post.updatedAt,
// Image sitemap
images: [{ loc: post.image, title: post.title }],
// Video sitemap
videos: [{ content_loc: post.videoUrl, title: post.title }]
} satisfies SitemapUrlInput))
})
Per-Page Control
Route rules:
export default defineNuxtConfig({
routeRules: {
'/blog/**': { sitemap: { changefreq: 'daily', priority: 0.9 } },
'/hidden': { sitemap: false }
}
})
Nuxt Content frontmatter:
---
sitemap:
changefreq: weekly
priority: 0.8
lastmod: 2025-01-15
---
Multiple Sitemaps
For large sites:
export default defineNuxtConfig({
sitemap: {
sitemaps: {
pages: { include: ['/**'], exclude: ['/blog/**'] },
blog: { include: ['/blog/**'] }
}
}
})
Generates /pages-sitemap.xml, /blog-sitemap.xml, and /sitemap_index.xml.
i18n Sitemaps
With @nuxtjs/i18n, auto-generates per-locale sitemaps with hreflang alternates.
Debug
In development:
- Robots: Check
/robots.txtdirectly - Sitemap: Visit
/__sitemap__/debug.jsonfor raw data
Supporting file: references/og-image.md
OG Image Generation
Dynamic Open Graph image generation using Vue components.
Quick Start
// Component-first (recommended)
defineOgImage('NuxtSeo', { title: 'My Page Title' })
// Object syntax
defineOgImage({ component: 'NuxtSeo', title: 'My Page Title' })
// Disable OG image
defineOgImage(false)
Built-in Template
The NuxtSeo template supports:
defineOgImage('NuxtSeo', {
title: 'Hello World',
description: 'My description',
theme: '#3b82f6',
colorMode: 'dark',
icon: 'carbon:cloud',
siteName: 'My Site',
siteLogo: '/logo.png'
})
Multiple Images Per Page
Use key for platform-specific images:
// Default OG image (1200x600)
defineOgImage('NuxtSeo', { title: 'Default' })
// Square for WhatsApp (800x800)
defineOgImage('NuxtSeo', {
title: 'Square',
key: 'square',
width: 800,
height: 800
})
Custom Vue Components
Create in components/OgImage/:
<script setup lang="ts">
defineProps<{ title: string; author: string }>()
</script>
<template>
<div class="w-full h-full flex flex-col justify-center items-center bg-gradient-to-br from-blue-500 to-purple-600 p-12">
<h1 class="text-6xl font-bold text-white text-center">{{ title }}</h1>
<p class="text-2xl text-white/80 mt-4">By {{ author }}</p>
</div>
</template>
Use in pages:
defineOgImage('OgImageBlog', { title: 'My Post', author: 'John' })
Renderers
| Renderer | Speed | CSS Support | Edge | Best For |
|---|---|---|---|---|
| satori | Fast | Partial | ✅ | Default, most templates |
| chromium | Slow | Full | ❌ | Complex designs, prerender |
export default defineNuxtConfig({
ogImage: {
defaults: { renderer: 'satori' }
}
})
Satori Limitations
- No
display: grid- useflex - No
position: absolutewithout explicit dimensions - Fonts: use
@nuxt/fontswithglobal: truefor best results
Configuration
export default defineNuxtConfig({
ogImage: {
defaults: {
component: 'NuxtSeo',
width: 1200,
height: 600,
cacheMaxAgeSeconds: 60 * 60 * 24 * 3 // 3 days
},
// For static sites
zeroRuntime: true
}
})
Nuxt Content
Frontmatter:
---
ogImage:
component: OgImageBlog
props:
author: John Doe
---
With asSeoCollection() (see main SKILL.md):
<script setup>
const { data: page } = await useAsyncData(() => queryCollection('posts').path(route.path).first())
if (page.value?.ogImage)
defineOgImage(page.value.ogImage)
</script>
Debug
- Preview:
/__og-image__/image/[path]/og.png - Inspector: Enable
ogImage: { debug: true }in config
Screenshots
Capture page as OG image (requires Chromium):
defineOgImageScreenshot({
colorScheme: 'dark',
mask: '.navigation, .footer',
selector: '.article-content'
})
Route Rules
export default defineNuxtConfig({
routeRules: {
'/blog/**': { ogImage: { component: 'OgImageBlog' } },
'/admin/**': { ogImage: false }
}
})
Deployment
Community templates are dev-only. Before deploying, eject:
npx nuxt-og-image eject NuxtSeo
Supporting file: references/schema-org.md
Schema.org Structured Data
JSON-LD structured data for rich search results.
Site Identity
Configure once in nuxt.config.ts:
import { defineOrganization } from 'nuxt-schema-org/schema'
export default defineNuxtConfig({
schemaOrg: {
identity: defineOrganization({
name: 'My Company',
url: 'https://example.com',
logo: '/logo.png',
sameAs: ['https://twitter.com/mycompany', 'https://github.com/mycompany']
})
}
})
For personal sites:
import { definePerson } from 'nuxt-schema-org/schema'
export default defineNuxtConfig({
schemaOrg: {
identity: definePerson({
name: 'John Doe',
url: 'https://johndoe.com',
image: '/avatar.jpg',
sameAs: ['https://twitter.com/johndoe']
})
}
})
Page-Level Schema
Define functions are auto-imported in components (no import needed):
// Article page
useSchemaOrg([
defineArticle({
headline: 'My Article Title',
description: 'Article description',
image: '/article-image.jpg',
datePublished: '2025-01-15',
dateModified: '2025-01-20',
author: { name: 'John Doe', url: 'https://johndoe.com' }
})
])
// Product page (include url in offers for Google validation)
useSchemaOrg([
defineProduct({
name: 'Product Name',
description: 'Product description',
image: '/product.jpg',
offers: {
price: 99.99,
priceCurrency: 'USD',
availability: 'InStock',
url: 'https://example.com/product'
}
})
])
Define Functions
| Function | Use Case |
|---|---|
defineArticle() | Blog posts, news |
defineProduct() | E-commerce products |
defineFAQPage() | FAQ pages |
defineHowTo() | Tutorial/guide pages |
defineRecipe() | Recipe pages |
defineEvent() | Events |
defineLocalBusiness() | Business info |
defineVideo() | Video content |
defineBreadcrumb() | Breadcrumb navigation |
defineWebPage() | Generic page |
defineWebSite() | Site-wide (auto-added) |
defineJobPosting() | Job listings |
defineSoftwareApp() | Software/apps |
defineService() | Services |
Data Inference
Module auto-infers from page head:
title→ WebPage namedescription→ WebPage descriptionog:image→ WebPage image
Breadcrumbs
Auto-generated from route path, or customize:
useSchemaOrg([
defineBreadcrumb({
itemListElement: [
{ name: 'Home', item: '/' },
{ name: 'Blog', item: '/blog' },
{ name: 'My Post', item: '/blog/my-post' }
]
})
])
Or use the useBreadcrumbItems() composable (from seo-utils):
const items = useBreadcrumbItems()
useSchemaOrg([defineBreadcrumb({ itemListElement: items })])
FAQ Page
useSchemaOrg([
defineFAQPage({
mainEntity: [
{ name: 'What is your return policy?', acceptedAnswer: 'You can return within 30 days.' },
{ name: 'How do I contact support?', acceptedAnswer: 'Email us at support@example.com' }
]
})
])
Nuxt Content
Frontmatter:
---
title: My Article
schemaOrg:
- type: BlogPosting
headline: My Article
datePublished: 2025-01-15
author:
type: Person
name: John Doe
---
With asSeoCollection() (see main SKILL.md), ensure schema renders:
<script setup>
const { data: page } = await useAsyncData(() => queryCollection('posts').path(route.path).first())
useHead(page.value?.head || {})
</script>
Debug & Validation
- Debug endpoint:
/__schema-org__/debug.jsonin dev - Config:
schemaOrg: { debug: true } - Google Rich Results Test (https://search.google.com/test/rich-results)
- Schema.org Validator (https://validator.schema.org/)
Route Rules
export default defineNuxtConfig({
routeRules: {
'/blog/**': {
schemaOrg: { type: 'Article' }
}
}
})
Supporting file: references/site-config.md
Site Config
Foundation module providing shared configuration for all SEO modules.
Configuration
// nuxt.config.ts
export default defineNuxtConfig({
site: {
url: 'https://example.com', // Required for absolute URLs
name: 'My Site', // Site name (used in titles, schema)
description: 'Site description', // Default meta description
defaultLocale: 'en', // Default language
indexable: true, // Allow search engine indexing
trailingSlash: false, // URL trailing slash preference
}
})
Environment-Based Indexing
Control indexing per environment using NUXT_SITE_* env vars:
# .env.production
NUXT_SITE_URL=https://example.com
NUXT_SITE_ENV=production
# .env.staging
NUXT_SITE_URL=https://staging.example.com
NUXT_SITE_ENV=staging
The module auto-detects env and sets indexable: false for non-production environments.
For explicit control:
export default defineNuxtConfig({
site: {
url: process.env.NUXT_SITE_URL,
// Explicit: only index when explicitly set to 'true'
indexable: process.env.NUXT_SITE_INDEXABLE === 'true'
}
})
Note: !== 'false' defaults to true when env var is undefined - use === 'true' for fail-safe behavior.
Runtime Access
const site = useSiteConfig()
console.log(site.url, site.name, site.description)
Works in components, composables, and server routes.
i18n Integration
Automatically integrates with @nuxtjs/i18n:
export default defineNuxtConfig({
site: {
url: 'https://example.com',
defaultLocale: 'en',
},
i18n: {
locales: [
{ code: 'en', language: 'en-US' },
{ code: 'fr', language: 'fr-FR' },
]
}
})
Locale-specific overrides in site object:
site: {
name: 'My Site',
locales: {
fr: { name: 'Mon Site' }
}
}
Override Per-Page
Use route rules for page-specific config:
export default defineNuxtConfig({
routeRules: {
'/admin/**': { site: { indexable: false } },
'/fr/**': { site: { name: 'Mon Site', defaultLocale: 'fr' } }
}
})
Supporting file: references/utilities.md
SEO Utilities
Additional utilities from nuxt-seo-utils and nuxt-link-checker.
Canonical URLs
Automatic canonical URLs based on site config.
export default defineNuxtConfig({
seoUtils: {
canonicalQueryWhitelist: ['page', 'sort'], // Keep these query params
redirectToCanonicalSiteUrl: true // 301 to canonical domain
}
})
Override per-page:
useHead({
link: [{ rel: 'canonical', href: 'https://example.com/preferred-url' }]
})
Breadcrumbs
Generate breadcrumb items from current route:
const items = useBreadcrumbItems()
// [{ label: 'Home', to: '/' }, { label: 'Blog', to: '/blog' }, { label: 'My Post' }]
For schema.org integration, see schema-org.md (schema-org.md#breadcrumbs).
Render in template:
<template>
<nav aria-label="Breadcrumb">
<ol class="flex gap-2">
<li v-for="(item, i) in items" :key="i">
<NuxtLink v-if="item.to" :to="item.to">{{ item.label }}</NuxtLink>
<span v-else>{{ item.label }}</span>
</li>
</ol>
</nav>
</template>
Customize labels in route meta:
// pages/blog/[slug].vue
definePageMeta({
breadcrumb: { label: 'Article' }
})
Title Templates
Set site-wide title template:
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
titleTemplate: '%s | My Site'
}
}
})
Override per-page:
useHead({
title: 'Page Title',
titleTemplate: '%s - Different Template'
})
Meta Defaults
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
meta: [
{ name: 'author', content: 'My Name' },
{ property: 'og:site_name', content: 'My Site' }
]
}
}
})
Link Checker
Build-time validation of links.
export default defineNuxtConfig({
linkChecker: {
failOnError: true, // Default: fail build on errors
exclude: ['/api/**'],
skipInspections: ['missing-hash'],
report: { html: true } // Generate HTML report
}
})
Inspections:
no-error-response- 404/500 errorsno-baseless- Missing base URLno-javascript- javascript: linkstrailing-slash- Inconsistent slashesmissing-hash- Invalid anchor targetsno-uppercase-chars- URL casingabsolute-site-urls- Hardcoded domain
Ignoring Links
<a href="/maybe-broken" data-link-checker-ignore>Link</a>
File-Based Icons
Place favicon files in public/:
public/
├── favicon.ico
├── favicon.svg # Modern browsers
├── apple-touch-icon.png
└── site.webmanifest
Auto-detected and added to <head>.
For SVG favicon with dark mode support:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #fff; }
}
</style>
<path d="..."/>
</svg>
Social Meta Tags
Automatic Open Graph and Twitter cards from site config (see site-config.md).
Override per-page:
useSeoMeta({
title: 'Page Title',
description: 'Page description',
ogImage: '/images/page-og.png',
twitterCard: 'summary_large_image'
})
Trailing Slash Redirect
Enforce consistent URLs:
export default defineNuxtConfig({
site: {
trailingSlash: false // Redirect /blog/ to /blog
}
})
Debug Panel
Enable comprehensive debug panel:
export default defineNuxtConfig({
seo: { debug: true }
})
Shows in dev:
- Current meta tags
- Schema.org data
- OG image preview
- Sitemap/robots status
Supporting file: skills/nuxt-content/SKILL.md
Nuxt Content v3
Progressive guidance for content-driven Nuxt apps with typed collections and SQL-backed queries.
When to Use
Working with:
- Content collections (
content.config.ts,defineCollection) - Remote sources (GitHub repos, external APIs via
defineCollectionSource) - Content queries (
queryCollection, navigation, search) - MDC rendering (
<ContentRenderer>, prose components) - Database configuration (SQLite, PostgreSQL, D1, LibSQL)
- Content hooks (
content:file:beforeParse,content:file:afterParse) - i18n multi-language content
- NuxtStudio or preview mode
- LLMs integration (
nuxt-llms)
For writing documentation: use document-writer skill
For Nuxt basics: use nuxt skill
For NuxtHub deployment: use nuxthub skill (NuxtHub v1 compatible)
Available Guidance
Read specific files based on current work:
- references/collections.md - defineCollection, schemas, sources, content.config.ts
- references/querying.md - queryCollection, navigation, search, surroundings
- references/rendering.md - ContentRenderer, MDC syntax, prose components, Shiki
- references/config.md - Database setup, markdown plugins, renderer options
- references/studio.md - NuxtStudio integration, preview mode, live editing
Loading Files
Consider loading these reference files based on your task:
- references/collections.md - if setting up collections, schemas, or content.config.ts
- references/querying.md - if using queryCollection, navigation, or search
- references/rendering.md - if rendering markdown/MDC or working with ContentRenderer
- references/config.md - if configuring database, markdown plugins, or renderer options
- references/studio.md - if integrating NuxtStudio or preview mode
DO NOT load all files at once. Load only what's relevant to your current task.
Key Concepts
| Concept | Purpose |
|---|---|
| Collections | Typed content groups with schemas |
| Page vs Data | page = routes + body, data = structured data only |
| Remote sources | source.repository for GitHub, defineCollectionSource for APIs |
| queryCollection | SQL-like fluent API for content |
| MDC | Vue components inside markdown |
| ContentRenderer | Renders parsed markdown body |
Quick Start
// content.config.ts
import { defineCollection, defineContentConfig, z } from '@nuxt/content'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**',
schema: z.object({
title: z.string(),
date: z.date(),
}),
}),
},
})
<script setup lang="ts">
const { data: page } = await useAsyncData(
() => queryCollection('blog').path(useRoute().path).first()
)
</script>
<template>
<ContentRenderer v-if="page" :value="page" />
</template>
Verify setup: Run npx nuxi typecheck to confirm collection types resolve. If queryCollection returns empty, check that content files exist in the path matching your source glob.
Directory Structure
project/
├── content/ # Content files
│ ├── blog/ # Maps to 'blog' collection
│ └── .navigation.yml # Navigation metadata
├── components/content/ # MDC components
└── content.config.ts # Collection definitions
Official Documentation
- Nuxt Content: https://content.nuxt.com
- MDC syntax: https://content.nuxt.com/docs/files/markdown#mdc-syntax
- Collections: https://content.nuxt.com/docs/collections/collections
Token Efficiency
Main skill: ~300 tokens. Each sub-file: ~800-1200 tokens. Only load files relevant to current task.
Common questions
How do I install Nuxt SEO in Cursor, Claude Code, or Codex?
Run npx skills add onmax/nuxt-skills --skill nuxt-seo in the project where you want it, then ask your agent for the skill by name. The --skill flag installs only Nuxt SEO, not every skill in the repository.
Where does Nuxt SEO come from and what license is it under?
Nuxt SEO comes from the onmax/nuxt-skills repository on GitHub. That repository has 685 GitHub stars. No license was detected on the source repository, so check with the author before redistributing it.
Prefer plain text? Read the Nuxt SEO guide as markdown.