Nuxt.jsにnuxt/contentをインストールしてブログを作成する

この記事は最終更新日から 1 年以上が経過しており、内容が古くなっている可能性があります。

nuxt/content モジュールを使えば、カテゴリー・タグの作成、前後の記事リンクの出力など、ブログに必要な一通りのカスタマイズができる。学習コストも比較的低めなので、Nuxt.js 初心者でも取り入れやすい。

nuxt/content モジュールを使って NuxtJS アプリケーションを強化します。content/ディレクトリに書き込むこと で、MongoDB のような API を使って Markdown、JSON、YAML、XML、CSV ファイルを取得します。

MongoDB というのがよく分からないが、外部サービスから API で引っ張ってくるのではなく、ローカル環境のファイルで記事を管理できる。

nuxt/content のインストール

npm install @nuxt/content
// or
yarn add @nuxt/content
{
  modules: [
    '@nuxt/content'
  ],
}

コンテンツの作成

content ディレクトリを作成し、その中にコンテンツを作っていく。content 配下のディレクトリ名は任意名で作成できる。

ブログ記事

content/
  post/
    post-1.md
    post-2.md
  home.md

カテゴリー・タグ

content/
  category/
    category-1.md
    category-2.md
  tag/
    tag-1.md
    tag-2.md

カテゴリー・タグの分だけファイルを作成する。category-1 tag-1の部分は、カテゴリー・タグそれぞれのスラッグになる。

フロントマター

ブログ記事

---
title: "タイトル"
published: "2018-02-15"
updated: "2020-05-26"
category: "カテゴリー名"
tag: ["タグ名1", "タグ名2"]
image: "/images/path/filename.png"
---

記事の説明が入ります

フロントマター(---で囲まれた部分、yaml 形式で記述)で記事のメタデータが管理できる。任意で追加することが可能である。フロントマターでdescriptionを定義していない場合は、<!--more-->(※半角を挟まない)より前の部分がdescriptionとして抽出される。

カテゴリー・タグ

---
name: コーディング
---

カテゴリー・タグの説明が入ります

<!--more-->

component を作成

component ディレクトリ内にコンテンツ表示用の vue ファイルを作成する。

components/
    post/
    _slug.vue
    index.vue
index.vue

記事一覧

記事一覧を取得

<script>
  export default {
    (前略)
    async asyncData ({ $content }) {
      const posts = await $content('post')
        .sortBy('published', 'desc')
        .fetch()
      return { posts }
    },
    (後略)
  }
</script>
  • $content('post')content内のpostフォルダーより記事を抽出
  • .sortBy('published', 'desc') …公開日を降順でソート
  • .fetch() …データを収集

記事一覧を表示

<template>
  <main role="main">
    <div class="p-card">
      <article
        class="p-article-item"
        v-for="(post, index) in posts"
        :key="index"
      >
        <nuxt-link :to="'/post/' + post.slug +'/'" class="p-article-item__link">
          <figure class="p-article-item__thumb">
            <img
              :src="post.image"
              :alt="post.title + ' サムネイル'"
              class="p-article-item__img"
            />
          </figure>
          <div class="p-article-item__text">
            <h2 class="p-article-item__title">{{ post.title }}</h2>
            <ul class="meta-container">
              <li class="meta meta--published">
                <time :datetime="post.published"
                  >{{ post.published | format-date }}</time
                >
              </li>
              <li class="meta meta--category">{{ post.category }}</li>
            </ul>
          </div>
        </nuxt-link>
      </article>
    </div>
  </main>
</template>
  • v-for="(post, index) in posts" :key="index"

変数postに格納された情報をpostの数だけ繰り返し表示する。post.title post.imageなどで、フロントマターで定義したメタ情報を引用できる。

カテゴリー・タグに属する記事一覧を取得する

<script>
  export default {
    (前略)
    async asyncData ({ $content }) {
      .where({ tags: { $contains: tag.name }})
      .sortBy('published', 'desc')
      .fetch()
      return { posts }
    },
    (後略)
  }
</script>

記事全体からカテゴリー・タグ名を含む記事を抽出する。

個別ページ

ページの情報を取得する

<script>
  export default {
    (略)
    async asyncData ({ $content, params }) {
      const post = await $content('post', params.slug).fetch()
      const [prev, next] = await $content('post')
        .only(['title', 'slug'])
        .sortBy('published', 'asc')
        .surround(params.slug)
        .fetch()
      const tagList = await $content('tag')
        .only(['name', 'slug'])
        .where({ name: { $containsAny: post.tags } })
        .fetch()
      const tags = Object.assign({}, ...tagList.map((s) => ({ [s.name]: s })))
      const cate = await $content('category')
        .only(['name', 'slug'])
        .where({ name: { $containsAny: post.category } })
        .limit(1)
        .fetch()
      const category = cate[0]
      return {
        post,
        prev,
        next,
        tags,
        category,
      }
    },
  }
</script>
  • $content('post', params.slug)post フォルダよりスラッグと一致する記事を取得
  • .surround(params.slug)…スラッグの前後の記事を取得
  • category tag フォルダより、フロントマターで定義された情報(post.category post.tag)と一致するカテゴリー・タグ情報(name slug)を取得

ページの情報を表示する

