20.10.2020

Statische Blogartikel mit @nuxt/content erzeugen

Mit dem neuen nuxt-Modul @nuxt/content (https://content.nuxtjs.org/) ist es ein leichtes, einen statischen Blog zu erstellen. In diesem Artikel möchte ich zeigen, wie dieser Blog mit Hilfe von @nuxt/content entstanden ist.

Erzeugen/Anlegen der Inhalte

Inhalte werden im Root-Ordner content abgelegt und können standardmäßig in unterschiedlichen Formaten angelegt werden, dazu zählen:

  • Markdown
  • JSON
  • CSV
  • XML
  • YAML

Je nach Typ sorgen unterschiedliche Parser dafür, dass die Inhalte in der Nuxt-App über den Root-Kontext abrufbar sind (this.$content bzw. context.$content).

Für den Blog ist sicherlich Markdown der passendste Typ. Über einen vorangestellten Bereich (Front Matter) ist es möglich Attribute des Inhalts zu setzen. Dazu zählen z.B. Titel und Beschreibung, aber man ist nicht auf die Standard-Attribute beschränkt, es ist auch möglich eigene Attribute hinzuzufügen (im Beispiel: img und date)

---
title: '@nuxt/content + Blog = 😍'
description: 'Das ist ein Beispiel-Beschreibungstext'
img: blog-images/2020/nuxt-content.jpg
date: 2020-10-20
---
# Überschrift 1

## Überschrift 2

...

Wichtig zu wissen: aktuell ist es die empfohlene Variante, Bilder und andere statische Dateien, die innerhalb des Inhalts eingebunden werden, im static Ordner der Nuxt-App abzulegen. Hintergrund hierbei ist, dass bei einem Build der App der static-Ordner von Webpack ignoriert wird und so beim Hinzufügen von Inhalten kein neuer Webpack-Build erforderlich ist.
Für andere Ablageordner gibt es auf GitHub bereits mehrere Vorschläge, wie das umsetzbar ist (siehe hier). Ebenfalls kann man dem GitHub Issue entnehmen, dass bereits an einer Lösung gearbeitet wird, sodass in Zukunft standardmäßig vermutlich auch andere Ordner verwendet werden können.

Abruf der Inhalte: Query-API ähnlich zu MongoDB

Nach dem Erstellen der Inhalte sollen diese natürlich auch in der Nuxt-App angezeigt werden. Dazu bietet das Modul eine API, die der von MongoDB ähnelt. Mit einfachen verkettbaren Methoden hat man die Kontrolle darüber, welche Inhalte gefunden und angezeigt werden sollen.

Beispiel: Folgende Abfrage liefert alle Blogartikel mit ihrem Titel, ihrer Beschreibung, URL-Slug, vollständigem Pfad und Erstellungsdatum zurück.

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

Anzeige der Inhalte

Innerhalb der Vue-Komponente können die Inhalte dann ganz einfach verwendet werden. Für Markdown-Inhalte gibt es bereits die mitgelieferte Komponente nuxt-content, über die das Markdown geparsed und gerendert wird. Die Styles der einzelnen Bereiche können dann einfach über simple CSS-Selektoren angepasst werden, die über die CSS-Klasse "nuxt-content" nur auf den Markdown Bereich gescoped werden.

<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>

Automatisches Routing über Verzeichnisstruktur

Innerhalb der Nuxt-App integriert sich das Modul auch nahtlos in das Routing. Wie man es vom Routing in Nuxt gewöhnt ist, ergibt sich die URL aus einer Abbildung der Verzeichnisstruktur. Dabei werden vom Modul automatisch die entsprechenden Routen dem Router hinzugefügt.

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

Für diese Verzeichnisstruktur werden also automatisch folgende Routen angelegt:

  • /home
  • /2019/artikel1
  • /2020/artikel2

Dieses Verhalten kann natürlich angepasst werden, so kann man z.B. einen eigenen URL-Slug als Attribut im Front Matter Bereich hinzufügen und diesen dann bei der Generierung der Routen abfragen und verwenden.

Static site generation Support

Nuxt bietet standardmäßig die Möglichkeit der Generierung von statischen Seiten. Auch hier fügt sich das content-Modul nahtlos ein - die Generierung funktioniert weiterhin ohne Probleme.
Allerdings gibt es eine kleine Einschränkung: sofern die Inhalte nicht nachgeladen werden dürfen/können/sollen, muss die Abfrage der Inhalte innerhalb der jeweiligen Nuxt-Page gemacht werden, da nur dort das Laden der Inhalte über asyncData verfügbar ist.

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,
    }
  },
}

Die Generierung der Seiten selbst ist davon nicht eingeschränkt, es geht also wirklich hauptsächlich darum, ob die Inhalte im generierten HTML bereits vorhanden sein sollen.

Trennung von Code und Content

Durch die Trennung von Applikationscode und Content ist möglich, die Code-Generierung (Webpack) beim Hinzufügen von neuen Inhalten zu überspringen, da einfach festgestellt werden kann, ob sich der Applikations-Code geändert hat oder nicht. Das führt zu einer deutlich schnelleren "Build"-Zeit, da nur noch die neuen Inhalte prozessiert werden müssen.

Fragen oder Anmerkungen?