20.10.2020

Creating static blog articles with @nuxt/content

With the new nuxt module "@nuxt/content" it is easy to create a static blog. In this article I would like to show how this blog was created with the help of @nuxt/content.

Creating the content

Content is stored in the root folder content and can be created in different formats by default, including

  • Markdown
  • JSON
  • CSV
  • XML
  • YAML

Depending on the type, different parsers ensure that the content in the Nuxt app is accessible via the root context (this.$content or context.$content).

For the blog, Markdown is certainly the most suitable type. Via a prefixed area (Front Matter) it is possible to set attributes of the content. This includes e.g. title and description, but you are not limited to the standard attributes, it is also possible to add your own attributes (in the example: img and date)

---
title: '@nuxt/content + blog = 😍'
description: 'This is my example description'
img: blog-images/2020/nuxt-content.jpg
date: 2020-10-20
---
# Heading 1

## Heading 2

...

Important to know: currently the recommended variant is to store images and other static files that are included within the content in the static folder of the Nuxt app. The background to this is that during a build of the app the static folder is ignored by webpack and therefore no new webpack build is required when adding content.
GitHub already has several suggestions on how to do this for other storage folders (see here). The GitHub Issue also says that a solution is already being worked on, so you can probably use other folders by default in the future.

Querying the content: Query-API similar to MongoDB

After creating the content, it should of course be displayed in the Nuxt app. For this purpose the module offers an API similar to MongoDB. With simple concatenated methods you have control over which content is found and displayed.

Example: The following query returns all blog articles with their title, description, URL plug, full path and creation date.

const articles = await $content('blog', { deep: true })
  .only(['title', 'description', 'slug', 'path', 'createdAt'])
  .sortBy('createdAt', 'desc')
  .fetch()

Displaying the content

Within the Vue component, the content can then be easily used. For markdown content there is already the included component 'nuxt-content', which is used to parse and render the markdown. The styles of the individual areas can then be easily adapted using simple CSS selectors, which are only scoped to the Markdown area using the CSS class nuxt-content.

<template>
  <article class="article">
    <div class="banner" :class="article.img ? 'with-bg' : ''">
      <img v-if="article.img" :src="`/${article.img}`" />
      <h1>{{ article.title }}</h1>
    </div>
    <div class="date">{{ date }}</div>
    <nuxt-content :document="article" class="content" />
  </article>
</template>

<script>
import { toLocaleDate } from '@/helpers/date'
export default {
  props: {
    article: {
      type: Object,
    },
  },
  computed: {
    date() {
      return toLocaleDate(this.article.date)
    },
  },
}
</script>
<style lang="scss">
.nuxt-content {
  h1 {
    font-size: 2em;
    margin: 0 0 0.3em;
  }
  h2 {
    margin: 1.5em 0 0.5em;
  }
  ul {
    margin: 0.5em 0;
  }
}
</style>

Automatic routing through file structure

Within the Nuxt app, the module also integrates seamlessly into the routing. As one is used to from the routing in Nuxt, the URL results from a mapping of the directory structure. The module automatically adds the corresponding routes to the router.

content
|-- home.md
|-- 2019
    |-- artikel1.md
|-- 2020
    |-- artikel2.md

So the following routes are automatically created for this directory structure:

  • /home
  • /2019/article1
  • /2020/article2

This behavior can of course be customized, e.g. you can add your own URL plug as an attribute in the Front Matter area and then query and use it when generating the routes.

Static site generation support

Nuxt offers by default the possibility to generate static pages. Here too, the content module fits in seamlessly - the generation continues to work without problems.
However, there is a small restriction: if the content is not allowed/can/should not be reloaded, the content query must be made within the respective Nuxt page, since only there the loading of content via asyncData is available.

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

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

    return {
      article,
    }
  },
}

The generation of the pages themselves is not restricted by this, so it is really mainly about whether the content should already exist in the generated HTML.

Seperation of code and content

By separating application code and content, it is possible to skip code generation (webpack) when adding new content, as it is easy to determine whether the application code has changed or not. This leads to a much faster "build" time, since only the new content needs to be processed.

Fragen oder Anmerkungen?