<template>
  <main class="l-main">
  <article class="p-article">
    <header class="p-article__header">
      <div class="p-article__thumbWrap">
        <figure class="p-article__thumb">
          <img :src="post.image" :alt="post.title + ' メイン画像'">
        </figure>
      </div>
      <div class="p-article__main">
        <h1 class="p-article__title">{{ post.title }}</h1>
        <div class="meta-top">
          <ul class="meta-container">
            <li class="meta meta--published">
              {{ post.published | format-date }}</time>
            </li>
            <li v-if="post.updated" class="meta meta--updated">
              <time datetime="post.updated">{{ post.updated | format-date }}</time>
            </li>
          </ul>
        </div>
      </div>
    </header>
    <div class="p-article__content">
      <nuxt-content :document="post" />
    </div>
    <footer class="p-article__footer">
      <!-- メタ情報 -->
      <div class="meta-bottom">
        <ul class="meta-container">
          <li class="meta meta--category">
            <nuxt-link :to="`/post/category/${category.slug}/`">{{ category.name }}</nuxt-link>
          </li>
          <li class="meta meta--tag">
            <span v-for="(tag, index) in post.tags" :key="index" class="mr-2"><nuxt-link :to="`/post/tag/${tags[tag].slug}/`">{{ tags[tag].name }}</nuxt-link></span>
          </li>
        </ul>
      </div>
      <!-- 前後の記事リンク -->
      <ul class="page-nav">
        <li v-if="next" class="page-nav__item page-nav__item--next">
          <p class="page-nav__text page-nav__text--next"><span class="page-nav__text-inner">次の記事</span></p>
          <nuxt-link :to="`/post/${next.slug}/`" class="page-nav__link">
            {{ next.title }}
          </nuxt-link>
        </li>
        <li v-else class="page-nav__item page-nav__item--none">
          &nbsp;
        </li>
        <li v-if="prev" class="page-nav__item page-nav__item--prev">
          <p class="page-nav__text page-nav__text--prev"><span class="page-nav__text-inner">前の記事</span></p>
          <nuxt-link :to="`/post/${prev.slug}/`" class="page-nav__link">
          {{ prev.title }}
          </nuxt-link>
        </li>
        <li v-else class="page-nav__item page-nav__item--none">
          &nbsp;
        </li>
      </ul>
    </footer>
  </article>
</main>
</template>
  • <nuxt-content :document="post" />…ページの body を表示
  • v-if="prev" v-if="next" “…prev nextが存在するときに前後の記事リンクを表示
  • v-else…存在しない時は空白を表示

nuxt/content で PrismJS を使う

nuxt/content をインストールすると、PrismJSも一緒にインストールされる。prism-theme をインストールして PrismJS のテーマを選ぶ方法もあるが、今回は拡張性のある別の方法にする。

nuxt.config.js の記述

  content: {
    markdown: {
      prism: {
        theme: false,
      }
    }
  },

サーバーサイドのハイライトは使わないのでfalseとする。

prism.js を作成

/plugins/フォルダ内にprism.jsファイルを作成する。

import Prism from "prismjs";

// テーマの適用
import "prismjs/themes/prism-tomorrow.css";
// 行番号を表示する(任意)
import "prismjs/plugins/line-numbers/prism-line-numbers";
import "prismjs/plugins/line-numbers/prism-line-numbers.css";
// 言語を表示する(任意)
import "prismjs/plugins/show-language/prism-show-language";
// 表示できる言語を追加する(任意)
import "prismjs/components/prism-json.js";
import "prismjs/components/prism-markdown.min.js";

export default Prism;

Vue ファイルに埋め込み

<script>
  import Prism from '~/plugins/prism'

  export default {

    mounted() {
      Prism.highlightAll()
    },

  }
</script>

Prism.highlightAll()で PrismJS のハイライトが適用される。

マークダウンファイル内での記述方法

/* 
```の後で言語名を指定
※実際は半角開けない↓
*/
` ` `css
/* prism */
.nuxt-content-highlight {
    position: relative;
    .filename {
        display: inline-block;
        border-bottom: none;
        padding: .25rem .5rem;
        font-size: .8rem;
        position: absolute;
        right: 1px;
        top: 1px;
        color: #bbb;
    }
}
` ` `
/* ```で閉じる */

```(バッククオート 3 つ)の後ろで指定したい言語、[]内でファイル名が記述できる。

/* prism */
.nuxt-content-highlight {
  position: relative;
  .filename {
    display: inline-block;
    border-bottom: none;
    padding: 0.25rem 0.5rem;
    font-size: 0.8rem;
    position: absolute;
    right: 1px;
    top: 1px;
    color: #bbb;
  }
}

出力されるタグ

<div class="nuxt-content-highlight" data-v-xxxxxxxxx="">
  <span data-v-xxxxxxxxx="" class="filename">_base.css</span>
  <pre class="line-numbers language-css" data-v-xxxxxxxxx="">
  (略)
  </pre>
</div>

<pre>…</pre> 内はハイライト用 css が適用されているが、<span data-v-xxxxxxxxx="" class="filename">…</span>(ファイル名の部分)にはデフォルトのスタイルがない。当ブログではコードブロック右上に表示されるように css スタイルを付けた。

参考ページ

Suzunatsu

このブログについて

コーディングやWeb関連技術の記事と、買い物など日々のメモから成り立っています。 →少しだけ詳しく

この記事がお役に立ちましたら、サポートをお願いします。

広告