Nuxt SEO

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.
Install-only

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.

Terminal
$ npx skills add onmax/nuxt-skills --skill nuxt-seo

Skill instructions

The instruction file for this skill. The skill also includes other files you need to install to use it.

SKILL.md

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:

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

ModulePurposeKey API
nuxt-site-configShared configuseSiteConfig()
@nuxtjs/robotsrobots.txtuseRobotsRule()
@nuxtjs/sitemapsitemap.xmldefineSitemapEventHandler()
nuxt-og-imageOG imagesdefineOgImage()
nuxt-schema-orgJSON-LDuseSchemaOrg()
nuxt-seo-utilsMeta utilitiesuseBreadcrumbItems()
nuxt-link-checkerLink validationBuild-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

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.txt directly
  • Sitemap: Visit /__sitemap__/debug.json for 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

RendererSpeedCSS SupportEdgeBest For
satoriFastPartialDefault, most templates
chromiumSlowFullComplex designs, prerender
export default defineNuxtConfig({
  ogImage: {
    defaults: { renderer: 'satori' }
  }
})

Satori Limitations

  • No display: grid - use flex
  • No position: absolute without explicit dimensions
  • Fonts: use @nuxt/fonts with global: true for 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

FunctionUse 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 name
  • description → WebPage description
  • og: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

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 errors
  • no-baseless - Missing base URL
  • no-javascript - javascript: links
  • trailing-slash - Inconsistent slashes
  • missing-hash - Invalid anchor targets
  • no-uppercase-chars - URL casing
  • absolute-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:

Loading Files

Consider loading these reference files based on your task:

DO NOT load all files at once. Load only what's relevant to your current task.

Key Concepts

ConceptPurpose
CollectionsTyped content groups with schemas
Page vs Datapage = routes + body, data = structured data only
Remote sourcessource.repository for GitHub, defineCollectionSource for APIs
queryCollectionSQL-like fluent API for content
MDCVue components inside markdown
ContentRendererRenders 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

Token Efficiency

Main skill: ~300 tokens. Each sub-file: ~800-1200 tokens. Only load files relevant to current task.

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.