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

  • 公開日アイコン
  • 更新日アイコン
Nuxt.jsにnuxt/contentをインストールしてブログを作成する メイン画像

Ad Area

ブラックフライデーセール. 最大 90%OFF

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
nuxt.config.js
{
  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の部分は、カテゴリー・タグそれぞれのスラッグになる。

フロントマター

ブログ記事

post-1.md
---
title: タイトル
published: 2018-02-15
updated: 2020-05-26
category: カテゴリー名
tags: 
    - タグ名,タグ名
image: /img/filename.png
---

記事の説明が入ります
<!--more-->

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

カテゴリー・タグ

category-1.md
---
name: コーディング
---

カテゴリー・タグの説明が入ります
<!--more-->

componentを作成

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

components/
    post/
    _slug.vue
    index.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() …データを収集

記事一覧を表示

index.vue
<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などで、フロントマターで定義したメタ情報を引用できる。

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

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

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

個別ページ

ページの情報を取得する

_slug.vue
<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)を取得

ページの情報を表示する

_slug.vue
<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の記述

nuxt.config.js
  content: {
    markdown: {
      prism: {
        theme: false,
      }
    }
  },

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

prism.jsを作成

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

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ファイルに埋め込み

_slug.vue
<script>
import Prism from '~/plugins/prism'

export default {mounted() {
    Prism.highlightAll()
  },}
</script>

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

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

```css[_base.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: .25rem .5rem;
        font-size: .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スタイルを付けた。

参考ページ

Ad Area