Nuxt.js で Markdown ベースのブログを構築する(Nuxt.js 編)

この記事は「Nuxt.js で Markdown ベースのブログを構築する」シリーズの一部です。


今回は Nuxt.js 側の実装について紹介する。

記事ページの実装

本ブログでは、記事ページの URL を /posts/YYYY-MM-DD/xxx-yyy-zzz/ というルールにしている。このような動的なルーティングを Nuxt.js で実装するには、

pages
└── posts/
    └── _date/
       └── _slug/
            └── index.vue

というディレクトリ構造にするだけで済む。_ で始まるディレクトリ名はパラメータとして扱われる。つまり、 _date_slug がそれぞれ YYYY-MM-DDxxx-yyy-zzz に該当する。

pages/posts/_date/_slug/index.vue の中身を見てみよう。

<template>
  <div>
    <h1>{{ title }}</h1>
    <div class="post-meta"><time>{{ params.date }}</time></div>
    <div v-html="bodyHtml"></div>
  </div>
</template>

<script>
import { sourceFileArray } from '../../../../content/posts/json/summary.json';

export default {
  validate({ params }) {
    return sourceFileArray.includes(`content/posts/${params.date}-${params.slug}.md`);
  },
  asyncData({ params }) {
    return Object.assign({}, require(`~/content/posts/json/${params.date}-${params.slug}.json`), { params });
  },
  head() {
    const title = `${this.title} - jmblog.jp`;
    const url = `https://jmblog.jp/posts/${this.params.date}/${this.params.slug}/`;
    return {
      title: title,
      meta: [
        { hid: 'og:url', property: 'og:url', content: url },
        { hid: 'og:title', property: 'og:title', content: title },
      ],
      link: [{ rel: 'canonical', href: url }],
    };
  },
};
</script>

<style>
@import 'assets/tomorrow-night-bright.css';
</style>

<style lang="scss" scoped>
.post-meta {
  font-size: 0.8em;
  color: #888;
  margin-top: -1rem;
  margin-bottom: 2.4rem;
  text-align: right;
}
</style>

Vue.js の単一ファイルコンポーネントになっていて <template><script><style> の 3 つで構成されている。

<template>

<template> の中はすごくシンプル。後述する <script> の asyncData メソッドで用意したデータ(タイトルと投稿日と Markdown から変換した HTML)を流し込んでいるだけ。

<script>

<script> がこのファイルのメインとなる。

まず validate メソッドで、アクセスされたページが実際に存在しているかをチェックしている。ここでは、前回の記事で紹介した summary.jsonsourceFileArrayフィールドに、対象の Markdown ファイルのパスが含まれているかどうかで、ページの存在を判定している。

ちなみに params という引数が渡ってきているが、ここには先ほど説明した pages/ 以下の _ で始まるディレクトリ名とその値がセットされている。

つまり、/pages/2018-01-01/new-post/ という URL にアクセスした場合、params の中身は

params = {
  date: '2018-01-01',
  slug: 'new-post',
};

のようになる。

asyncData メソッドでは、該当する記事ページの JSON ファイル(Markdown ファイルから変換したもの)を読み込んでいる。JSON オブジェクトだし、Markdown で書いた本文も HTML に変換済みなので、そのまま return して <template> に渡すだけで済む。

最後に head メソッドでは、<head> の中身(title とか description)をセットしている。

<style>

<style> で import しているのは highlight.js 用のテーマ CSS。ちなみに、以前私が作って、本家のリポジトリに取り込まれたファイルだったりする。

静的ファイルの書き出し

Nuxt.js では nuxt generate コマンドで HTML ファイルが生成することが、動的なルーティングは無視される。各記事ページの HTML ファイルも生成するには nuxt.config.js の generate オプションを使う必要がある。ここでも summary.json が活躍する。

const { sourceFileArray } = require('../content/posts/json/summary.json');
const sourceFileNameToUrl = require('./sourceFileNameToUrl');

const generateDynamicRoutes = callback => {
  const routes = sourceFileArray.map(sourceFileName => {
    return sourceFileNameToUrl(sourceFileName);
  });
  callback(null, routes);
};

module.exports = {
  //...
  generate: {
    routes: generateDynamicRoutes,
  },
  //...
};

これで dist ディレクトリには posts/2018-01-01/new-blog/index.html といったファイルが生成されるようになる。

sitemap.xml の生成

sitemap.xml@nuxtjs/sitemap を使えば簡単に生成できる。

npm か yarn でインストールしたあと、nuxt.config.js に追加する。

module.exports = {
  //...
  modules: ['@nuxtjs/sitemap'],
  sitemap: {
    path: '/sitemap.xml',
    hostname: 'https://jmblog.jp',
    generate: true,
    exclude: ['/404'],
    routes: generateDynamicRoutes,
  },
};

動的ルーティングはやはりデフォルトでは無視されるため、routes オプションに、上で説明した generate と同じものを渡す必要がある。

まとめ

というわけで、かなりお手軽に Nuxt.js で Markdown ベースのブログシステムを構築することができた。

Nuxt.js は公式ドキュメントが充実していてほとんど迷うことがなかったし、Vue.js の単一ファイルコンポーネントもかなり書き心地がよかった。(もう少し複雑な Web アプリケーションになると違ってくるかと思うが。)

Nuxt.js といえば、サーバーサイドレンダリングのためのフレームワークだという印象だったのだが、静的ファイルジェネレーターとしてもかなり使いみちが広いんじゃないかと今回感じた。機会があればまた使ってみたいと思う。

サイトパフォーマンス編 に続く。