Document-Driven Mode in Nuxt Content v2
Tags: nuxt, content, markdown
Published: 2023-01-20 14:31:14

---
title: Document-Driven Mode in Nuxt Content v2
published: true
description: Create a Markdown-based site with the new Document-Driven Mode in Nuxt Content v2, featuring content binding for pages and routes, layout support, and Markdown Component (MDC) Syntax.
tags: nuxt, content, markdown
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-01-09 08:34 +0000
---

The new [Document-Driven Mode](https://content.nuxtjs.org/guide/writing/document-driven) in Nuxt Content v2 automatically creates pages and routes from the files within your `content` directory. You can use Markdown components (MDC syntax), layouts, and global variables to enhance your Markdown files with style, interactivity, navigation, and more.

## What makes Document-Driven Mode different?

If you’ve used the v1 of the Content module, you’re already familiar with the file-based routing and query system. You also get the auto-imported component and rendering option benefits provided by Nuxt. 

Document-Driven Mode provides a different developer experience for letting your content “drive” the structure and generation of your site. There are some key features and differences when using Document-Driven Mode.

Note: Document-Driven Mode is an optional setting in Content v2.

### Direct binding between `content` directory and pages

This means that every file in your `content` directory automatically becomes a page. This means you no longer need a `pages` directory with `.vue` files for route creation. Creating an `index.md` file will bind to the default page for each directory.

```
content/
    index.md
    tutorials/
        index.md
	nuxt-content-module.md
	nuxt-ionic-module.md
    blog/
        index.md
	blog-post-1.md
	blog-post-2.md
```

### Layout binding in Markdown files

Because you are no longer creating `.vue` pages, leverage Layouts for ensuring specific types of content have a certain style or layout. For example, you may have tutorial and blog content types with different layouts. For example, create `tutorial.vue` and `blog.vue` files in your `layouts` directory, then reference the layout in the frontmatter of your Markdown file.

Example:

```markdown
---
title: Making connections at conferences
description: How to push past the discomfort and make new connections when attending tech conferences.
date: 2022-11-23
img: 
tags: [community, career]
layout: blog
---
```

### Markdown Components (MDC) Syntax

This feature isn’t specific to Document-driven Mode, but is an important new feature of Content v2 when building directly from Markdown files. MDC Syntax lets you insert Vue components directly into your Markdown files. You can even pass props within the Markdown file. 

MDC Syntax is useful when you want to use a Vue component in a single Markdown file. For example, this is an `index.md` file, which just contains the `<Hero>` component.

```markdown
:hero
```

### Global variables

Nuxt Content v2 provides a `useContent()` composable with `page`, `surround`, `navigation`, and `globals` variables that make it easy to use data from the current route before the page renders. This is helpful for rendering the page or creating navigation for `next` and `previous` in a blog context. The following example is from [the Nuxt Content docs](https://content.nuxtjs.org/api/composables/use-document-driven#creating-a-previousnext-page-component):

```jsx
<script setup lang="ts">
const { prev, next } = useContent()
</script>
<template>
  <div>
    <NuxtLink v-if="prev" :to="prev._path">{{ prev.title }}</NuxtLink>
    <NuxtLink v-if="next" :to="next._path">{{ next.title }}</NuxtLink>
  </div>
</template>
```

There are other aspects of Document-Driven Mode, such as default page slots for when document are empty or not found, and some configuration options. Check out the [documentation for more details and examples](https://content.nuxtjs.org/guide/writing/document-driven).

## Creating a Document-Driven Site

For this blog post, I’ll be using the updated version of my own site, [ceceliacreates.com](https://www.ceceliacreates.com). You can find the source code in [the repository here](https://github.com/ceceliacreates/nuxt-content-v2-doc-driven).

I first built this site with Nuxt Content v1. If you’re curious how the code compares between v1 and v2, you can check out the [previous version source code here](https://github.com/ceceliacreates/nuxt-garden).

I decided to start from scratch instead of upgrading the existing site, but kept some of the styling and layout (particularly the landing page). This is a fairly simple site focused on sharing content like events and blog posts, so Document-Driven Mode seemed like a good fit.

### Installation

To create a new Document-Driven Mode site from scratch, use the following command:

```bash
npx nuxi init doc-driven-app -t doc-driven
```

If you already have a Nuxt Content v2 site created, you can enable Document-Driven Mode in your `nuxt.config` file.

```typescript
export default defineNuxtConfig({
  modules: ['@nuxt/content'],
  content: {
    documentDriven: true
  }
})
```

### Directory Structure

A Document-Driven Mode site has three main directories to leverage: `content`, `layouts`, and `components`. There is also the `app.vue` root component. 

Here is a simplified example of my directory structure:

```
nuxt.config.ts
app.vue
content/
    index.md
    blog/
        index.md
	blog-post-1.md
	blog-post-2.md
    events/
        index.md
	event-1.md
	event-2.md
layouts/
    blog.vue
components/
    content/
        BlogHeader.vue
	BlogFooter.vue
	ContentList.vue
	Event.vue
	Hero.vue
```

I have an `index.md` as the root for each directory inside `content`. Without this, Nuxt Content will use [the built-in `<DocumentNotFound>` component](https://content.nuxtjs.org/guide/writing/document-driven#page-slots) automatically. 

I have a single `blog.vue` layout file. You can also create a `default.vue` layout, but my site didn’t need a fallback layout because my `app.vue` was sufficient.

>❗ You’ll notice that there is a `content` directory INSIDE `components`. This is required to use your components inside content Markdown files, so make sure to include it.

#### Setting up `app.vue`

My `app.vue` file is simple, with just the required `<NuxtPage />` component and a custom `<Navigation />` component inside my template. I’ve also added some global styles and am leveraging the Nuxt [`useHead()` component](https://nuxt.com/docs/api/composables/use-head) to set the meta tags and favicon for my site.

```jsx
<template>
    <Navigation />
    <NuxtPage />
</template>

<style>
// ...
</style>

<script setup lang="ts">
useHead({
  title:
    "Cecelia speaks and writes about web and mobile dev, testing, OSS, communities, and mentoring.",
  meta: [
    {
      name: "description",
      content:
        "Cecelia Martinez is a Developer Advocate who speaks and writes about web and mobile development, CI/CD, and testing. She is passionate about open source devtools, building inclusive communities, and ensuring everyone feels like they belong in tech. She is a volunteer with Out in Tech and Women Who Code Front End and a GitHub Star.",
    },
  ],
  link: [{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
});
</script>
```

### Adding Markdown Content

From here, I can start creating Markdown files within the content directory. However, I’d recommend starting by setting up types for the content. Because we’re using Markdown files, we need to extend the `MarkdownParsedContent` type built into Nuxt Content, as described in [the documentation here](https://content.nuxtjs.org/guide/displaying/typescript/#markdown-specific-types).

Here are the types for my `Blog` and `Event` content.

```tsx
import type { MarkdownParsedContent } from "@nuxt/content/dist/runtime/types";

export type Section = "blog" | "events";
export type EventCategory = "workshop" | "conference" | "webinar" | "stream";

export interface Blog extends MarkdownParsedContent {
  title: string;
  description: string;
  date: string;
  img?: string;
  tags: string[];
}

export interface Event extends MarkdownParsedContent {
  event: string;
  title: string;
  date: string;
  type: EventCategory;
  description: string;
  url: string;
  img?: string;
  embed?: string;
  video?: string;
  slides?: string;
  tags: string[];
}
```

I also recommend creating a snippet for generating the Markdown front matter to correspond to the required fields for your content. You can learn how to do this with VS Code in [the blog post here](https://dev.to/ceceliacreates/use-vs-code-snippets-to-generate-markdown-front-matter-fpc).

Here is an example of one of the Markdown files inside my `events` directory, which leverages front matter for the event information.

```markdown
---
event: All Things Open 2022
title: No-Code OSS Contributions
date: 2022-10-30
type: Conference
description: There are many ways to contribute to an open source project without writing a single line of code. For contributors, identify was you can impact a project without diving into the codebase. For maintainers, learn how to make your project no-code friendly to encourage contributions of all kinds and broaden your community of contributors.
url: https://2022.allthingsopen.org/sessions/no-code-oss-contributions/
img:
embed: _KD10cx9H90
video:
slides: https://slides.com/ceceliamartinez/no-code-oss-contributions
tags: ["oss", "community", "diversity"]
---
```

The blog post files are similar, except there is Markdown content following the front matter which contains the body of the post.

### Using Markdown Components (MDC) Syntax

I’m using Markdown files for everything, including my main index page and the index pages for my content categories. This means I’ll need to use Markdown Components (MDC) Syntax to introduce styling, data fetching, or other functionality not included in Markdown.

Let’s start with the index page. I’ve created a `<Hero>` component with the site title, my avatar, some social icons, and an intro. 

Below is a snippet of the component:

```jsx
<template>
  <main>
    <h1>Cecelia Creates</h1>
    <div id="avatar">
      <NuxtImg src="/avatar.png" sizes="sm:80vw md:50vw lg:300px" id="avatar" />
    </div>
    <div id="icons">
      <a
        v-for="link in socialLinks"
        :key="link.name"
        :href="link.url"
        target="blank"
      >
        <font-awesome-icon :icon="['fab', link.icon]" />
      </a>
    </div>
    <div id="about">
     // some links and text about me
    </div>
  </main>
</template>

<script setup>
const socialLinks = [
  {
    name: "Twitter",
    url: "https://twitter.com/ceceliacreates",
    icon: "twitter-square",
  },
  {
    name: "Youtube",
    url: "https://www.youtube.com/@ceceliacreates",
    icon: "youtube-square",
  },
  {
    name: "GitHub",
    url: "https://github.com/ceceliacreates",
    icon: "github-square",
  },
];
</script>

<style scoped>
// ...
</style>
```

Now, to use the component, I use the `:` MDC Syntax in my `index.md` file.

```markdown
:hero
```

That’s all there is. It’s not particularly exciting, but for more complex components, you can also use MDC Syntax to set props or attributes, and event nest components. Check out [the documentation here](https://content.nuxtjs.org/guide/writing/mdc) for more options.

I also created an `<Event>` component that renders the details for a given event on its own page. Here is the template for this component:

```jsx
<template>
  <main>
    <p v-if="!event">Event info missing</p>
    <div v-else>
      <h1>{{ event.title }}</h1>
      <h3>{{ event.event }}</h3>
      <p>{{ event.type + ", " + new Date(event.date).toDateString() }}</p>
      <a :href="event.url" v-show="event.url" target="blank"
        ><p>More Details</p></a
      >
      <div id="video-wrapper">
        <iframe
          id="video"
          v-show="event.embed"
          :src="`https://www.youtube.com/embed/${event.embed}`"
          frameborder="0"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowfullscreen
        ></iframe>
      </div>
      <a :href="event.video" v-show="event.video && !event.embed" target="blank"
        ><span>Video</span></a
      >
      <span v-show="event.video && !event.embed && event.slides"> || </span>
      <a :href="event.slides" v-show="event.slides" target="blank"
        ><span>Slides</span></a
      >
      <p>{{ event.description }}</p>
    </div>
    <p><NuxtLink to="/events"> ⬅️ Back to events </NuxtLink></p>
  </main>
</template>
```

To include this component for my events, I pass the `:event` MDC Syntax in the body of my event Markdown files, like this:

```markdown
---
event: All Things Open 2022
title: No-Code OSS Contributions
date: 2022-10-30
type: Conference
description: There are many ways to contribute to an open source project without writing a single line of code. For contributors, identify was you can impact a project without diving into the codebase. For maintainers, learn how to make your project no-code friendly to encourage contributions of all kinds and broaden your community of contributors.
url: https://2022.allthingsopen.org/sessions/no-code-oss-contributions/
img:
embed: _KD10cx9H90
video:
slides: https://slides.com/ceceliamartinez/no-code-oss-contributions
tags: ["oss", "community", "diversity"]
---
:event
```

Because I am using this component across all events, using a layout would probably be more efficient, and I will likely create an `event` layout in the future. In the meantime, my VS Code front matter snippet automatically inserts the `:event` component syntax.

### Fetching Content Data

For my category `index.md` files, I created a `<ContentList>` component that renders the list of either blog posts or events, with a link, title, date, image, and description.

```jsx
<template>
  <main>
    <div v-for="item in data" :key="item._path" class="item">
      <h3>
        ➡️ <NuxtLink :to="item._path">{{ item.title }}</NuxtLink>
      </h3>
      <p id="date">{{ new Date(item.date).toDateString() }}</p>
      <NuxtImg
        v-show="item.img"
        :src="`${item.img}`"
        sizes="sm:80vw md:50vw lg:30vw"
      />
      <p id="description">{{ item.description }}</p>
    </div>
  </main>
</template>
```

To fetch the items for my `<ContentList>` component, I use the Nuxt Content `queryContent()` method inside my `<script setup>`. The `queryContent()` method is new for Nuxt 3. It accepts a path to query and has chained methods for filtering, sorting, and specifying how many results to return. More details are available [in the docs here](https://content.nuxtjs.org/guide/displaying/querying).

Wrap your query in the `useAsyncData()` composable, which is auto-imported. The first parameter to `useAsyncData()` is just a name for the query. In the code below, I’m dynamically passing either `events` or `blog` via the `section` prop to specify the name and path for the query.

```typescript
<script setup lang="ts">
import { Section } from "~~/types";

const props = defineProps<{
  section: Section;
}>();

const { data } = await useAsyncData(`content-list-${props.section}`, () =>
  queryContent(`/${props.section}`)
    .where({ _dir: { $ne: "" } })
    .sort({ date: -1 })
    .find()
);
</script>
```

In the example above, I’m using `.where({ _dir: { $ne: '' } })` to only return content files where the directory is not blank. This is to exclude the `index.md` files from the list.

#### Fetching with useContent()

Document-Driven Mode also provides a `useContent()` method for fetching data about the content used to generate the current page. This is helpful if you only need context for the page you’re on, or info on the content surrounding the current page.

For my `<Event>` component, I only need the current content to render the event details. 

```typescript
<script setup lang="ts">
import { Event } from "~~/types";

const { page } = useContent();

const event: Event = page;
</script>
```

I can get the `page` variable from `useContent()` then assign that as the `event` to use in my template. More details on `useContent()` are [in the docs here](https://content.nuxtjs.org/guide/writing/document-driven#global-variables).

### Applying Layouts

Right now, the `<NuxtPage />` component in my `app.vue` handles injecting the content from my Markdown files directly into the page. For my blog posts, however, I’d like to show the title of the post and add some navigation on the bottom to go back to the posts list. I could use MDC Syntax, but would have to add this to every Markdown file. Because I know I want to use this for every blog post, I can create a layout instead.

Inside my `layouts` directory, I have a `blog.vue` file.

```jsx
<template>
  <div>
    <BlogHeader />
    <slot />
    <BlogFooter />
  </div>
</template>
```

In this layout, I’m adding a `<BlogHeader />` component that renders the blog post title, and a `<BlogFooter />` component that adds some navigation. These are wrapped around `<slot />` component, which is required to ensure the Markdown component is injected in the page.

> ⚠️ Make sure you do not have a `<NuxtLayout />` component in your `app.vue`.

With Document-Driven Mode, I can set the layout in the Markdown front matter with the `layout` field. I’d recommend adding this to your VS Code snippet so it’s set for every blog post.

```markdown
---
title: Using FontAwesome Icons with Nuxt 3
description:
date: 2023-01-08
img:
tags: ["nuxt", "icons"]
layout: blog
---
```

## Next Steps

This is an early implementation of using Document-Driven Mode in Nuxt Content v2, and there are other features we can add in the future. For example:

- Creating an `event` layout instead of inserting the `<Event>` component directly in the Markdown files
- Add next/previous navigation to individual blog and event pages using `useContent()`
- Adding a tag filter feature to content category index pages
- Adding a `range` prop to the `<ContentList>` component to show upcoming or past events
- Add code syntax highlighting to blog posts
- Adding more content categories like videos, podcasts, tutorials, etc.

If you are interested in contributing to Nuxt Content, check out [the GitHub repository here](https://github.com/nuxt/content). Also, if you are migrating from Nuxt 2 to Nuxt 3, I recommend [Debbie O'Brien's blog post here](https://debbie.codes/blog/migrating-nuxt2-nuxt3/), which was very helpful as I worked on my own site.

If you end up using Document-Driven Mode for your own site, share the link! I’d love to see how others use this and the various implementations possible.