Collection Architecture
How collections are defined and managed in the codebase.
Overview
Collections follow a centralized specification pattern that separates data from logic. This allows both nuxt.config.ts and content.config.ts to share the same collection definitions without circular dependencies.
File Structure
collections.ts # Pure data — single source of truth
shared/collectionUtils.ts # Types + transforms + utilities
shared/contentUtils.ts # Content utility functions
content.config.ts # Builds Nuxt Content collections
nuxt.config.ts # Builds llms sections
collections.ts
The root collections.ts file contains only data — no imports, no types, no logic. It uses Nuxt Content's native CollectionSource structure:
export const collectionsRegistry = {
landing: {
source: { include: '*.md' }, // No prefix = root ('/')
frontpage: '/',
navigation: false
},
docs: {
source: { include: 'docs/**', prefix: '/docs' },
frontpage: '/docs/about',
navigation: {
title: 'Docs',
description: 'Portal documentation.',
icon: 'i-lucide-book-open'
}
},
engineering: {
source: {
repository: 'https://github.com/ontopix/engineering-handbook',
include: '**/*.md',
prefix: '/engineering'
},
frontpage: '/engineering/readme',
navigation: {
title: 'Engineering Handbook',
description: 'Engineering standards and guidelines.',
icon: 'i-lucide-wrench'
}
},
schemas: {
source: {
repository: 'https://github.com/ontopix/schemas',
include: 'doc/**/*.md',
prefix: '/schemas'
},
frontpage: '/schemas/readme',
navigation: {
title: 'Schemas',
description: 'Data schemas and type definitions.',
icon: 'i-lucide-database',
badge: 'Draft' // Collection-level badge
}
}
} as const
The as const assertion provides full type inference without explicit interfaces.
shared/collectionUtils.ts
The shared collection utilities module provides:
- Type definitions for collection specs
- Re-export of the typed registry
- Badge utilities for normalizing badge props
- Transform functions like
buildLlmsSections() - Accessor functions for collection lookup
- GitHub URL generators for remote collections
import { collectionsRegistry } from '../collections'
import type { CollectionSource } from '@nuxt/content'
import type { BadgeProps } from '@nuxt/ui'
// Extended source type with optional prefix (Nuxt Content native pattern)
// When prefix is omitted, content is served from root ('/')
export type CollectionSourceWithPrefix = CollectionSource & {
prefix?: string
}
// Types
export interface NavigationConfig {
title: string
description?: string
icon?: string
badge?: Badge // String shorthand or full BadgeProps
}
export interface CollectionSpec {
source: CollectionSourceWithPrefix
frontpage: string
navigation: false | NavigationConfig
}
// Get prefix from source (helper)
export function getCollectionPrefix(spec: CollectionSpec): string {
return spec.source.prefix ?? ''
}
// Re-export typed registry
export { collectionsRegistry }
// Transform for nuxt-llms sections
export function buildLlmsSections(spec: Record<string, CollectionSpec>) {
return Object.entries(spec)
.filter(([_, s]) => s.navigation !== false)
.map(([key, s]) => {
const nav = s.navigation as NavigationConfig
return {
title: nav.title,
description: nav.description,
contentCollection: key,
contentFilters: [
{ field: 'path', operator: 'LIKE' as const, value: `${getCollectionPrefix(s)}/%` }
]
}
})
}
content.config.ts
Imports the registry and builds Nuxt Content collections:
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { collectionsRegistry } from './shared/collectionUtils'
const collections = Object.fromEntries(
Object.entries(collectionsRegistry).map(([key, spec]) => {
return [key, defineCollection({
type: 'page',
source: spec.source, // Pass source directly
// ... schema definition
})]
})
)
export default defineContentConfig({ collections })
nuxt.config.ts
Imports the registry and transform for llms configuration:
import { collectionsRegistry, buildLlmsSections } from './shared/collectionUtils'
export default defineNuxtConfig({
llms: {
sections: buildLlmsSections(collectionsRegistry)
}
})
Why This Pattern?
Problem: Circular Dependencies
Originally, collectionsRegistry was defined in content.config.ts and exported for use in nuxt.config.ts. This caused issues:
nuxt.config.tsimports fromcontent.config.tscontent.config.tsimportszfromzod/v4- Zod schema evaluation happens before Nuxt Content's validator context is initialized
- Build fails with "Zod is not installed" error
Solution: Separation of Concerns
By extracting the pure data to collections.ts:
- No circular dependencies — data file has no imports
- Single source of truth — one place to add/edit collections
- Type safety — types are defined in
shared/collectionUtils.tsand used everywhere - Automatic propagation — changes flow to navigation, llms.txt, and MCP
Adding a New Collection
- Edit
collections.ts:
export const collectionsRegistry = {
// ... existing
myDocs: {
source: {
repository: 'https://github.com/ontopix/my-repo',
include: 'docs/**/*.md',
prefix: '/my-docs' // URL prefix is part of source
},
frontpage: '/my-docs/',
navigation: {
title: 'My Documentation',
description: 'Documentation for my project.',
icon: 'i-lucide-book',
badge: 'Beta' // Optional status indicator
}
}
} as const
- Run
pnpm dev— the collection appears automatically in:- Navigation sidebar (with badge if specified)
- Homepage grid (with badge if specified)
/llms.txtsections- MCP tool responses
No changes needed to content.config.ts or nuxt.config.ts.
Collection Properties
| Property | Type | Required | Description |
|---|---|---|---|
source | CollectionSourceWithPrefix | Yes | Nuxt Content source config with optional prefix |
source.include | string | Yes | Glob pattern for files |
source.prefix | string | No | URL prefix (e.g., /docs). Omit for root (/) |
source.repository | string | object | Remote only | GitHub URL or { url, branch } |
frontpage | string | Yes | Default landing page for the collection |
navigation | false | object | Yes | Navigation config or false to hide |
navigation.title | string | When visible | Display name in navigation and llms |
navigation.description | string | No | Used in llms.txt sections |
navigation.icon | string | No | Lucide icon identifier |
navigation.badge | Badge | No | Status badge (see Badges) |
Source Configuration
Collections use Nuxt Content's native source structure with prefix as part of source:
Root Collection (No Prefix)
source: { include: '*.md' } // Served from root ('/')
Bundled Source with Prefix
source: { include: 'docs/**', prefix: '/docs' }
Remote Source (GitHub)
source: {
repository: 'https://github.com/ontopix/my-repo',
include: '**/*.md',
prefix: '/my-docs'
}
Remote with Branch/Tag
source: {
repository: {
url: 'https://github.com/ontopix/my-repo',
branch: 'v2' // or 'tag': 'v1.0.0'
},
include: '**/*.md',
prefix: '/my-docs'
}
With Exclusions
source: {
repository: 'https://github.com/ontopix/my-repo',
include: '**/*.md',
exclude: ['**/drafts/**', '**/internal/**'],
prefix: '/my-docs'
}