You are reading Nuxt Content V1 documentation. Read the latest version
Community
Snippets
Learn how to implement @nuxt/content into your app with these code snippets.
Usage
asyncData
export default {
async asyncData({ $content, params }) {
const article = await $content('articles', params.slug).fetch()
return {
article
}
}
}
head
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
Search
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 has the same slug, you should set path
as the first argument of the surround
method, instead of slug
.
This is because Nuxt Content finds previous and next documents based on the one that matched first.
For example, if you sort documents according to position
, the lower-positioned document will be always used for calculation,
even when the current page is showing the higer-positioned 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 lower-cased.
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:
<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
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/main/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(rawCode, { language: lang }).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 inline it in the HTML.
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/main/packages/shiki/themes
theme: 'nord'
})
return (rawCode, lang) => {
return highlighter.codeToHtml(rawCode, lang)
}
}
}
}
}
Shiki Twoslash
Twoslash is a markup format for TypeScript code. Internally, Twoslash uses the TypeScript compiler to generate rich highlight info.
To get a better idea of how Twoslash works, you can go over to the Official TypeScript Documentation and hover over some code examples there.
You can achieve the same result by using Shiki Twoslash. This package is also the one that powers the Official TypeScript Documentation.
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/main/packages/shiki/themes
theme: 'nord'
})
return (rawCode, lang) => {
const twoslashResults = runTwoSlash(rawCode, lang)
return renderCodeToHTML(
twoslashResults.code,
lang,
['twoslash'],
{},
highlighter,
twoslashResults
)
}
}
}
}
}
Remark Plugin
Nuxt Content uses remark under the hood to process markdown documents. Creating remark plugins is a way to manipulate documents and add new features.
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 the document data.
- Create the plugin. This plugin fetches the contributors if
fetchContributors
is set totrue
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
export default {
contents: {
markdown: {
remarkPlugins: [
'~~/plugins/contributors.js'
]
}
}
}
- Create a simple component to show contributors
<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 the document to fetch the contributors
---
title: Nuxt Content
fetchContributors: true
---
## Contributors
<list :items="$contributors"></list>