Snippets

Check out these code snippets that can be copied directly into your application.

Usage

asyncData

export default {
  async asyncData({ $content, params }) {
    const article = await $content('articles', params.slug).fetch()

    return {
      article
    }
  }
}

Add dynamic metas based on title and description defined in the front-matter:

export default {
  async asyncData({ $content, params }) {
    const article = await $content('articles', params.slug).fetch()

    return {
      article
    }
  },
  head() {
    return {
      title: this.article.title,
      meta: [
        { hid: 'description', name: 'description', content: this.article.description },
        // Open Graph
        { hid: 'og:title', property: 'og:title', content: this.article.title },
        { hid: 'og:description', property: 'og:description', content: this.article.description },
        // Twitter Card
        { hid: 'twitter:title', name: 'twitter:title', content: this.article.title },
        { hid: 'twitter:description', name: 'twitter:description', content: this.article.description }
      ]
    }
  }
}

Features

Add a search input component by using watch:

<template>
  <div>
    <input v-model="query" type="search" autocomplete="off" />

    <ul v-if="articles.length">
      <li v-for="article of articles" :key="article.slug">
        <NuxtLink :to="{ name: 'blog-slug', params: { slug: article.slug } }">{{ article.title }}</NuxtLink>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data () {
    return {
      query: '',
      articles: []
    }
  },
  watch: {
    async query (query) {
      if (!query) {
        this.articles = []
        return
      }

      this.articles = await this.$content('articles')
        .only(['title', 'slug'])
        .sortBy('createdAt', 'asc')
        .limit(12)
        .search(query)
        .fetch()
    }
  }
}
</script>

Check out the search documentation

Prev and Next

Add previous and next links using the surround method:

<template>
  <div>
    <NuxtLink v-if="prev" :to="{ name: 'blog-slug', params: { slug: prev.slug } }">
      {{ prev.title }}
    </NuxtLink>

    <NuxtLink v-if="next" :to="{ name: 'blog-slug', params: { slug: next.slug } }">
      {{ next.title }}
    </NuxtLink>
  </div>
</template>

<script>
export default {
  async asyncData({ $content, params }) {
    const [prev, next] = await $content('articles')
      .only(['title', 'slug'])
      .sortBy('createdAt', 'asc')
      .surround(params.slug)
      .fetch()

    return {
      prev,
      next
    }
  }
}
</script>

If more than one document have same slug, you should set path as first argument of surround method, instead of slug. This is because Nuxt Content finds previous and next documents based on one which first-matched.

For example, if you sort documents according to position, lower-position document will be always used for calculation, even when the current page is showing higer-position document.

Check out the surround documentation

Case-Insensitive Sorting

It is needed to work around Nuxt Content's case-sensitive sorting, to add extra properties to documents, whose value is the lower-cased.

nuxt.config.js
export default {
  hooks: {
    'content:file:beforeInsert': (document) => {
      if (document.extension === '.md') {
        Object.entries(document).forEach(([key, value]) => {
          const _key = `case_insensitive__${key}`; // prefix is arbitrary

          if (!document[_key] && typeof value === 'string') {
            document[_key] = value.toLocaleLowerCase();
          }
        });
      }
    }
  }
};

Then, call sortBy method with the extra prop's key by which to sort.

export default {
  async asyncData({ $content, params }) {
    const articles = await $content('articles', params.slug)
      .sortBy('case_insensitive__title', 'asc') // Set prefixed prop
      .fetch()

    return {
      articles
    }
  }
}

Check out the sortBy documentation

Table of contents

Add a table of contents by looping over our array of toc and use the id to link to it and the text to show the title. We can use the depth to style the titles differently:

<template>
  <ul>
    <li
      v-for="link of article.toc"
      :key="link.id"
      :class="{ 'toc2': link.depth === 2, 'toc3': link.depth === 3 }"
    >
      <NuxtLink :to="`#${link.id}`">{{ link.text }}</NuxtLink>
    </li>
  </ul>
</template>

<script>
export default {
  async asyncData({ $content, params }) {
    const article = await $content('articles', params.slug)
      .fetch()

    return {
      article
    }
  }
}
</script>

Check out the Table of contents documentation

Dynamic routing

Let's say you want to create an app with routes following the content/ file structure, you can do so by creating a pages/_.vue component:

pages/_.vue
<script>
export default {
  async asyncData ({ $content, app, params, error }) {
    const path = `/${params.pathMatch || 'index'}`
    const [article] = await $content({ deep: true }).where({ path }).fetch()

    if (!article) {
      return error({ statusCode: 404, message: 'Article not found' })
    }

    return {
      article
    }
  }
}
</script>

This way, if you go the /themes/docs route, it will display the content/themes/docs.md file. If you need an index page for your directories, you need to create a file with the same name as the directory:

content/
  themes/
    docs.md
  themes.md

Don't forget to prefix your calls with the current locale if you're using nuxt-i18n.

Custom Highlighter

Highlight.js

nuxt.config.js
import highlightjs from 'highlight.js'

const wrap = (code, lang) => `<pre><code class="hljs ${lang}">${code}</code></pre>`

export default {
  // Complete themes: https://github.com/highlightjs/highlight.js/tree/master/src/styles
  css: ['highlight.js/styles/nord.css'],

  modules: ['@nuxt/content'],

  content: {
    markdown: {
      highlighter(rawCode, lang) {
        if (!lang) {
          return wrap(highlightjs.highlightAuto(rawCode).value, lang)
        }
        return wrap(highlightjs.highlight(lang, rawCode).value, lang)
      }
    }
  }
}

Shiki

Shiki is syntax highlighter that uses TexMate grammar and colors the tokens with VS Code themes. It will generate HTML that looks like exactly your code in VS Code.

You don't need to add custom styling, because Shiki will inlined it in the HTML.

nuxt.config.js
import shiki from 'shiki'

export default {
  modules: ['@nuxt/content'],

  content: {
    markdown: {
      async highlighter() {
        const highlighter = await shiki.getHighlighter({
          // Complete themes: https://github.com/shikijs/shiki/tree/master/packages/themes
          theme: 'nord'
        })
        return (rawCode, lang) => {
          return highlighter.codeToHtml(rawCode, lang)
        }
      }
    }
  }
}

Shiki Twoslash

Twoslash is a markup format for TypeScript code. Internally, Twoslash uses TypeScript compiler to generate rich highlight info.

To get better idea how Twoslash works, you can get over to the Official TypeScript Documentation and hover some code example there.

You can achieve the same result by using Shiki Twoslash. This package is also the one that power Official TypeScript Documentation.

nuxt.config.js
import {
  createShikiHighlighter,
  runTwoSlash,
  renderCodeToHTML
} from 'shiki-twoslash'

export default {
  modules: ['@nuxt/content'],

  content: {
    markdown: {
      async highlighter() {
        const highlighter = await createShikiHighlighter({
          // Complete themes: https://github.com/shikijs/shiki/tree/master/packages/themes
          theme: 'nord'
        })
        return (rawCode, lang) => {
          const twoslashResults = runTwoSlash(rawCode, lang)
          return renderCodeToHTML(
            twoslashResults.code,
            lang,
            ['twoslash'],
            {},
            highlighter,
            twoslashResults
          )
        }
      }
    }
  }
}

Remark Plugin

Nuxt Content used remark under the hood to process markdown documents. Creating remark plugins is a way to manipulate document and add new features contents.

List all contributors

Let's say you want to list all contributors of the project in a document. You can create a plugin that fetches all contributors and injects them into document data.

  • Create the plugin, this plugin fetches the contributors if fetchContributors is set to true
~~/plugins/contributors.js
const fetch = require('node-fetch')

module.exports = function () {
  return async (tree, { data }) => {
    if (data.fetchContributors) {
      const contributors = await fetch(
        'https://api.github.com/repos/nuxt/content/contributors'
      ).then(res => res.json())
      .then(res => res.map(({ login }) => login))
      
      data.$contributors = [...new Set(contributors)]
    }
    return tree
  }
}
  • Register plugin in Nuxt config
nuxt.config.js
export default {
  contents: {
    markdown: {
      remarkPlugins: [
        '~~/plugins/contributors.js'
      ]
    }
  }
}
  • Create a simple component to show contributors
~~/components/List.vue
<template>
  <ul>
    <li v-for="(item, i) in items" :key="i">
      {{ item }}
    </li>
  </ul>
</template>

<script>
export default {
  props: {
    items: {
      type: Array,
      default: () => []
    }
  }
}
  • Finally use the components and mark document to fetch the contributors
document.md
---
title: Nuxt Content
fetchContributors: true
---

## Contributors

<list :items="$contributors"></list>

Edit this page on GitHub Updated at Tue, Mar 2, 2021