
Nuxt Content
- 148 installs
- 202 repo stars
- Updated August 4, 2026
- secondsky/claude-skills
Helps with ai & agent building tasks during AI-assisted development.
About
nuxt-content is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- nuxt-content
- AI & Agent Building
- AI-coding skill
Nuxt Content by the numbers
- 148 all-time installs (skills.sh)
- +12 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #3,400 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/secondsky/claude-skills --skill nuxt-contentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 148 |
|---|---|
| repo stars | ★ 202 |
| Last updated | August 4, 2026 |
| Repository | secondsky/claude-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Nuxt Content v3
Status: Production Ready Last Updated: 2025-01-10 Dependencies: None Latest Versions: @nuxt/content@^3.0.0, nuxt-studio@^0.1.0-alpha, zod@^4.1.12, valibot@^0.42.0, better-sqlite3@^11.0.0
---
Overview
Nuxt Content v3 is a powerful Git-based CMS for Nuxt projects that manages content through Markdown, YAML, JSON, and CSV files. It transforms content files into structured data with type-safe queries, automatic validation, and SQL-based storage for optimal performance.
What's New in v3
Major Improvements:
- Content Collections: Structured data organization with type-safe queries, automatic validation, and advanced query builder
- SQL-Based Storage: Production uses SQL (vs. large bundle sizes in v2) for optimized queries and universal compatibility (server/serverless/edge/static)
- Full TypeScript Integration: Automatic types for all collections and APIs
- Enhanced Performance: Ultra-fast data retrieval with adapter-based SQL system
- Nuxt Studio Integration: Self-hosted content editing in production with GitHub sync
When to Use This Skill
Use this skill when:
- Building blogs, documentation sites, or content-heavy applications
- Managing content with Markdown, YAML, JSON, or CSV files
- Implementing Git-based content workflows
- Creating type-safe content queries
- Deploying to Cloudflare (Pages/Workers) or Vercel
- Setting up production content editing with Nuxt Studio
- Building searchable content with full-text search
- Creating navigation systems from content structure
---
Quick Start (10 Minutes)
1. Install Nuxt Content
# Bun (recommended)
bun add @nuxt/content better-sqlite3
# npm
npm install @nuxt/content better-sqlite3
# pnpm
pnpm add @nuxt/content better-sqlite3Why this matters:
@nuxt/contentis the core CMS modulebetter-sqlite3provides SQL storage for optimal performance- Zero configuration required for basic usage
2. Register Module
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content']
})CRITICAL:
- Module must be added to
modulesarray (notbuildModules) - No additional configuration needed for basic setup
3. Create First Collection
// content.config.ts
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
content: defineCollection({
type: 'page',
source: '**/*.md',
schema: z.object({
tags: z.array(z.string()).optional(),
date: z.date().optional()
})
})
}
})Create content file:
<!-- content/index.md -->
---
title: Hello World
description: My first Nuxt Content page
tags: ['nuxt', 'content']
---
# Welcome to Nuxt Content v3
This is my first content-driven site!4. Query and Render Content
<!-- pages/[...slug].vue -->
<script setup>
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () =>
queryCollection('content').path(route.path).first()
)
</script>
<template>
<ContentRenderer v-if="page" :value="page" />
</template>See Full Template: templates/blog-collection-setup.ts
---
Critical Rules
Always Do
1. Define Collections in content.config.ts before querying 2. Use ISO 8601 Date Format: 2024-01-15 or 2024-01-15T10:30:00Z 3. Restart Dev Server after changing content.config.ts 4. Use `.only()` to select specific fields (performance) 5. Place MDC Components in components/content/ directory 6. Use Zero-Padded Prefixes for numeric sorting: 01-, 02- 7. Install Database Connector: better-sqlite3 required 8. Specify Language in code blocks for syntax highlighting 9. Use `<!--more-->` in content to separate excerpts 10. D1 Binding Must Be "DB" (case-sensitive) on Cloudflare
Never Do
1. Don't Query Before Defining Collection in content.config.ts 2. Don't Use Non-ISO Date Formats (e.g., "January 15, 2024") 3. Don't Forget to Restart dev server after config changes 4. Don't Query All Fields when you only need some (use .only()) 5. Don't Place Components outside components/content/ 6. Don't Use Single-Digit Prefixes (use 01- not 1-) 7. Don't Skip Database Connector installation 8. Don't Forget Language in code fences 9. Don't Expect Excerpts without <!--more--> divider 10. Don't Use Different Binding Names for D1 (must be "DB")
---
Content Collections
Defining Collections
Collections organize related content with shared configuration:
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
title: z.string(),
date: z.date(),
tags: z.array(z.string()).default([])
})
}),
authors: defineCollection({
type: 'data',
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
bio: z.string()
})
})
}
})---
Collection Types
Page Type (type: 'page')
Use for: Content that maps to URLs
Features:
- Auto-generates paths from file structure
- Built-in fields:
path,title,description,body,navigation - Perfect for: blogs, docs, marketing pages
Path Mapping:
content/index.md → /
content/about.md → /about
content/blog/hello.md → /blog/helloData Type (type: 'data')
Use for: Structured data without URLs
Features:
- Complete schema control
- No automatic path generation
- Perfect for: authors, products, configs
---
Schema Validation
With Zod v4 (Recommended)
bun add -D zod@^4.1.12
# or: npm install -D zod@^4.1.12import { z } from 'zod'
schema: z.object({
title: z.string(),
date: z.date(),
published: z.boolean().default(false),
tags: z.array(z.string()).optional(),
category: z.enum(['news', 'tutorial', 'update'])
})With Valibot (Alternative)
bun add -D valibot@^0.42.0
# or: npm install -D valibot@^0.42.0import * as v from 'valibot'
schema: v.object({
title: v.string(),
date: v.date(),
published: v.boolean(false),
tags: v.optional(v.array(v.string()))
})---
Querying Content
Basic Queries
// Get all posts
const posts = await queryCollection('blog').all()
// Get single post by path
const post = await queryCollection('blog').path('/blog/hello').first()
// Get post by ID
const post = await queryCollection('blog').where('_id', '=', 'hello').first()---
Field Selection
// Select specific fields (performance optimization)
const posts = await queryCollection('blog')
.only(['title', 'description', 'date', 'path'])
.all()
// Exclude fields
const posts = await queryCollection('blog')
.without(['body'])
.all()---
Where Conditions
// Single condition
const posts = await queryCollection('blog')
.where('published', '=', true)
.all()
// Multiple conditions
const posts = await queryCollection('blog')
.where('published', '=', true)
.where('category', '=', 'tutorial')
.all()
// Operators: =, !=, <, <=, >, >=, in, not-in, like
const recent = await queryCollection('blog')
.where('date', '>', new Date('2024-01-01'))
.all()
const tagged = await queryCollection('blog')
.where('tags', 'in', ['nuxt', 'vue'])
.all()---
Ordering and Pagination
// Sort by field
const posts = await queryCollection('blog')
.sort('date', 'DESC')
.all()
// Pagination
const posts = await queryCollection('blog')
.sort('date', 'DESC')
.limit(10)
.offset(0)
.all()---
Counting
// Count results
const total = await queryCollection('blog')
.where('published', '=', true)
.count()---
Server-Side Queries
// server/api/posts.get.ts
export default defineEventHandler(async (event) => {
const posts = await queryCollection('blog')
.where('published', '=', true)
.sort('date', 'DESC')
.all()
return { posts }
})---
Navigation
Auto-generate navigation tree from content structure:
const navigation = await queryCollectionNavigation('blog').all()Returns hierarchical structure:
[
{
title: 'Getting Started',
path: '/docs/getting-started',
children: [{ title: 'Installation', path: '/docs/getting-started/installation' }]
}
]Advanced patterns (filters, ordering, custom structures): See references/collection-examples.md
---
MDC Syntax (Markdown Components)
Basic Component Syntax
<!-- Default slot -->
::my-alert
This is an alert message
::
<!-- Named slots -->
::my-card
#title
Card Title
#default
Card content here
::Component:
<!-- components/content/MyAlert.vue -->
<template>
<div class="alert">
<slot />
</div>
</template>---
Props
::my-button{href="/docs" type="primary"}
Click me
::Component:
<!-- components/content/MyButton.vue -->
<script setup>
defineProps<{
href: string
type: 'primary' | 'secondary'
}>()
</script>---
Full-Text Search
// Search across all content
const results = await queryCollectionSearchSections('blog', 'nuxt content')
.where('published', '=', true)
.all()Returns:
[
{
id: 'hello',
path: '/blog/hello',
title: 'Hello World',
content: '...matching text...'
}
]---
Deployment
Cloudflare Pages + D1
bun add -D @nuxthub/core
bunx wrangler d1 create nuxt-content
bun run build && bunx wrangler pages deploy distwrangler.toml:
[[d1_databases]]
binding = "DB" # Must be exactly "DB" (case-sensitive)
database_name = "nuxt-content"
database_id = "your-database-id"CRITICAL: D1 binding MUST be named DB (case-sensitive).
See: references/deployment-checklists.md for complete Cloudflare deployment guide with troubleshooting.
---
Vercel
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
routeRules: {
'/blog/**': { prerender: true }
}
})vercel deploySee: references/deployment-checklists.md for complete Vercel configuration and prerender strategy.
---
Nuxt Studio Integration
Enable Studio
bun add -D nuxt-studio@alpha
# or: npm install -D nuxt-studio@alpha// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content', 'nuxt-studio'],
studio: {
enabled: true,
gitInfo: {
name: 'your-name',
email: 'your-email@example.com'
}
}
})OAuth Setup
1. Create GitHub OAuth App 2. Set Authorization callback URL: https://yourdomain.com/api/__studio/oauth/callback 3. Add client ID and secret to environment variables
CRITICAL: Callback URL must match production domain exactly (including https://).
---
Top 5 Critical Issues
Issue #1: Collection Not Found
Error: Collection 'xyz' not found
Solution: Define collection in content.config.ts and restart dev server
rm -rf .nuxt && bun dev---
Issue #2: Date Validation Failure
Error: Validation error: Expected date, received string
Solution: Use ISO 8601 format:
---
date: 2024-01-15 # ✅ Correct
# NOT: "January 15, 2024" # ❌ Wrong
------
Issue #3: D1 Binding Not Found (Cloudflare)
Error: DB is not defined
Solution: D1 binding name must be exactly DB (case-sensitive) in Cloudflare dashboard.
---
Issue #4: MDC Components Not Rendering
Error: Components show as raw text
Solution: Place components in components/content/ with exact name matching:
<!-- components/content/MyAlert.vue -->::my-alert
Content
::---
Issue #5: Navigation Not Updating
Error: New content doesn't appear in navigation
Solution: Clear .nuxt cache:
rm -rf .nuxt && bun dev---
See All 18 Issues: references/error-catalog.md
---
When to Load References
Load `references/error-catalog.md` when:
- User encounters error beyond Top 5 shown above
- Debugging collection validation, schema errors, or deployment issues
- Need complete error catalog with all 18 documented solutions and sources
Load `references/collection-examples.md` when:
- Setting up advanced collection types (data vs page)
- Implementing complex schema validation patterns
- Need examples for Zod v4 or Valibot schemas
- Working with multiple collection configurations
Load `references/query-operators.md` when:
- Building complex queries beyond basic examples
- Need full reference of all operators (=, !=, <, >, <=, >=, in, not-in, like)
- Implementing pagination, sorting, or field selection
- Troubleshooting query syntax errors
Load `references/deployment-checklists.md` when:
- Deploying to specific platform (Cloudflare Pages, Cloudflare Workers D1, Vercel)
- Setting up production environment configurations
- Troubleshooting deployment-specific errors (D1 binding, prerender routes)
- Need platform-specific wrangler.toml or vercel.json examples
Load `references/mdc-syntax-reference.md` when:
- Implementing custom MDC (Markdown Components)
- Debugging component rendering issues
- Need complete syntax reference for props, slots, nesting
- Creating advanced content components
Load `references/studio-setup-guide.md` when:
- Setting up Nuxt Studio for production content editing
- Configuring GitHub OAuth authentication
- Enabling self-hosted content editing with GitHub sync
- Troubleshooting Studio authentication or Git sync issues
Templates (templates/):
blog-collection-setup.ts- Complete blog setup with collections, queries, navigation, search, and deployment (334 lines)
---
Performance Tips
1. Use `.only()` to select specific fields 2. Enable Caching for production 3. Use Pagination for large collections 4. Prerender Static Routes on Vercel 5. Use SQL Storage (better-sqlite3) for optimal performance
---
Best Practices
1. Always define collections before querying 2. Use TypeScript for type-safe queries 3. Validate schemas with Zod or Valibot 4. Place MDC components in components/content/ 5. Use ISO 8601 date format 6. Add <!--more--> for excerpts 7. Specify language in code blocks 8. Clear .nuxt after config changes 9. For Cloudflare: D1 binding must be "DB" 10. Use pagination for large datasets
---
Integration with Other Skills
This skill composes well with:
- nuxt-v4 → Core Nuxt framework
- nuxt-ui-v4 → UI component library
- cloudflare-worker-base → Cloudflare deployment
- tailwind-v4-shadcn → Styling
- drizzle-orm-d1 → Additional database queries
- cloudflare-d1 → Database integration
---
Secure Installation
When installing CMS packages, follow supply chain security best practices:
- Block post-install scripts —
npm config set ignore-scripts true(or Bun: disabled by default) - Cooldown period — Wait 7 days for new package versions to be vetted by the community
- Audit before installing — Run
socket package score npm <pkg>or usesocket npm install <pkg>to check packages
Load the dependency-upgrade skill for full security configuration including Socket CLI integration, cooldown setup, lockfile validation, and CI enforcement.
Additional Resources
Official Documentation:
- Nuxt Content Docs: https://content.nuxt.com
- GitHub: https://github.com/nuxt/content
- Nuxt Studio: https://nuxt.studio
Examples:
- Official Examples: https://github.com/nuxt/content/tree/main/examples
- Starter Templates: https://github.com/nuxt-themes
---
Production Tested: Documentation sites, blogs, content platforms Last Updated: 2025-01-27 Token Savings: ~60% (reduces content + error documentation)
// Blog Collection Setup Example
// Complete configuration for a blog with Nuxt Content
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
// Required fields
title: z.string(),
description: z.string(),
date: z.date(),
author: z.string(),
// Optional fields with defaults
published: z.boolean().default(false),
featured: z.boolean().default(false),
tags: z.array(z.string()).default([]),
// Images
image: z.string(),
imageAlt: z.string().optional(),
// Categories with enum
category: z.enum([
'news',
'tutorial',
'update',
'announcement',
'case-study'
]),
// SEO fields
seo: z.object({
ogImage: z.string().optional(),
ogDescription: z.string().optional(),
keywords: z.array(z.string()).optional()
}).optional(),
// Reading time (auto-calculated or manual)
readingTime: z.number().optional()
})
}),
// Authors as separate collection
authors: defineCollection({
type: 'data',
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
role: z.string().optional(),
social: z.object({
github: z.string().optional(),
twitter: z.string().optional(),
linkedin: z.string().optional(),
website: z.string().url().optional()
}).optional()
})
})
}
})
/*
File Structure:
===============
content/
blog/
2024-01-15-first-post.md
2024-01-20-second-post.md
2024-02-01-tutorial.md
authors/
john-doe.yml
jane-smith.yml
Example Blog Post (content/blog/2024-01-15-first-post.md):
===========================================================
---
title: 'Getting Started with Nuxt Content'
description: 'Learn how to build a blog with Nuxt Content v3'
date: 2024-01-15
author: 'john-doe'
published: true
featured: true
tags: ['nuxt', 'content', 'tutorial']
image: '/images/blog/nuxt-content.jpg'
imageAlt: 'Nuxt Content logo'
category: 'tutorial'
seo:
ogImage: '/images/blog/nuxt-content-og.jpg'
keywords: ['nuxt', 'cms', 'markdown']
readingTime: 5
---
# Getting Started with Nuxt Content
Content goes here...
<!--more-->
Full article content after the excerpt...
---
Example Author (content/authors/john-doe.yml):
===============================================
name: John Doe
bio: Full-stack developer and technical writer
avatar: /images/authors/john-doe.jpg
role: Senior Developer
social:
github: johndoe
twitter: johndoe
website: https://johndoe.com
Query Examples:
===============
// List all published posts
const posts = await queryCollection('blog')
.where('published', '=', true)
.select('path', 'title', 'description', 'date', 'author', 'image', 'category')
.order('date', 'DESC')
.all()
// Get featured posts
const featured = await queryCollection('blog')
.where('featured', '=', true)
.where('published', '=', true)
.limit(3)
.all()
// Filter by category
const tutorials = await queryCollection('blog')
.where('category', '=', 'tutorial')
.where('published', '=', true)
.all()
// Search by tags
const nuxtPosts = await queryCollection('blog')
.where('tags', 'IN', ['nuxt'])
.all()
// Get post with author
const post = await queryCollection('blog')
.path('/blog/first-post')
.first()
const author = await queryCollection('authors')
.where('id', '=', post.author)
.first()
*/
// Complete Nuxt Content Configuration Example
// Copy this to your project root as content.config.ts
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
// Blog Collection
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
title: z.string(),
description: z.string(),
date: z.date(),
author: z.string(),
tags: z.array(z.string()).default([]),
image: z.string(),
published: z.boolean().default(false),
category: z.enum(['news', 'tutorial', 'update', 'announcement'])
})
}),
// Documentation Collection
docs: defineCollection({
type: 'page',
source: 'docs/**/*.md',
schema: z.object({
title: z.string(),
description: z.string(),
category: z.string(),
order: z.number().optional(),
badge: z.string().optional(),
icon: z.string().optional()
})
}),
// Authors (Data Collection)
authors: defineCollection({
type: 'data',
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
email: z.string().email().optional(),
social: z.object({
github: z.string(),
twitter: z.string().optional(),
linkedin: z.string().optional(),
website: z.string().url().optional()
}).optional()
})
}),
// Categories (Data Collection)
categories: defineCollection({
type: 'data',
source: 'categories/*.json',
schema: z.object({
name: z.string(),
slug: z.string(),
description: z.string(),
color: z.string().optional()
})
})
}
})
// Documentation Collection Setup Example
// Complete configuration for documentation site with Nuxt Content
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
docs: defineCollection({
type: 'page',
source: 'docs/**/*.md',
schema: z.object({
// Required fields
title: z.string(),
description: z.string(),
// Organization
category: z.string(),
order: z.number().optional(),
// UI metadata
badge: z.string().optional(), // "New", "Beta", "Deprecated"
icon: z.string().optional(), // Icon identifier
// Versioning
version: z.string().optional(),
lastUpdated: z.date().optional(),
// Navigation control
navigation: z.boolean().default(true),
// Related docs
related: z.array(z.string()).optional()
})
})
}
})
/*
File Structure with Numeric Prefixes:
======================================
content/
docs/
.navigation.yml
01-getting-started/
.navigation.yml
01-installation.md
02-configuration.md
03-first-steps.md
02-guides/
.navigation.yml
01-basics.md
02-advanced.md
03-best-practices.md
03-api/
01-overview.md
02-collections.md
03-queries.md
Navigation Metadata (.navigation.yml):
=======================================
# content/docs/.navigation.yml
title: Documentation
icon: i-lucide-book
badge: v3
# content/docs/01-getting-started/.navigation.yml
title: Getting Started
icon: i-lucide-square-play
description: Learn the basics
Example Doc Page (content/docs/01-getting-started/01-installation.md):
======================================================================
---
title: Installation
description: Install and set up Nuxt Content v3
category: getting-started
order: 1
badge: New
icon: i-lucide-download
lastUpdated: 2024-01-15
related: ['/docs/getting-started/configuration', '/docs/getting-started/first-steps']
---
# Installation
Install Nuxt Content v3...
[Content continues...]
Query Examples:
===============
// Get navigation tree
const navigation = await queryCollectionNavigation('docs')
// Get page by path
const page = await queryCollection('docs')
.path(route.path)
.first()
// Get all docs in category
const gettingStarted = await queryCollection('docs')
.where('category', '=', 'getting-started')
.order('order', 'ASC')
.all()
// Get latest updated docs
const recentlyUpdated = await queryCollection('docs')
.where('lastUpdated', 'IS NOT NULL')
.order('lastUpdated', 'DESC')
.limit(5)
.all()
Navigation Utilities:
=====================
import {
findPageHeadline,
findPageBreadcrumb,
findPageChildren,
findPageSiblings
} from '@nuxt/content/utils'
// Get section title
const headline = findPageHeadline(navigation, '/docs/getting-started/installation')
// Build breadcrumbs
const breadcrumb = findPageBreadcrumb(navigation, route.path, {
current: true
})
// Get child pages
const children = findPageChildren(navigation, '/docs/getting-started')
// Get sibling pages (prev/next)
const siblings = findPageSiblings(navigation, route.path)
Component Example (pages/docs/[...slug].vue):
==============================================
<script setup>
import { findPageBreadcrumb, findPageSiblings } from '@nuxt/content/utils'
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () =>
queryCollection('docs').path(route.path).first()
)
const { data: navigation } = await useAsyncData('docs-nav', () =>
queryCollectionNavigation('docs')
)
const breadcrumb = computed(() =>
findPageBreadcrumb(navigation.value, route.path)
)
const siblings = computed(() =>
findPageSiblings(navigation.value, route.path)
)
const prev = computed(() => {
const index = siblings.value?.findIndex(s => s._path === route.path)
return index > 0 ? siblings.value[index - 1] : null
})
const next = computed(() => {
const index = siblings.value?.findIndex(s => s._path === route.path)
return index < siblings.value.length - 1 ? siblings.value[index + 1] : null
})
useHead({
title: page.value.title,
meta: [
{ name: 'description', content: page.value.description }
]
})
</script>
<template>
<div class="docs-layout">
<!-- Sidebar Navigation -->
<aside class="sidebar">
<nav>
<!-- Render navigation tree -->
</nav>
</aside>
<!-- Main Content -->
<main class="content">
<!-- Breadcrumbs -->
<nav class="breadcrumb">
<NuxtLink
v-for="(item, index) in breadcrumb"
:key="item._path"
:to="item._path"
>
{{ item.title }}
<span v-if="index < breadcrumb.length - 1">/</span>
</NuxtLink>
</nav>
<!-- Page Title & Badge -->
<header>
<h1>
{{ page.title }}
<span v-if="page.badge" class="badge">{{ page.badge }}</span>
</h1>
<p>{{ page.description }}</p>
</header>
<!-- Content -->
<ContentRenderer :value="page" />
<!-- Prev/Next Navigation -->
<nav class="page-nav">
<NuxtLink v-if="prev" :to="prev._path">
← {{ prev.title }}
</NuxtLink>
<NuxtLink v-if="next" :to="next._path">
{{ next.title }} →
</NuxtLink>
</nav>
</main>
<!-- Table of Contents (optional) -->
<aside class="toc">
<!-- TOC component -->
</aside>
</div>
</template>
Layout Example (layouts/docs.vue):
===================================
<script setup>
const { data: navigation } = await useAsyncData('docs-nav', () =>
queryCollectionNavigation('docs')
)
</script>
<template>
<div class="docs-layout">
<Header />
<div class="container">
<Sidebar :navigation="navigation" />
<main>
<slot />
</main>
</div>
<Footer />
</div>
</template>
*/
// Complete Nuxt Configuration with Nuxt Content & Studio
// Copy relevant sections to your nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/content',
'nuxt-studio' // Optional: Remove if not using Nuxt Studio
],
// Nuxt Content Configuration (optional - has sensible defaults)
content: {
// All configuration is optional
},
// Nuxt Studio Configuration (optional)
studio: {
route: '/_studio', // Admin route
repository: {
provider: 'github',
owner: 'your-username',
repo: 'your-repo',
branch: process.env.STUDIO_GITHUB_BRANCH_NAME || 'main'
},
// Optional: Development mode
development: {
sync: true // Writes changes to local files
}
},
// Nitro Configuration for Deployment
nitro: {
// Choose preset based on deployment platform:
preset: 'cloudflare_pages', // or 'cloudflare', 'vercel', 'vercel-edge', 'netlify'
// Prerendering configuration
prerender: {
crawlLinks: true,
routes: ['/']
}
},
// Route Rules for Hybrid Rendering
routeRules: {
// Static at build time
'/': { prerender: true },
'/about': { prerender: true },
// ISR (Incremental Static Regeneration)
'/blog/**': { isr: 3600 }, // Regenerate every hour
// SWR (Stale While Revalidate)
'/docs/**': { swr: 3600 }, // Cache for 1 hour
// Always SSR
'/_studio/**': { ssr: true },
// API routes
'/api/**': { cors: true }
},
// TypeScript Configuration
typescript: {
strict: true
},
// App Configuration
app: {
head: {
title: 'My Nuxt Content Site',
meta: [
{ name: 'description', content: 'Built with Nuxt Content v3' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
}
},
// Development Server
devtools: { enabled: true }
})
Nuxt Content Collection Examples
Quick reference for common collection patterns.
Blog Collection
// content.config.ts
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
title: z.string(),
description: z.string(),
date: z.date(),
author: z.string(),
tags: z.array(z.string()).default([]),
image: z.string(),
published: z.boolean().default(false),
category: z.enum(['news', 'tutorial', 'update'])
})
})
}
})File Structure:
content/
blog/
2024-01-15-first-post.md
2024-01-20-second-post.mdQuery Examples:
// List all published posts
const posts = await queryCollection('blog')
.where('published', '=', true)
.order('date', 'DESC')
.all()
// Get post by path
const post = await queryCollection('blog')
.path('/blog/first-post')
.first()
// Filter by category and tags
const tutorials = await queryCollection('blog')
.where('category', '=', 'tutorial')
.where('tags', 'IN', ['nuxt', 'vue'])
.all()---
Documentation Collection
export default defineContentConfig({
collections: {
docs: defineCollection({
type: 'page',
source: 'docs/**/*.md',
schema: z.object({
title: z.string(),
description: z.string(),
category: z.string(),
order: z.number().optional(),
badge: z.string().optional(),
icon: z.string().optional()
})
})
}
})File Structure (with numeric prefixes):
content/
docs/
01-getting-started/
01-installation.md
02-configuration.md
02-guides/
01-basics.md
02-advanced.mdQuery with Navigation:
// Get navigation tree
const navigation = await queryCollectionNavigation('docs')
// Get specific page
const page = await queryCollection('docs')
.path(route.path)
.first()
// Get breadcrumbs
const breadcrumb = findPageBreadcrumb(navigation, route.path)---
Authors (Data Collection)
export default defineContentConfig({
collections: {
authors: defineCollection({
type: 'data', // Not URL-mapped
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
email: z.string().email(),
social: z.object({
github: z.string(),
twitter: z.string().optional(),
website: z.string().url().optional()
})
})
})
}
})File: content/authors/john-doe.yml
name: John Doe
bio: Full-stack developer
avatar: /images/john.jpg
email: john@example.com
social:
github: johndoe
twitter: johndoe
website: https://johndoe.comQuery:
// Get author by ID
const author = await queryCollection('authors')
.where('id', '=', 'john-doe')
.first()
// Get all authors
const authors = await queryCollection('authors').all()---
Multi-Language Collection
export default defineContentConfig({
collections: {
en: defineCollection({
type: 'page',
source: {
include: 'en/**/*.md',
prefix: '/en'
}
}),
fr: defineCollection({
type: 'page',
source: {
include: 'fr/**/*.md',
prefix: '/fr'
}
})
}
})File Structure:
content/
en/
index.md
about.md
blog/
post-1.md
fr/
index.md
about.md
blog/
post-1.mdURLs:
/en→content/en/index.md/en/about→content/en/about.md/fr→content/fr/index.md/fr/about→content/fr/about.md
---
Products Collection (E-Commerce)
export default defineContentConfig({
collections: {
products: defineCollection({
type: 'page',
source: 'products/**/*.md',
schema: z.object({
name: z.string(),
description: z.string(),
price: z.number(),
sku: z.string(),
category: z.string(),
images: z.array(z.string()),
inStock: z.boolean().default(true),
tags: z.array(z.string()).default([]),
specifications: z.object({
weight: z.string().optional(),
dimensions: z.string().optional(),
color: z.string().optional()
}).optional()
})
}),
categories: defineCollection({
type: 'data',
source: 'categories/*.json',
schema: z.object({
name: z.string(),
slug: z.string(),
description: z.string(),
image: z.string()
})
})
}
})Query Products:
// Get products by category
const products = await queryCollection('products')
.where('category', '=', 'electronics')
.where('inStock', '=', true)
.where('price', '<', 1000)
.order('price', 'ASC')
.all()
// Search products
const results = await queryCollection('products')
.where('name', 'LIKE', '%phone%')
.all()---
Remote Repository Collection
export default defineContentConfig({
collections: {
docs: defineCollection({
type: 'page',
source: {
include: 'docs/**/*.md',
repository: 'https://github.com/username/docs-repo',
authToken: process.env.GITHUB_TOKEN // For private repos
}
})
}
})Environment Variable:
# .env
GITHUB_TOKEN=ghp_your_token_here---
With Nuxt Studio Editor Metadata
import { property } from '@nuxt/content'
export default defineContentConfig({
collections: {
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
title: z.string(),
// Media picker in Studio
image: property(z.string()).editor({
input: 'media'
}),
// Dropdown select
category: property(z.string()).editor({
input: 'select',
options: ['news', 'tutorial', 'update']
}),
// Toggle switch
featured: property(z.boolean()).editor({
input: 'toggle'
})
})
})
}
})---
Best Practices
✅ Use meaningful collection names: blog, docs, products, not collection1 ✅ Define schemas for all fields: Enables validation and TypeScript types ✅ Use `type: 'page'` for URLs: Blog posts, docs, products ✅ Use `type: 'data'` for metadata: Authors, categories, settings ✅ Add defaults for optional fields: z.boolean().default(false) ✅ Use enums for fixed values: z.enum(['news', 'tutorial']) ✅ Organize with subdirectories: blog/, docs/, authors/
Deployment Checklists
Cloudflare Pages Checklist
Prerequisites
- [ ] Cloudflare account
- [ ] Wrangler CLI installed (optional)
- [ ] D1 database created
Configuration
- [ ] Build preset:
cloudflare_pagesinnuxt.config.ts - [ ] Build command:
npm run build - [ ] Build output:
.output/public
D1 Database
- [ ] D1 database created (
nuxt-content-db) - [ ] D1 binding configured
- [ ] Binding name is exactly
DB(case-sensitive)
Environment Variables
- [ ]
STUDIO_GITHUB_CLIENT_ID(if using Studio) - [ ]
STUDIO_GITHUB_CLIENT_SECRET(if using Studio) - [ ] Custom env vars as needed
Deployment
- [ ] GitHub repository connected
- [ ] Build successful
- [ ] D1 binding active
- [ ] Custom domain configured (optional)
- [ ] SSL/TLS verified
Verification
- [ ] Site loads correctly
- [ ] Content queries work
- [ ] Studio accessible (if configured)
- [ ] No console errors
---
Cloudflare Workers Checklist
Configuration
- [ ] Build preset:
cloudflareinnuxt.config.ts - [ ]
wrangler.tomlconfigured - [ ] D1 binding in
wrangler.toml
wrangler.toml Example
name = "nuxt-content-worker"
main = ".output/server/index.mjs"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "nuxt-content-db"
database_id = "your-database-id"Deployment
- [ ] Build:
nuxi build --preset=cloudflare - [ ] Deploy:
wrangler deploy
---
Vercel Checklist
Prerequisites
- [ ] Vercel account
- [ ] Vercel CLI installed (optional)
Configuration
- [ ] Framework: Nuxt (auto-detected)
- [ ] Build command:
npm run build - [ ] Output directory:
.output
Environment Variables
- [ ]
STUDIO_GITHUB_CLIENT_ID(if using Studio) - [ ]
STUDIO_GITHUB_CLIENT_SECRET(if using Studio) - [ ]
POSTGRES_URL(if using Vercel Postgres) - [ ] Custom env vars as needed
Database Options
Choose one:
- [ ] SQLite at
/tmp(default, zero config) - [ ] Vercel Postgres (create in dashboard)
- [ ] Vercel KV (key-value store)
- [ ] Vercel Blob (file storage)
Deployment
- [ ] GitHub repository connected
- [ ] Build successful
- [ ] Environment variables added
- [ ] Custom domain configured (optional)
- [ ] SSL certificate active
Route Rules (Optional)
routeRules: {
'/': { prerender: true },
'/blog/**': { swr: 3600 }, // Cache 1 hour
'/docs/**': { isr: 3600 }, // ISR
'/_studio/**': { ssr: true }
}Verification
- [ ] Site loads correctly
- [ ] Content queries work
- [ ] Studio accessible (if configured)
- [ ] Preview deployments work
---
Netlify Checklist
Configuration
- [ ] Build command:
npm run build - [ ] Publish directory:
.output/public - [ ] Functions directory:
.output/server
Environment Variables
- [ ] Add Studio credentials (if using)
- [ ] Add custom env vars
Deployment
- [ ] GitHub connected
- [ ] Build settings configured
- [ ] Deploy successful
---
General Checklist (All Platforms)
Pre-Deployment
- [ ] All dependencies installed
- [ ]
content.config.tsconfigured - [ ] Collections defined
- [ ] Content files created
- [ ] Local build successful:
npm run build - [ ] Dev server works:
npm run dev
Post-Deployment
- [ ] Site accessible
- [ ] Content renders correctly
- [ ] Navigation works
- [ ] Search works (if implemented)
- [ ] Studio works (if configured)
- [ ] No 404 errors
- [ ] No console errors
- [ ] Performance acceptable
SEO & Meta
- [ ] Meta tags configured
- [ ] OG images working
- [ ] Sitemap generated
- [ ] Robots.txt configured
Monitoring
- [ ] Error tracking setup
- [ ] Analytics configured
- [ ] Performance monitoring
---
Hybrid Rendering Configuration
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
prerender: {
crawlLinks: true,
routes: ['/']
}
},
routeRules: {
// Static at build time
'/': { prerender: true },
'/about': { prerender: true },
// ISR (Incremental Static Regeneration)
'/blog/**': { isr: 3600 }, // Regenerate every hour
// SWR (Stale While Revalidate)
'/docs/**': { swr: 3600 }, // Cache 1 hour
// Always SSR
'/_studio/**': { ssr: true },
'/api/**': { cors: true }
}
})---
Troubleshooting
Build Fails
1. Check build logs 2. Verify all dependencies installed 3. Test locally: npm run build 4. Check Node.js version compatibility
Content Not Loading
1. Verify database configured (D1 or SQLite) 2. Check bindings (Cloudflare) 3. Test queries in development 4. Check console for errors
Studio Not Working
1. Verify SSR enabled 2. Check OAuth credentials 3. Verify callback URLs 4. Check environment variables 5. Test in development first
Performance Issues
1. Enable route rules (caching) 2. Prerender static pages 3. Implement pagination 4. Optimize images 5. Use CDN
Nuxt Content v3 - Complete Error Catalog
This document contains all 18 documented issues and their solutions for Nuxt Content v3.
Last Updated: 2025-01-10 Nuxt Content Version: 3.0.0 Source: Production deployments, GitHub issues, Nuxt Content documentation
---
Issue #1: Collection Not Found Error
Error: Collection 'xyz' not found
Source: https://github.com/nuxt/content/issues
Why It Happens: Collection not defined in content.config.ts or dev server not restarted
Prevention: Always define collections in content.config.ts and restart dev server after changes
Solution:
// content.config.ts
export default defineContentConfig({
collections: {
xyz: defineCollection({
type: 'page',
source: 'xyz/**/*.md'
})
}
})Then restart: rm -rf .nuxt && bun dev
---
Issue #2: Schema Validation Failure with Dates
Error: Validation error: Expected date, received string
Source: https://github.com/nuxt/content/discussions
Why It Happens: Incorrect date format in frontmatter
Prevention: Use ISO 8601 format: 2024-01-15 or 2024-01-15T10:30:00Z
Solution:
---
title: My Post
date: 2024-01-15 # ✅ ISO 8601 format
# NOT: "January 15, 2024" # ❌ Invalid
------
Issue #3: Database Locked Error
Error: SQLITE_BUSY: database is locked
Source: https://github.com/nuxt/content/issues
Why It Happens: Multiple processes accessing database simultaneously
Prevention: Delete .nuxt directory and restart dev server
Solution:
rm -rf .nuxt
bun devNote: This can happen when running multiple dev servers or after force-killing a process.
---
Issue #4: D1 Binding Not Found on Cloudflare
Error: DB is not defined
Source: Cloudflare D1 documentation
Why It Happens: D1 binding name is not exactly DB
Prevention: Use binding name DB (case-sensitive) in Cloudflare dashboard
Solution: 1. Go to Cloudflare Dashboard → Workers & Pages → Your project → Settings → Variables 2. D1 Database Bindings → Variable name must be exactly DB 3. Redeploy after changing binding name
---
Issue #5: MDC Components Not Rendering
Error: Components show as raw text instead of rendering
Source: MDC documentation
Why It Happens: Component not in components/content/ or incorrect syntax
Prevention: Place components in components/content/ with exact name matching
Solution:
<!-- components/content/MyAlert.vue -->
<template>
<div class="alert">
<slot />
</div>
</template><!-- content/page.md -->
::my-alert
This is an alert message
::Important: Component name in markdown uses kebab-case, Vue file uses PascalCase.
---
Issue #6: Navigation Not Updating
Error: New content doesn't appear in navigation
Source: https://github.com/nuxt/content/issues
Why It Happens: .nuxt cache not cleared
Prevention: Delete .nuxt directory when adding new content files
Solution:
rm -rf .nuxt
bun dev---
Issue #7: Path Not Resolved Correctly
Error: Content returns null for valid path
Source: https://github.com/nuxt/content/discussions
Why It Happens: Path doesn't match file structure or prefix misconfigured
Prevention: Use .all() to debug paths
Solution:
// Debug all paths in collection
const items = await queryCollection('blog').all()
console.log(items.map(i => i.path))
// Then query with correct path
const post = await queryCollection('blog').path('/blog/hello').first()---
Issue #8: Studio OAuth Callback Fails
Error: redirect_uri_mismatch
Source: GitHub OAuth documentation
Why It Happens: OAuth callback URL doesn't match exactly
Prevention: Ensure callback URL in GitHub OAuth app matches production domain exactly (including https://)
Solution: 1. Go to GitHub → Settings → Developer settings → OAuth Apps 2. Set Authorization callback URL to: https://yourdomain.com/api/__studio/oauth/callback 3. Must include protocol (https://) 4. Must match production domain exactly
---
Issue #9: Studio Changes Not Saving
Error: Changes in Studio don't commit to GitHub
Source: Nuxt Studio documentation
Why It Happens: GitHub token lacks write permissions or repository config incorrect
Prevention: Verify repository configuration and GitHub token permissions
Solution:
// nuxt.config.ts
export default defineNuxtConfig({
studio: {
enabled: true,
gitInfo: {
name: 'your-name',
email: 'your-email@example.com'
}
}
})Ensure GitHub token has repo scope (full repository access).
---
Issue #10: Better-SQLite3 Module Not Found
Error: Cannot find module 'better-sqlite3'
Source: Node.js error logs
Why It Happens: Database connector not installed
Prevention: Install database connector
Solution:
bun add better-sqlite3
# or
npm install better-sqlite3Note: This is required for Nuxt Content v3 SQL storage.
---
Issue #11: JSON File Validation Error
Error: Unexpected token [ in JSON
Source: Nuxt Content validation
Why It Happens: JSON file contains array instead of object
Prevention: Each JSON file must contain single object, not array
Solution:
// ❌ Wrong: array at root
[
{ "name": "Item 1" },
{ "name": "Item 2" }
]
// ✅ Correct: object at root
{
"name": "Item 1",
"description": "This is an item"
}For arrays: Use separate JSON files or nest array inside object.
---
Issue #12: Numeric Prefix Sorting Wrong
Error: Files sort as 1, 10, 2, 3 instead of 1, 2, 3, 10
Source: File system alphabetical sorting
Why It Happens: Single-digit numbers sort alphabetically
Prevention: Use zero-padded prefixes
Solution:
# ❌ Wrong
1-introduction.md
2-setup.md
10-advanced.md
# ✅ Correct
01-introduction.md
02-setup.md
10-advanced.md---
Issue #13: Server Query Type Error
Error: TypeScript error in server routes
Source: TypeScript compilation
Why It Happens: Missing server/tsconfig.json
Prevention: Create server/tsconfig.json extending ../.nuxt/tsconfig.server.json
Solution:
// server/tsconfig.json
{
"extends": "../.nuxt/tsconfig.server.json"
}---
Issue #14: Vercel Build Fails
Error: Build timeout or out of memory
Source: Vercel build logs
Why It Happens: Large content causing memory issues
Prevention: Use route rules with prerendering and pagination
Solution:
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/blog/**': { prerender: true },
'/api/**': { cors: true }
},
nitro: {
prerender: {
crawlLinks: true,
routes: ['/'],
ignore: ['/admin/**']
}
}
})Also consider: Implement pagination for large collections.
---
Issue #15: Excerpt Not Working
Error: Excerpt returns full content
Source: Nuxt Content documentation
Why It Happens: Missing <!--more--> divider in Markdown
Prevention: Add <!--more--> in content to separate excerpt from full content
Solution:
---
title: My Post
---
This is the excerpt content. It appears in listings.
<!--more-->
This is the full content. It only appears on the full post page.// Query with excerpt
const posts = await queryCollection('blog')
.only(['title', 'excerpt', 'path'])
.all()---
Issue #16: Code Highlighting Not Working
Error: Code blocks show plain text without syntax highlighting
Source: Shiki configuration
Why It Happens: Language not specified or Shiki not configured
Prevention: Specify language in code fence
Solution:
<!-- ❌ Wrong: no language specified -->
\`\`\`
const hello = 'world'
\`\`\`
<!-- ✅ Correct: language specified -->
\`\`\`typescript
const hello: string = 'world'
\`\`\`Configure Shiki (optional):
// nuxt.config.ts
export default defineNuxtConfig({
content: {
highlight: {
theme: 'github-dark',
langs: ['typescript', 'javascript', 'vue', 'css']
}
}
})---
Issue #17: Remote Repository Auth Fails
Error: Authentication failed when using remote repository
Source: Git authentication
Why It Happens: Invalid token or missing credentials
Prevention: Store GitHub token in environment variable and reference in source.authToken
Solution:
# .env
GITHUB_TOKEN=your_github_token_here// content.config.ts
export default defineContentConfig({
collections: {
docs: defineCollection({
type: 'page',
source: {
repository: 'owner/repo',
prefix: 'docs',
authToken: process.env.GITHUB_TOKEN
}
})
}
})Token Permissions: Ensure token has repo scope for private repositories.
---
Issue #18: Prose Components Not Applied
Error: Custom Prose components don't override defaults
Source: MDC configuration
Why It Happens: Component name doesn't match exactly or not in components/content/
Prevention: Use exact names (ProseA, ProseH1, etc.) in components/content/ directory
Solution:
<!-- components/content/ProseA.vue -->
<template>
<a :href="href" class="custom-link" target="_blank">
<slot />
</a>
</template>
<script setup>
defineProps<{ href: string }>()
</script>Prose Component Names:
ProseA- LinksProseH1,ProseH2, etc. - HeadingsProseP- ParagraphsProseCode- Inline codeProseCodeInline- Code spansProseImg- ImagesProseUl,ProseOl- Lists
---
Summary
Total Issues Documented: 18 Categories:
- Configuration: 7 issues (#1, #2, #4, #10, #13, #17, #18)
- Content Management: 6 issues (#6, #7, #11, #12, #15, #16)
- Development: 3 issues (#3, #5, #14)
- Studio Integration: 2 issues (#8, #9)
Prevention: Always follow the Quick Start guide and use proper TypeScript types for content collections.
MDC Syntax Quick Reference
Basic Component Syntax
::component-name
Default slot content
::Example:
::alert
This is an alert!
::---
Named Slots
::hero
Main content
#description
Description content
#actions
Action buttons
::---
Component Props
Inline Props:
::alert{type="warning" icon="i-lucide-alert"}
Warning message
::YAML Props:
::card
---
title: Card Title
description: Card description
icon: IconNuxt
link: /docs
---
::---
Inline Components
:badge[New]{color="green"}
:icon{name="i-lucide-star"}---
Attributes
# Heading {#custom-id}
Paragraph {.text-lg .font-bold}
[Link](/url){target="_blank"}
Combined {#id .class style="color: blue;"}---
Data Binding
---
title: My Post
author: John Doe
count: 42
---
# {{ $doc.title }}
By {{ $doc.author }}
Count: {{ $doc.count }}---
Common Components
Alert
::alert{type="info"}
Info message
::
::alert{type="warning"}
Warning message
::
::alert{type="error"}
Error message
::Callout
::callout
Important information
::Code Group
::code-groupexport default defineNuxtConfig({})
npm run dev
::Tabs
::tabs
#tab1
Content for tab 1
#tab2
Content for tab 2
::---
Prose Components Override
Create in components/content/:
ProseA.vue- LinksProseCode.vue- Inline codeProsePre.vue- Code blocksProseH1.vuethroughProseH6.vue- HeadingsProseP.vue- ParagraphsProseImg.vue- ImagesProseUl.vue,ProseOl.vue,ProseLi.vue- Lists
Example (components/content/ProseA.vue):
<template>
<a :href="href" class="custom-link">
<slot />
</a>
</template>
<script setup>
defineProps({ href: String })
</script>---
Slot Unwrapping
<!-- components/content/Callout.vue -->
<template>
<div class="callout">
<slot mdc-unwrap="p" />
</div>
</template>Removes wrapping <p> tags from slot content.
Query Operators Quick Reference
SQL Operators
Equality & Comparison
// Equal to
.where('published', '=', true)
// Not equal
.where('status', '<>', 'draft')
// Greater than / Less than
.where('date', '>', '2024-01-01')
.where('price', '<', 1000)
// Greater/Less than or equal
.where('views', '>=', 100)
.where('score', '<=', 50)List Membership
// IN operator
.where('category', 'IN', ['news', 'tutorial'])
// NOT IN
.where('status', 'NOT IN', ['draft', 'archived'])Range
// BETWEEN
.where('date', 'BETWEEN', ['2024-01-01', '2024-12-31'])
// NOT BETWEEN
.where('price', 'NOT BETWEEN', [100, 500])Null Checks
// IS NULL
.where('deletedAt', 'IS NULL')
// IS NOT NULL
.where('publishedAt', 'IS NOT NULL')Pattern Matching
// LIKE (with % wildcard)
.where('title', 'LIKE', '%Nuxt%')
.where('email', 'LIKE', '%@gmail.com')
// NOT LIKE
.where('title', 'NOT LIKE', '%draft%')---
Combining Conditions
AND Conditions (Default)
queryCollection('blog')
.where('published', '=', true)
.where('category', '=', 'tutorial')
.where('date', '>', '2024-01-01')
.all()Grouped AND
queryCollection('blog')
.andWhere(q =>
q.where('date', '>', '2024-01-01')
.where('category', '=', 'news')
)
.all()OR Conditions
queryCollection('blog')
.orWhere(q =>
q.where('featured', '=', true)
.where('priority', '>', 5)
)
.all()Complex Conditions
queryCollection('blog')
.where('published', '=', true)
.andWhere(q =>
q.where('category', '=', 'news')
.orWhere(sub =>
sub.where('featured', '=', true)
)
)
.all()---
Ordering
// Single field
.order('date', 'DESC')
.order('title', 'ASC')
// Multiple fields
.order('category', 'ASC')
.order('date', 'DESC')---
Pagination
// Limit
.limit(10)
// Skip and limit
.skip(5)
.limit(10)
// Page-based
const page = 2
const perPage = 10
.skip((page - 1) * perPage)
.limit(perPage)---
Field Selection
// Select specific fields
.select('path', 'title', 'description', 'date')
// Benefits: Smaller payload, faster queries---
Counting
const count = await queryCollection('blog')
.where('published', '=', true)
.count()---
Server-Side Queries
// Pass event as first argument
export default eventHandler(async (event) => {
const posts = await queryCollection(event, 'blog').all()
return posts
})---
Complete Query Example
// Get latest 10 published tutorials from 2024,
// ordered by date, with specific fields only
const tutorials = await queryCollection('blog')
.where('published', '=', true)
.where('category', '=', 'tutorial')
.where('date', '>=', '2024-01-01')
.select('path', 'title', 'description', 'date', 'author')
.order('date', 'DESC')
.limit(10)
.all()Nuxt Studio Setup Checklist
Prerequisites
- [ ] Nuxt Content v3 installed
- [ ] GitHub account
- [ ] Production domain (for OAuth)
---
1. Install Nuxt Studio
# Bun
bun add nuxt-studio@alpha
# npm
npm install nuxt-studio@alpha
# pnpm
pnpm add nuxt-studio@alpha---
2. Configure nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content', 'nuxt-studio'],
studio: {
route: '/_studio', // Access route
repository: {
provider: 'github',
owner: 'your-username',
repo: 'your-repo',
branch: 'main'
}
}
})---
3. Create GitHub OAuth App
Production OAuth App
1. Visit: https://github.com/settings/developers 2. Click New OAuth App 3. Fill in:
- Application name: Your App Name
- Homepage URL:
https://yourdomain.com - Authorization callback URL:
https://yourdomain.com
4. Click Register application 5. Copy the Client ID 6. Click Generate a new client secret 7. Copy the Client Secret (shown only once!)
Staging OAuth App (Optional)
Create a separate OAuth app for staging:
- Application name: Your App Name (Staging)
- Homepage URL:
https://staging.yourdomain.com - Callback URL:
https://staging.yourdomain.com
---
4. Set Environment Variables
Local Development
# .env
STUDIO_GITHUB_CLIENT_ID=your_client_id_here
STUDIO_GITHUB_CLIENT_SECRET=your_client_secret_hereProduction (Cloudflare)
Add in Cloudflare Dashboard:
- Project → Settings → Environment Variables
- Add
STUDIO_GITHUB_CLIENT_ID - Add
STUDIO_GITHUB_CLIENT_SECRET
Production (Vercel)
Add in Vercel Dashboard:
- Project → Settings → Environment Variables
- Production scope
- Add both variables
---
5. Deployment Configuration
Required
- ✅ SSR enabled (not static generation)
- ✅ Build command:
nuxt build - ✅ Server runtime support
Hybrid Rendering (Optional)
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
'/blog/**': { prerender: true },
'/_studio/**': { ssr: true } // Studio requires SSR
}
})---
6. Staging/Preview Setup
Environment-Based Branch
// nuxt.config.ts
studio: {
repository: {
provider: 'github',
owner: 'your-username',
repo: 'your-repo',
branch: process.env.STUDIO_GITHUB_BRANCH_NAME || 'main'
}
}Environment Variables
Staging:
STUDIO_GITHUB_BRANCH_NAME=staging
STUDIO_GITHUB_CLIENT_ID=staging_client_id
STUDIO_GITHUB_CLIENT_SECRET=staging_client_secretProduction:
STUDIO_GITHUB_BRANCH_NAME=main
STUDIO_GITHUB_CLIENT_ID=production_client_id
STUDIO_GITHUB_CLIENT_SECRET=production_client_secret---
7. Access Studio
Web Interface
https://yourdomain.com/_studioKeyboard Shortcut
Press Ctrl + . anywhere on the site
Login Flow
1. Click "Login with GitHub" 2. Authorize OAuth app 3. Start editing!
---
8. Development Mode (Optional)
Enable local file writing:
studio: {
development: {
sync: true // Writes to local content/
},
repository: {
// ... config
}
}---
Troubleshooting
OAuth Fails
- ✅ Callback URL matches exactly (including
https://) - ✅ Environment variables set correctly
- ✅ OAuth app not suspended
Studio Not Accessible
- ✅ SSR is enabled
- ✅ Route is correct (
/_studio) - ✅ Module in
modulesarray
Changes Not Saving
- ✅ GitHub token has write permissions
- ✅ Repository configuration correct
- ✅ Check browser console for errors
---
Complete Checklist
- [ ] Nuxt Studio installed
- [ ] Module added to
nuxt.config.ts - [ ] Repository configured
- [ ] GitHub OAuth app created (production)
- [ ] Environment variables set
- [ ] SSR enabled
- [ ] Deployed to hosting platform
- [ ] Studio accessible at
/_studio - [ ] Login works
- [ ] Changes commit to GitHub
---
Advanced: Monorepo Support
studio: {
repository: {
provider: 'github',
owner: 'your-username',
repo: 'monorepo-name',
branch: 'main',
rootDir: 'apps/website' // Path to Nuxt app
}
}#!/bin/bash
# Cloudflare Pages Deployment Helper
# Assists with deploying Nuxt Content to Cloudflare Pages with D1
set -e
echo "☁️ Cloudflare Pages Deployment"
echo "==============================="
echo ""
# Check if we're in a Nuxt project
if [ ! -f "nuxt.config.ts" ]; then
echo "❌ Error: No nuxt.config.ts found."
exit 1
fi
# Check if wrangler is installed
if ! command -v wrangler &> /dev/null; then
echo "📦 Wrangler CLI not found. Installing..."
npm install -g wrangler
fi
echo "✅ Wrangler CLI ready"
echo ""
# Login to Cloudflare
echo "🔐 Logging into Cloudflare..."
wrangler login
echo ""
echo "📝 D1 Database Setup"
echo ""
read -p "D1 Database name (default: nuxt-content-db): " DB_NAME
DB_NAME=${DB_NAME:-nuxt-content-db}
# Check if database exists
echo "Checking if database exists..."
if wrangler d1 list | grep -q "$DB_NAME"; then
echo "✅ Database '$DB_NAME' already exists"
else
echo "Creating D1 database..."
wrangler d1 create "$DB_NAME"
echo "✅ Database created"
fi
echo ""
echo "⚙️ Configuring nuxt.config.ts for Cloudflare..."
# Check if preset is already set
if ! grep -q "preset.*cloudflare" nuxt.config.ts; then
# Create backup
cp nuxt.config.ts nuxt.config.ts.backup
# Add or update nitro preset
if grep -q "nitro:" nuxt.config.ts; then
echo "⚠️ Please manually add preset: 'cloudflare_pages' to nitro config"
else
cat >> nuxt.config.ts << 'EOF'
nitro: {
preset: 'cloudflare_pages'
}
EOF
echo "✅ Added Cloudflare Pages preset to nuxt.config.ts"
fi
fi
echo ""
echo "🏗️ Building for Cloudflare Pages..."
npm run build -- --preset=cloudflare_pages
if [ $? -eq 0 ]; then
echo "✅ Build successful!"
else
echo "❌ Build failed. Please check errors above."
exit 1
fi
echo ""
echo "🎉 Ready for deployment!"
echo ""
echo "📋 Next Steps:"
echo ""
echo "1. Go to Cloudflare Dashboard → Workers & Pages"
echo "2. Create new Pages project or select existing"
echo "3. Connect your GitHub repository"
echo "4. Build settings:"
echo " - Build command: npm run build"
echo " - Build output directory: .output/public"
echo "5. Add D1 binding:"
echo " - Settings → Functions → D1 Database Bindings"
echo " - Variable name: DB (must be exactly 'DB')"
echo " - D1 Database: $DB_NAME"
echo "6. Environment variables (if using Studio):"
echo " - STUDIO_GITHUB_CLIENT_ID"
echo " - STUDIO_GITHUB_CLIENT_SECRET"
echo ""
echo "Or deploy directly with Wrangler:"
echo " wrangler pages deploy .output/public"
echo ""
echo "📚 Resources:"
echo " - Cloudflare Pages: https://pages.cloudflare.com/"
echo " - D1 Docs: https://developers.cloudflare.com/d1/"
echo " - Deployment Guide: skills/nuxt-content/references/deployment-checklists.md"
echo ""
#!/bin/bash
# Vercel Deployment Helper
# Assists with deploying Nuxt Content to Vercel
set -e
echo "▲ Vercel Deployment"
echo "==================="
echo ""
# Check if we're in a Nuxt project
if [ ! -f "nuxt.config.ts" ]; then
echo "❌ Error: No nuxt.config.ts found."
exit 1
fi
# Check if vercel CLI is installed
if ! command -v vercel &> /dev/null; then
echo "📦 Vercel CLI not found. Installing..."
npm install -g vercel
fi
echo "✅ Vercel CLI ready"
echo ""
# Login to Vercel
echo "🔐 Logging into Vercel..."
vercel login
echo ""
echo "🏗️ Building project..."
npm run build
if [ $? -eq 0 ]; then
echo "✅ Build successful!"
else
echo "❌ Build failed. Please check errors above."
exit 1
fi
echo ""
echo "Would you like to deploy now?"
select yn in "Yes" "No"; do
case $yn in
Yes )
echo ""
echo "🚀 Deploying to Vercel..."
vercel --prod
break;;
No )
echo ""
echo "⏭️ Skipping deployment"
break;;
esac
done
echo ""
echo "📋 Vercel Configuration Checklist:"
echo ""
echo "✓ Build Command: npm run build"
echo "✓ Output Directory: .output"
echo "✓ Framework Preset: Nuxt"
echo ""
echo "If using Nuxt Studio, add environment variables:"
echo " - STUDIO_GITHUB_CLIENT_ID"
echo " - STUDIO_GITHUB_CLIENT_SECRET"
echo ""
echo "Database Options:"
echo " 1. Default: SQLite at /tmp (zero config)"
echo " 2. Vercel Postgres (create in dashboard)"
echo " 3. Vercel KV (key-value store)"
echo " 4. Vercel Blob (file storage)"
echo ""
echo "To add environment variables:"
echo " vercel env pull # Pull from Vercel"
echo " # Or add in Vercel Dashboard → Settings → Environment Variables"
echo ""
echo "📚 Resources:"
echo " - Vercel Docs: https://vercel.com/docs"
echo " - Nuxt on Vercel: https://nuxt.com/deploy/vercel"
echo " - Deployment Guide: skills/nuxt-content/references/deployment-checklists.md"
echo ""
#!/bin/bash
# Nuxt Content Setup Script
# Initializes a new Nuxt Content v3 project
set -e
echo "🚀 Nuxt Content v3 Setup"
echo "========================"
echo ""
# Check if we're in a Nuxt project
if [ ! -f "nuxt.config.ts" ] && [ ! -f "nuxt.config.js" ]; then
echo "❌ Error: No nuxt.config file found. Are you in a Nuxt project?"
echo " Run 'npx nuxi init my-project' first."
exit 1
fi
# Detect package manager
if [ -f "bun.lockb" ]; then
PKG_MGR="bun"
INSTALL_CMD="bun add"
elif [ -f "pnpm-lock.yaml" ]; then
PKG_MGR="pnpm"
INSTALL_CMD="pnpm add"
elif [ -f "yarn.lock" ]; then
PKG_MGR="yarn"
INSTALL_CMD="yarn add"
else
PKG_MGR="npm"
INSTALL_CMD="npm install"
fi
echo "📦 Detected package manager: $PKG_MGR"
echo ""
# Install dependencies
echo "📥 Installing @nuxt/content and better-sqlite3..."
$INSTALL_CMD @nuxt/content better-sqlite3
# Install Zod v4 for schema validation
echo "📥 Installing zod v4 for schema validation..."
$INSTALL_CMD -D zod@^4.1.12
echo ""
echo "✅ Dependencies installed!"
echo ""
# Add module to nuxt.config.ts if not already present
if ! grep -q "@nuxt/content" nuxt.config.ts 2>/dev/null; then
echo "📝 Adding @nuxt/content to nuxt.config.ts..."
# Create backup
cp nuxt.config.ts nuxt.config.ts.backup
# Add module (simple approach - user may need to adjust)
if grep -q "modules: \[" nuxt.config.ts; then
sed -i.tmp "s/modules: \[/modules: ['@nuxt\/content', /" nuxt.config.ts
rm -f nuxt.config.ts.tmp
else
echo "⚠️ Please manually add '@nuxt/content' to your modules array"
fi
fi
# Create content.config.ts
if [ ! -f "content.config.ts" ]; then
echo "📝 Creating content.config.ts..."
cat > content.config.ts << 'EOF'
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
content: defineCollection({
type: 'page',
source: '**/*.md',
schema: z.object({
title: z.string().optional(),
description: z.string().optional(),
date: z.date().optional(),
tags: z.array(z.string()).default([])
})
})
}
})
EOF
echo "✅ Created content.config.ts"
else
echo "⚠️ content.config.ts already exists, skipping..."
fi
# Create content directory
if [ ! -d "content" ]; then
echo "📁 Creating content/ directory..."
mkdir -p content
# Create example content file
cat > content/index.md << 'EOF'
---
title: Welcome to Nuxt Content
description: Your content-driven site powered by Nuxt Content v3
date: 2024-01-01
tags: ['nuxt', 'content']
---
# Welcome to Nuxt Content v3
This is your first content page! Edit this file in `content/index.md`.
## Features
- 📝 **Markdown Support** - Write content in Markdown
- 🎨 **MDC Syntax** - Use Vue components in Markdown
- 🔍 **Type-Safe Queries** - Query content with TypeScript
- 🚀 **Git-Based** - Content stored in your repository
## Next Steps
1. Add more content files to `content/`
2. Create collections in `content.config.ts`
3. Query content with `queryCollection()`
4. Render with `<ContentRenderer>`
Happy writing! 🎉
EOF
echo "✅ Created content/ directory with example file"
else
echo "⚠️ content/ directory already exists, skipping..."
fi
# Create example page to render content
if [ ! -d "pages" ]; then
mkdir -p pages
fi
if [ ! -f "pages/[...slug].vue" ]; then
echo "📝 Creating pages/[...slug].vue..."
cat > 'pages/[...slug].vue' << 'EOF'
<script setup>
const route = useRoute()
const { data: page } = await useAsyncData(route.path, () =>
queryCollection('content').path(route.path).first()
)
if (!page.value) {
throw createError({ statusCode: 404, message: 'Page not found' })
}
useHead({
title: page.value.title,
meta: [
{ name: 'description', content: page.value.description }
]
})
</script>
<template>
<div class="container mx-auto px-4 py-8">
<article class="prose dark:prose-invert max-w-none">
<h1>{{ page.title }}</h1>
<ContentRenderer :value="page" />
</article>
</div>
</template>
EOF
echo "✅ Created pages/[...slug].vue"
else
echo "⚠️ pages/[...slug].vue already exists, skipping..."
fi
echo ""
echo "🎉 Nuxt Content v3 setup complete!"
echo ""
echo "Next steps:"
echo " 1. Run your dev server: $PKG_MGR run dev"
echo " 2. Visit http://localhost:3000"
echo " 3. Edit content/index.md"
echo " 4. Check SKILL.md for complete documentation"
echo ""
echo "📚 Resources:"
echo " - Docs: https://content.nuxt.com/"
echo " - Skill: skills/nuxt-content/SKILL.md"
echo ""
#!/bin/bash
# Nuxt Studio Setup Script
# Configures Nuxt Studio for production content editing
set -e
echo "🎨 Nuxt Studio Setup"
echo "===================="
echo ""
# Check if we're in a Nuxt project
if [ ! -f "nuxt.config.ts" ]; then
echo "❌ Error: No nuxt.config.ts found. Run setup-nuxt-content.sh first."
exit 1
fi
# Check if @nuxt/content is installed
if ! grep -q "@nuxt/content" package.json; then
echo "❌ Error: @nuxt/content not installed. Run setup-nuxt-content.sh first."
exit 1
fi
# Detect package manager
if [ -f "bun.lockb" ]; then
INSTALL_CMD="bun add"
elif [ -f "pnpm-lock.yaml" ]; then
INSTALL_CMD="pnpm add"
elif [ -f "yarn.lock" ]; then
INSTALL_CMD="yarn add"
else
INSTALL_CMD="npm install"
fi
# Install Nuxt Studio
echo "📥 Installing nuxt-studio@alpha..."
$INSTALL_CMD nuxt-studio@alpha
echo ""
echo "✅ nuxt-studio installed!"
echo ""
# Prompt for GitHub repository details
echo "📝 GitHub Repository Configuration"
echo ""
read -p "GitHub username/org: " GITHUB_OWNER
read -p "Repository name: " GITHUB_REPO
read -p "Branch (default: main): " GITHUB_BRANCH
GITHUB_BRANCH=${GITHUB_BRANCH:-main}
echo ""
echo "🔑 GitHub OAuth Setup Required"
echo ""
echo "To use Nuxt Studio, you need to create a GitHub OAuth app:"
echo ""
echo "1. Visit: https://github.com/settings/developers"
echo "2. Click 'New OAuth App'"
echo "3. Settings:"
echo " - Application name: Your app name"
echo " - Homepage URL: https://yourdomain.com"
echo " - Callback URL: https://yourdomain.com"
echo "4. Copy the Client ID and generate a Client Secret"
echo ""
read -p "Press ENTER when you've created the OAuth app..."
# Add to nuxt.config.ts
echo ""
echo "📝 Updating nuxt.config.ts..."
# Create backup
cp nuxt.config.ts nuxt.config.ts.backup
# Add studio configuration (user will need to adjust)
cat >> nuxt.config.ts << EOF
// Nuxt Studio Configuration
studio: {
route: '/_studio',
repository: {
provider: 'github',
owner: '$GITHUB_OWNER',
repo: '$GITHUB_REPO',
branch: '$GITHUB_BRANCH'
}
}
EOF
echo "✅ Updated nuxt.config.ts (please verify the configuration)"
echo ""
# Create .env.example
echo "📝 Creating .env.example..."
cat > .env.example << 'EOF'
# Nuxt Studio GitHub OAuth Configuration
STUDIO_GITHUB_CLIENT_ID=your_github_oauth_client_id
STUDIO_GITHUB_CLIENT_SECRET=your_github_oauth_client_secret
# Optional: Branch name for different environments
# STUDIO_GITHUB_BRANCH_NAME=main
EOF
# Create or update .env
if [ ! -f ".env" ]; then
echo "📝 Creating .env file..."
cp .env.example .env
echo "⚠️ Please edit .env and add your GitHub OAuth credentials"
else
echo "⚠️ .env file exists. Please add these variables:"
cat .env.example
fi
# Add .env to .gitignore if not already there
if [ -f ".gitignore" ]; then
if ! grep -q "^\.env$" .gitignore; then
echo ".env" >> .gitignore
echo "✅ Added .env to .gitignore"
fi
else
echo ".env" > .gitignore
echo "✅ Created .gitignore with .env"
fi
echo ""
echo "🎉 Nuxt Studio setup complete!"
echo ""
echo "⚠️ IMPORTANT: Next Steps"
echo ""
echo "1. Edit .env and add your GitHub OAuth credentials:"
echo " STUDIO_GITHUB_CLIENT_ID=your_client_id"
echo " STUDIO_GITHUB_CLIENT_SECRET=your_client_secret"
echo ""
echo "2. Verify nuxt.config.ts has 'nuxt-studio' in modules array"
echo ""
echo "3. For production deployment:"
echo " - Add environment variables to your hosting platform"
echo " - Ensure SSR is enabled (not static generation)"
echo " - Create separate OAuth app for staging if needed"
echo ""
echo "4. Access Studio at: http://localhost:3000/_studio (after running dev server)"
echo " Or use keyboard shortcut: Ctrl + ."
echo ""
echo "📚 Resources:"
echo " - Nuxt Studio Docs: https://github.com/nuxt-content/studio"
echo " - OAuth Setup Guide: skills/nuxt-content/references/studio-setup-guide.md"
echo ""
/**
* Nuxt Content v3 - Blog Collection Setup Template
*
* This template shows how to set up a complete blog with Nuxt Content v3,
* including collections, schemas, queries, and deployment.
*/
// ===== 1. INSTALL DEPENDENCIES =====
// bun add @nuxt/content better-sqlite3 zod
// ===== 2. NUXT CONFIG =====
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content'],
// Optional: Configure content
content: {
highlight: {
theme: 'github-dark',
langs: ['typescript', 'javascript', 'vue', 'css', 'bash']
}
}
})
// ===== 3. CONTENT CONFIG =====
// content.config.ts
import { defineContentConfig, defineCollection } from '@nuxt/content'
import { z } from 'zod'
export default defineContentConfig({
collections: {
// Blog posts collection
blog: defineCollection({
type: 'page',
source: 'blog/**/*.md',
schema: z.object({
title: z.string(),
description: z.string(),
date: z.date(),
author: z.string(),
tags: z.array(z.string()).default([]),
published: z.boolean().default(false),
coverImage: z.string().optional()
})
}),
// Authors data collection
authors: defineCollection({
type: 'data',
source: 'authors/*.yml',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string().url(),
social: z.object({
twitter: z.string().optional(),
github: z.string().optional()
}).optional()
})
})
}
})
// ===== 4. CREATE CONTENT FILES =====
// content/blog/hello-world.md
/*
---
title: Hello World
description: My first blog post
date: 2024-01-15
author: john-doe
tags: ['nuxt', 'content']
published: true
---
# Hello World
This is my first blog post using Nuxt Content v3!
<!--more-->
## Features
- Type-safe queries
- MDC components
- Full-text search
\`\`\`typescript
const posts = await queryCollection('blog').all()
\`\`\`
*/
// content/authors/john-doe.yml
/*
name: John Doe
bio: Full-stack developer passionate about Nuxt
avatar: https://example.com/avatar.jpg
social:
twitter: johndoe
github: johndoe
*/
// ===== 5. QUERY PAGES =====
// pages/blog/index.vue
/*
<script setup lang="ts">
// Query all published posts, sorted by date
const { data: posts } = await useAsyncData('blog-posts', () =>
queryCollection('blog')
.where('published', '=', true)
.sort('date', 'DESC')
.all()
)
</script>
<template>
<div>
<h1>Blog</h1>
<article v-for="post in posts" :key="post.path">
<NuxtLink :to="post.path">
<h2>{{ post.title }}</h2>
<p>{{ post.description }}</p>
<time>{{ new Date(post.date).toLocaleDateString() }}</time>
</NuxtLink>
</article>
</div>
</template>
*/
// pages/blog/[...slug].vue
/*
<script setup lang="ts">
const route = useRoute()
// Query single post by path
const { data: post } = await useAsyncData(route.path, () =>
queryCollection('blog').path(route.path).first()
)
// Handle 404
if (!post.value) {
throw createError({ statusCode: 404, message: 'Post not found' })
}
// Optional: Query author data
const { data: author } = await useAsyncData(`author-${post.value.author}`, () =>
queryCollection('authors').where('_id', '=', post.value.author).first()
)
</script>
<template>
<article v-if="post">
<header>
<h1>{{ post.title }}</h1>
<p>{{ post.description }}</p>
<div>
<time>{{ new Date(post.date).toLocaleDateString() }}</time>
<span v-if="author"> by {{ author.name }}</span>
</div>
<div v-if="post.tags.length">
<span v-for="tag in post.tags" :key="tag">#{{ tag }}</span>
</div>
</header>
<ContentRenderer :value="post" />
<footer v-if="author">
<img :src="author.avatar" :alt="author.name" />
<p>{{ author.bio }}</p>
</footer>
</article>
</template>
*/
// ===== 6. SEARCH PAGE =====
// pages/search.vue
/*
<script setup lang="ts">
import { ref } from 'vue'
const query = ref('')
const results = ref([])
async function search() {
if (!query.value) {
results.value = []
return
}
// Full-text search
results.value = await queryCollectionSearchSections('blog', query.value)
.where('published', '=', true)
.all()
}
</script>
<template>
<div>
<h1>Search Blog</h1>
<input v-model="query" @input="search" placeholder="Search posts..." />
<div v-if="results.length">
<article v-for="result in results" :key="result.id">
<NuxtLink :to="result.path">
<h2>{{ result.title }}</h2>
<p>{{ result.description }}</p>
</NuxtLink>
</article>
</div>
</div>
</template>
*/
// ===== 7. NAVIGATION =====
// pages/docs/index.vue
/*
<script setup lang="ts">
// Auto-generate navigation from content structure
const { data: navigation } = await useAsyncData('blog-navigation', () =>
queryCollectionNavigation('blog')
.where('published', '=', true)
.all()
)
</script>
<template>
<nav>
<ul>
<li v-for="item in navigation" :key="item.path">
<NuxtLink :to="item.path">{{ item.title }}</NuxtLink>
<ul v-if="item.children?.length">
<li v-for="child in item.children" :key="child.path">
<NuxtLink :to="child.path">{{ child.title }}</NuxtLink>
</li>
</ul>
</li>
</ul>
</nav>
</template>
*/
// ===== 8. DEPLOYMENT TO CLOUDFLARE =====
// wrangler.toml
/*
name = "my-nuxt-blog"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB" # Must be exactly "DB"
database_name = "nuxt-content"
database_id = "your-d1-database-id"
*/
// Deploy commands:
/*
# Create D1 database
npx wrangler d1 create nuxt-content
# Build for Cloudflare
bun run build
# Deploy
npx wrangler pages deploy dist
*/
// ===== 9. SERVER API ROUTES =====
// server/api/posts/[slug].get.ts
/*
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug')
const post = await queryCollection('blog')
.where('_id', '=', slug)
.where('published', '=', true)
.first()
if (!post) {
throw createError({ statusCode: 404, message: 'Post not found' })
}
return post
})
*/
// server/api/posts/index.get.ts
/*
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = Number(query.page || 1)
const limit = 10
const posts = await queryCollection('blog')
.where('published', '=', true)
.sort('date', 'DESC')
.limit(limit)
.offset((page - 1) * limit)
.all()
const total = await queryCollection('blog')
.where('published', '=', true)
.count()
return {
posts,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
}
})
*/
/**
* Best Practices:
*
* 1. Always define collections in content.config.ts
* 2. Use Zod schemas for type safety
* 3. Query only fields you need with .only()
* 4. Use pagination for large collections
* 5. Add <!--more--> in content for excerpts
* 6. Specify language in code blocks for syntax highlighting
* 7. Use ISO 8601 date format (YYYY-MM-DD)
* 8. Place MDC components in components/content/
* 9. Delete .nuxt directory when content structure changes
* 10. For Cloudflare: D1 binding MUST be named "DB"
*/