Nuxt Content × SSG で作る静的ブログ。2:基本的な記事項目のレンダリングと静的書き出し
技術スタック JavascriptNuxt.js

Nuxt Content × SSG で作る静的ブログ。2:基本的な記事項目のレンダリングと静的書き出し

2021.05.09 2021.05.05

こんにちはjunです。前回の記事は概念と基本的なコンテンツの作成・取得・表示について解説しました。今回の記事では

  • nuxt.jsにおけるpage構成の設定
  • 目次生成、画像のレンダリング、どんな項目をレンダリングできるのか?
  • 静的書き出しの設定

について解説していきます。それでは早速いきましょう。

サイトのルーティングを考えてpagesの構成を設定する

何をどう設定すればいいか

Nuxt.jsはpages/というディレクトリ配下にファイルを作ることで、vue-routerの設定が自動で行われURLが生成されます。私のブログでは/articles/{sulg}というURLで対象の記事を呼び出します。そのURLが有効になるにはpages/ディレクトリの構築をする必要があります。

一方、Nuxt Contentで任意の記事データを取得してみるとpath:というプロパティがあります。それはcontent/ディレクトリをルートとしたパスになっています。以下のような構成の場合、

├── content
│   ├── articles
│   │   ├── aaaaaa.md

aaaaaa.mdのデータのパスは/articles/aaaaaaとなります。つまりhttps://example.com/articles/aaaaaaというルートに対して、aaaaaa.mdが呼び出されるように設定します。

ひとまず以下のようにpage/を作成

ひとまず以下のようにpages/配下に作成してください。

├── pages
│   ├── articles
│       ├── _slug.vue

_slug.vueといものを作ることで公式にある通り/articles/{sulg}のルートに対してparams.sulg{sulg}の値を取得できるようになります。

params.sulgで指定されたsulgのファイルfetchする

それでは_sulg.vueで以下のように記述します。

/articles/_sulg.vue
<template>
  <article>
    <nuxt-content :document="content" />
  </article>
</template>
<scirpt>
export default {
  async asyncData({ $content, params,redirect }) {
    const content = await $content('articles').where({path:"/articles/"+params.slug}).fetch();
    if(content.length > 0){
      return {
        content:content[0],
      }
    }else{
      redirect('/articles')
    }
  }
}
</script>

asyncData()中で$content()をフェッチします。そしてwhere()"/articles/"+params.slugに一致するコンテンツを引っ張るようにします。

whereクエリを使用すると配列で結果が返るので、あれば一致した結果、なければ一覧ページにリダイレクトするようにします。以上でNuxtにおけるpagesディレクトリの設定は完了です。

asyncData()はSSRの時のpage/配下のファイルで使用できます。サーバーサイドで処理される箇所であり、そこで$contentをfetchします。静的書き出しを行うと、asyncData()内の処理は書き出し中に実行されます。 公式:Data Fetching

コンテンツの作成

ブログ記事は単に文章だけでなく、太字、リンク、画像、見出し、目次が大体必要になります。文章の修飾はマークダウンの記述を行えば問題ありません。マークダウン記法は今回は解説しません。こちらの記事がお世話になりました。

画像をレンダリングする場合

マークダウンで画像を表示する場合は基本的に以下のように記述します。

![image alt text](/image/sample.jpg)

Nuxtの場合は画像をassetあたりに入れておき、require('~/asset/sample.jpg')という感じで依存性を解決できます。しかしマークダウンでは以下のパターンで画像パスの解決ができません。

// パターン1
![image alt text](~/asset/sample.jpg)

// パターン2
![image alt text](/asset/sample.jpg)

// パターン3
![image alt text](require(~/asset/sample.jpg))

画像のレンダリングは"@nuxt/content": "^1.14.0"時点で特段に対応されておらず、ドキュメントにも書かれていませんでした。Githubのissueでも報告されているように議論となっています。

一応解決策としてはマークダウンファイルそのものにVueコンポーネントを書いてしまうことです。最初に画像レンダリング用のコンポーネントを作成します。

components/imageRender.vue
<template>
  <img :src="imgSrc()" :alt="alt"/>
</template>

<script>
export default {
  props: {
    src: {
      type: String,
      required: true
    },
    alt: {
      type: String,
    },
  },
  methods: {
    imgSrc() {
      try {
        return require(`~/assets/image/${this.src}`)
      } catch (error) {
        return null
      }
    }
  },
}
</script>

上記のコンポーネントを マークダウン に記述します。

以下のようにします。
<imageRender src="sample.jpg"/>

上記の記述はNuxt Contentがåいい感じにvueコンポーネントとして扱ってくれます。パスの解決はコンポーネント内のrequire()が行います。あまりかっこいい方法ではありませんが、一応これで画像をレンダリングできます。

HTML要素をレンダリングする

Nuxt Contentはマークダウン内のHTMLをHTMLとして扱ってくれますので、カスタムな要素を記述できます。

