from a folder of .md files to a type safe data source
Nuxt Content turns a plain directory of markdown files into a full featured, searchable data source with automatic routing, a query builder and Vue components right inside the text. For anyone maintaining documentation, blogs or marketing pages without an external CMS, Nuxt Content is the missing link between static text and a dynamic Vue application.
Table of Contents
- 1. What Nuxt Content actually solves
- 2. Installation and directory structure
- 3. Frontmatter and metadata in markdown
- 4. The query builder: loading and filtering content
- 5. MDC syntax: Vue components inside markdown
- 6. Automatic routing with catch all pages
- 7. Building full text search and navigation
- 8. Common pitfalls in practice
- 9. Nuxt Content compared to alternatives
- 10. Summary
- 11. FAQ
1. What Nuxt Content actually solves
Nuxt Content is an official Nuxt module that turns markdown, YAML, JSON and CSV files from the project directory into a queryable data layer. Instead of managing content in an external database or headless CMS, it lives as plain text files directly in the repository, versioned with Git, reviewable through pull requests and without extra infrastructure. For documentation sites, technical blogs and marketing landing pages this is often the more pragmatic choice compared to a full CMS with its own database and admin interface.
The core of Nuxt Content is a build time pipeline that reads every file in a configured directory, usually content/, parses it and loads it into a SQLite database that gets queried at runtime. That is what distinguishes Nuxt Content from a simple markdown renderer: you get a real query builder with filtering, sorting and full text search, not just a function that converts markdown into HTML. This combination of file based authoring and database style querying is exactly what makes Nuxt Content interesting for larger projects.
A practical example: a team maintains a product documentation set with fifty pages across several categories. Without Nuxt Content, every page would need to be linked manually, every navigation tree maintained by hand, and every structural change repeated across multiple files. With Nuxt Content, a single catalog query generates the complete navigation from the directory structure, including sorting by frontmatter fields and automatic breadcrumb generation.
2. Installation and directory structure
Installing Nuxt Content goes through the standard Nuxt module mechanism. After installation, the module only needs to be registered in nuxt.config.ts, and Nuxt automatically recognizes the content/ directory at the project root. Inside this directory, the folder structure directly mirrors the resulting URL structure, a pattern already familiar from file based routing in Nuxt Pages that Nuxt Content extends consistently.
An important naming convention controls the order of navigation entries: prefixes like 1.introduction.md or 2.installation.md control the sorting in generated navigation trees without those numbers appearing in the final URL. Nuxt Content automatically strips these prefixes when generating the route. This convention saves a separate sort field in every file and keeps the structure directly visible in the file system, which is a real benefit for team collaboration.
# Install Nuxt Content module
npx nuxi module add content
# Resulting directory structure for a documentation site
content/
├── 1.getting-started/
│ ├── 1.introduction.md
│ └── 2.installation.md
├── 2.guides/
│ ├── 1.routing.md
│ └── 2.data-fetching.md
└── index.md
# nuxt.config.ts registration
export default defineNuxtConfig({
modules: ['@nuxt/content'],
content: {
// Optional: configure markdown highlighter theme
highlight: {
theme: 'github-dark'
}
}
})
3. Frontmatter and metadata in markdown
Every markdown file in Nuxt Content can start with a YAML frontmatter block that stores structured metadata alongside the actual text. Title, description, publish date, author and any custom fields go there and become queryable columns inside the query builder. This is the key difference from plain markdown without frontmatter: you can filter, sort and display these fields on overview pages without parsing the body text at all.
For SEO relevant fields like description or a custom ogImage field, it pays off to enforce a fixed convention across all files so that later meta tag generation in Nuxt stays consistent. In larger projects it is worth adding a schema that validates frontmatter fields. Nuxt Content supports Zod based collection definitions for this, which precisely define which fields a file must have and what type they carry, catching typos in field names as a build error instead of a silent runtime bug.
---
title: "Nuxt Content Basics"
description: "How frontmatter and markdown work together"
date: 2026-07-14
author: "Mironsoft Editorial"
tags: ["nuxt", "content", "markdown"]
draft: false
---
## Introduction
The actual markdown text starts after the frontmatter block.
All fields above are queryable through the query builder,
without having to parse the body text.
4. The query builder: loading and filtering content
The query builder is the heart of Nuxt Content for every dynamic use case. Using the queryCollection composable, content can be filtered by path, frontmatter fields or full text, sorted and paginated, directly inside a Vue component or a server endpoint. These queries run at build time against the generated SQLite database and are considerably faster than parsing markdown files at runtime on every page request.
A typical pattern is a blog overview page that loads every article with a given tag, sorts them by date descending and shows the first ten entries. Without Nuxt Content, you would either need to wire up an external API or write your own indexing logic across all markdown files. The query builder in Nuxt Content handles exactly that task declaratively and with type safety, including automatically generated TypeScript types from the collection definition.
// composables/useBlogPosts.ts
export function useBlogPosts(tag: string) {
// Query the content collection with filtering, sorting and pagination
return useAsyncData(`blog-${tag}`, () =>
queryCollection('blog')
.where('tags', 'LIKE', `%${tag}%`)
.where('draft', '=', false)
.order('date', 'DESC')
.limit(10)
.all()
)
}
// Usage inside a Vue component <script setup>
const { data: posts } = await useBlogPosts('nuxt')
5. MDC syntax: Vue components inside markdown
A standout feature of Nuxt Content is the MDC syntax, short for Markdown Components. It lets you place real Vue components directly inside markdown text, complete with props and even nested slot content. An editor can embed an interactive callout box, a tab widget or a code preview right in the middle of the running text, without a developer having to write any HTML by hand.
Technically, MDC works through a special colon notation that Nuxt Content recognizes while parsing and resolves into a Vue component with the given props. Important in practice: any component intended for use through MDC must first be registered as a global component or placed inside the components/content/ directory so Nuxt Content can find it during rendering. This convention cleanly separates regular application components from content components exposed to editors.
## Deployment Note
::alert{type="warning"}
Before every production deployment, the environment variable
`NUXT_PUBLIC_API_BASE` must be set, otherwise the build fails.
::
Regular paragraphs keep working as usual, but
components with props can be embedded directly:
::code-preview{filename="app.vue" language="vue"}
#default
This is the actual preview content passed as a slot.
::
6. Automatic routing with catch all pages
Nuxt Content shows its full strength when combined with a single catch all route, usually placed as [...slug].vue in pages/. This one Vue component serves every markdown file in the content directory by reading the current path from the URL, loading the matching content entry through the query builder and rendering it via the ContentRenderer component. That eliminates the need to create a dedicated Vue page for every new markdown file.
This pattern scales remarkably well: whether ten or five hundred markdown files sit in the project, the routing logic stays identical, because Nuxt Content automatically derives the mapping between URL and file from the directory structure. For special cases, such as a homepage with an entirely custom layout, a dedicated page can coexist while all other content flows through the catch all route.
// pages/[...slug].vue
<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, statusMessage: 'Page not found' })
}
</script>
<template>
<article>
<h1>{{ page.title }}</h1>
<ContentRenderer :value="page" />
</article>
</template>
7. Building full text search and navigation
For full text search, Nuxt Content ships the queryCollectionSearchSections composable, which breaks the rendered text of every page into searchable sections, each with a heading, an anchor and a text excerpt. Combined with a client side search library like Fuse.js or Minisearch, this produces a complete search feature without running an external search index such as Algolia or Elasticsearch.
For navigation, the queryCollectionNavigation composable automatically returns a nested tree built from the directory structure, including titles pulled from frontmatter. This navigation can be rendered directly into a sidebar component and stays automatically in sync with the actual markdown files present. If a page is missing from the navigation, it is almost always because the file does not follow the expected naming scheme or a draft: true field is hiding it.
8. Common pitfalls in practice
The most common mistake when getting started with Nuxt Content is forgetting to restart the dev server after structural changes to the content directory. Unlike plain Vue files, the content schema is only rebuilt on server start, which means newly created collections sometimes only become visible after a restart. A second pitfall involves file name casing: on Linux production servers, inconsistency between a local macOS file system and the server leads to missing pages that worked perfectly fine locally.
A third, more subtle mistake is mixing raw HTML with MDC syntax inside the same file. Raw HTML is escaped by Nuxt Content by default, which confuses editors coming from a classic CMS. The correct fix is always to provide the desired interactive element as a registered MDC component rather than embedding HTML directly in markdown. Anyone referencing images should consistently resolve relative paths from the public/ directory, since absolute file system paths do not work in a production build.
9. Nuxt Content compared to alternatives
The decision to use Nuxt Content depends heavily on the project context. Compared to a classic headless CMS like Contentful or Strapi, extra infrastructure is eliminated, but editing by non technical editors without an admin interface becomes harder. The following table compares the main options for content driven Nuxt projects.
| Approach | Infrastructure | Editor friendliness | Best suited for |
|---|---|---|---|
| Nuxt Content | No external DB required | Medium, Git based | Docs, technical blogs, marketing pages |
| External headless CMS | Separate service required | High, admin interface | Large editorial teams |
| Plain markdown without query | Minimal | Low, no filtering | Very small static sites |
| Database plus custom API | High effort | High, flexible | Complex custom requirements |
In practice, Nuxt Content closes exactly the gap between plain static markdown and a full headless CMS. For projects where developers maintain the content, or where editors are comfortable with Git, the effort of a separate CMS is rarely justified. Nuxt Content brings versioning, review processes through pull requests and full control over rendering, without adding a contractual dependency on a SaaS provider.
Mironsoft
Vue.js and Nuxt development focused on content and performance
Need a Nuxt Content setup for your docs or blog?
We build Nuxt Content structures that scale: clean frontmatter schemas, MDC components for editors and automatic routing for hundreds of pages without manual upkeep.
Content architecture
Collection schemas, frontmatter validation and clean directory structure
MDC components
Editor friendly Vue components that embed directly into markdown
Search and navigation
Full text search and automatic navigation trees built from content data
10. Summary
Nuxt Content solves the recurring problem of managing content without an external database or CMS while still having a searchable, filterable data layer. Markdown files with frontmatter get loaded into a SQLite database at build time, and the query builder allows complex queries by field, tag and full text. The MDC syntax lets real Vue components live directly inside markdown text without editors ever having to write HTML.
A single catch all route serves any number of markdown files, and automatically generated navigation trees stay in sync with the directory structure without manual upkeep. For documentation sites, technical blogs and marketing landing pages, Nuxt Content is therefore often the more pragmatic alternative to a full headless CMS, especially when developers maintain the content themselves or Git based review workflows are desired.
Nuxt Content: Markdown Based Pages — Key Takeaways
Data layer
Markdown, YAML and JSON files are loaded into a SQLite database at build time and queried through the query builder.
MDC syntax
Vue components with props embed directly inside markdown text without editors ever writing HTML.
Routing
A single catch all route with ContentRenderer serves any number of markdown files without extra Vue pages.
Search and navigation
queryCollectionSearchSections and queryCollectionNavigation deliver full text search and navigation trees automatically.