Core Concepts

Nuxt Content

Last updated by Harlan Wilton in feat(content): `onUrl` function.

Introduction

Nuxt Sitemap comes with an integration for Nuxt Content that allows you to configure your sitemap entry straight from your content files directly.

Supported Content Types

The sitemap integration works with all content file types supported by Nuxt Content:

  • Markdown (.md)
  • YAML (.yml / .yaml)
  • JSON (.json)
  • CSV (.csv)

Setup Nuxt Content v3

In Nuxt Content v3 we need to use the asSitemapCollection() function to augment any collections to be able to use the sitemap frontmatter key.

content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'

export default defineContentConfig({
  collections: {
    content: defineCollection(
      // adds the robots frontmatter key to the collection
      asSitemapCollection({
        type: 'page',
        source: '**/*.md',
      }),
    ),
  },
})

Filtering Content

You can pass a filter function to asSitemapCollection() to exclude entries at runtime. This is useful for filtering out draft posts, future-dated content, or any entries that shouldn't appear in the sitemap.

content.config.ts
import { defineCollection, defineContentConfig, z } from '@nuxt/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'

export default defineContentConfig({
  collections: {
    // The `name` option must match the collection key — here both are 'blog'
    blog: defineCollection(
      asSitemapCollection({
        type: 'page',
        source: 'blog/**/*.md',
        schema: z.object({
          date: z.string().optional(),
          draft: z.boolean().optional(),
        }),
      }, {
        name: 'blog', // ← must match the key above
        filter: (entry) => {
          // exclude drafts and future-dated posts
          if (entry.draft) return false
          if (entry.date && new Date(entry.date) > new Date()) return false
          return true
        },
      }),
    ),
  },
})

The name option must match the collection key exactly (e.g. if your collection key is blog, use name: 'blog'). This is how the filter is matched to the correct collection at runtime.

The filter function receives the full content entry including your custom schema fields and should return true to include, false to exclude.

Transforming URLs with onUrl

Use the onUrl callback to transform the sitemap entry for each item in a collection. The callback receives the resolved URL object — mutate it directly to change loc, lastmod, priority, or any other field.

This is especially useful when a collection uses prefix: '' in its source config, which strips the directory prefix from content paths.

content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'

export default defineContentConfig({
  collections: {
    content_en: defineCollection(
      asSitemapCollection({
        type: 'page',
        source: { include: 'en/**', prefix: '' },
      }),
    ),
    content_zh: defineCollection(
      asSitemapCollection({
        type: 'page',
        source: { include: 'zh/**', prefix: '' },
      }, {
        name: 'content_zh',
        onUrl(url) {
          url.loc = `/zh${url.loc}`
        },
      }),
    ),
  },
})

Without onUrl, both collections would produce loc: '/about' for their about.md files. With the transform, the zh collection entries correctly produce loc: '/zh/about'.

The callback also receives the full content entry and collection name, so you can use any content field to drive sitemap values:

asSitemapCollection({
  type: 'page',
  source: 'blog/**/*.md',
  schema: z.object({
    featured: z.boolean().optional(),
  }),
}, {
  name: 'blog',
  onUrl(url, entry) {
    url.loc = url.loc.replace('/posts/', '/blog/')
    url.priority = entry.featured ? 1.0 : 0.5
  },
})

The name option must match the collection key exactly (e.g. if your collection key is content_zh, use name: 'content_zh').

Due to current Nuxt Content v3 limitations, you must load the sitemap module before the content module.

export default defineNuxtConfig({
  modules: [
    '@nuxtjs/sitemap',
    '@nuxt/content' // <-- Must be after @nuxtjs/sitemap
  ]
})

Setup Nuxt Content v2

In Nuxt Content v2 markdown files require either Document Driven Mode, a path key to be set in the frontmatter or the strictNuxtContentPaths option to be enabled.

nuxt.config.ts
export default defineNuxtConfig({
  // things just work!
  content: {
    documentDriven: true
  }
})

If you're not using documentDriven mode and your content paths are the same as their real paths, you can enable strictNuxtContentPaths to get the same behaviour.

nuxt.config.ts
export default defineNuxtConfig({
  sitemap: {
    strictNuxtContentPaths: true
  }
})

Advanced: Nuxt Content App Source

If you'd like to set up a more automated Nuxt Content integration and you're not using Document Driven mode, you can add content to the sitemap as you would with Dynamic URLs.

An example of what this might look like is below, customize to your own needs.

server/api/__sitemap__/urls.ts
import { defineEventHandler } from 'h3'
import type { ParsedContent } from '@nuxt/content/dist/runtime/types'
import { serverQueryContent } from '#content/server'
import { asSitemapUrl, defineSitemapEventHandler } from '#imports'

export default defineSitemapEventHandler(async (e) => {
  const contentList = (await serverQueryContent(e).find()) as ParsedContent[]
  return contentList
    .filter(c => c._path.startsWith('_articles'))
    .map((c) => {
      return asSitemapUrl({
        loc: `/blog/${c._path.replace('_articles', '')}`,
        lastmod: updatedAt
      })
    })
})
export default defineNuxtConfig({
  sitemap: {
    sources: [
      '/api/__sitemap__/urls'
    ]
  }
})

Usage

Frontmatter sitemap

Use the sitemap key in your frontmatter to add a page to your sitemap.

You can provide any data that you would normally provide in the sitemap configuration.

Markdown Example

---
sitemap:
  loc: /my-page
  lastmod: 2021-01-01
  changefreq: monthly
  priority: 0.8
---

# My Page

YAML Example

content/pages/about.yml
title: About Page
description: Learn more about us
sitemap:
  lastmod: 2025-05-13
  changefreq: monthly
  priority: 0.8
content: |
  This is the about page content

JSON Example

content/products/widget.json
{
  "title": "Widget Product",
  "price": 99.99,
  "sitemap": {
    "lastmod": "2025-05-14",
    "changefreq": "weekly",
    "priority": 0.9
  }
}

Exclude from Sitemap

If you'd like to exclude a page from the sitemap, you can set sitemap: false in the frontmatter or robots: false if you'd like to exclude it from search engines.

---
sitemap: false
robots: false
---

Troubleshooting Exclusions

If sitemap: false or robots: false aren't working, check the following:

Nuxt Content v3 — Ensure you've wrapped your collection with asSitemapCollection() in content.config.ts:

content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { asSitemapCollection } from '@nuxtjs/sitemap/content'

export default defineContentConfig({
  collections: {
    content: defineCollection(
      asSitemapCollection({
        type: 'page',
        source: '**/*.md',
      }),
    ),
  },
})

Nuxt Content v2 — Ensure you have Document Driven mode or strictNuxtContentPaths enabled:

nuxt.config.ts
export default defineNuxtConfig({
  content: {
    documentDriven: true
  },
  // OR
  sitemap: {
    strictNuxtContentPaths: true
  }
})

Module load order — The sitemap module must be loaded before the content module:

nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxtjs/sitemap', // Must be before @nuxt/content
    '@nuxt/content'
  ]
})

If pages still appear after these checks, clear .nuxt and rebuild.

Did this page help you?