<div class="alert alert-warning">
このアラートもこのようにHTMLをマークダウンに書いています!
</div>
このアラートもこのようにHTMLをマークダウンに書いています!

シンタックスハイライトを有効にする

Nuxt Contentは一応開発者向けなのか、簡単にコードブロックに対してシンタックスハイライトを有効にすることができます。prismJSを使用するのでまずはインストールします。

npm install prism-themes

そしてnuxt.config.jsで以下のようにテーマのCSSを設定します。

nuxt.config.js
content: {
    markdown: {
      prism: {
        theme: 'prism-themes/themes/prism-material-oceanic.css'
      }
    }
  },

以上でコードブロック内にてシンタックスハイライトが有効になります。

目次の作成

目次の作成、もとい見出しのデータの取得は簡単です。const content = $content().fetch()で取得した content.tocで見出しのデータが取得できます。

toc:Array[8]
    0:Object
        depth:2
        id:"サイトのルーティングを考えてpagesの構成を設定する"
        text:"サイトのルーティングを考えてpagesの構成を設定する"
    1:Object

私のブログの左サイドにある目次も、上記のようなオブジェクトを利用して作っています。

その他の項目や要素について

以上がブログに必要であろう要素をNuxt Contentで記述しました。私の一般記事のtemplateは以下ようになっていますので、是非参考にしてください。

pages/articles/_slug.vue
<template>
  <div class="p-main-container">
    <div class="p-main-wrapper">
        //目次のコンポーネント
        <toc :toc="content.toc"/>

        //メインのエリア
        <div id="l-center-area">

            //サムネイル
            <img v-if="thumbnail" class="c-article-thumbnail" :src="thumbnail" :alt="content.title">

            //タグとカテゴリーのバッチ
            <div class="p-article-badge p-badge-container">
                <nuxt-link class="c-tag-badge u-blue" v-for="(c,index) in content.category" :key="'category-'+index" :to="'/category/'+c">
                    <span>{{$store.getters['getCategoryTextBySlug'](c)}}</span>
                </nuxt-link>
                <nuxt-link class="c-tag-badge" v-for="(t,index) in content.tag" :key="'tag-'+index" :to="'/tag/'+t">
                    <span>{{$store.getters['getTagTextBySlug'](t)}}</span>
                </nuxt-link>
            </div>

            <h1 class="c-article-header">{{ content.title }}</h1>

            // 更新一時など
            <div class="p-articler-date">
                <span class="c-date"><fa-icon :icon="['fa', 'history']"/>{{ updateAt }}</span>
                <span class="c-date"><fa-icon :icon="['far', 'clock']"/>{{ createdAt }}</span>
            </div>

            // マークダウンのレンダリング箇所
            <nuxt-content :document="content" />
        </div>

        //サイドメニュー
        <sidemenu/>
    </div>
  </div>
</template>

静的書き出しをしてみる

それではpagesファイル、マークダウンも作成したのでとりあえずある分だけ静的書き出ししてみましょう。公式の説明がありますがnuxt.config.jsgenerateオプションを設定必要があります。では以下のように設定します。

nuxt.config.js
generate: {
    async routes () {
      const { $content } = require('@nuxt/content')
      const files = await $content({ deep: true }).only(['path']).fetch()
      return files.map(file => file.path === '/index' ? '/' : file.path);
    }
},

これは何をやっているかというと、$content({ deep: true })を使用してcontent配下にあるマークダウン一式とそのパスを全て取得して、Nuxtに生成すべきルートを伝えています。なぜこれを行う必要があるのかという理由ですが、Nuxt.jsはpages/配下の構成を元にして必要なページを生成します。しかしどんなルート名になるかわからない_sulg.vueというファイル(動的ルート)がある場合は、とりうるルートをgenerateオプション内で明示的に指定する必要があります。

Nuxt.js自身はcontents配下の構成とパスがどうなっているのかわからないので、Nuxt Contentから取得します。

nuxt.config.jsでSSGができる設定にしたら

npm run generate

を叩くことで静的書き出しが行われます。書き出し後にはdist/というビルドファイルが作成されます。

npm run start

でひとまずローカル環境でdist/をドキュメントルートとしてみることができます。以下のような構成で作っていた場合、

├── content
│   ├── articles
│   │   ├── sample.md
│
├── pages
│   ├── articles
│       ├── _slug.vue

http://localhost:3000/articles/sampleにアクセスすると内容が見れると思います。curlでhttp://localhost:3000/articles/sampleでアクセスしてきちんと静的HTMLが書き出されているかを確認してみましょう。

以上で基本的な構成の作成が完了

スタイルとかの問題はあるかもしれませんが、ひとまずpagescontentディレクトリ、nuxt.config.jsの設定を行えばブログ的な構成とCMSとしての機能が実装できました。次回は記事の一覧ページとページング処理について解説していきます。

Copyright © 2021 jun. All rights reserved.