Nuxt.jsでMarkdown記法のブログを作る
このブログはずっと LAMP 環境に WordPress を載せて運営してきたのですが,Markdown 記法を使ってオフラインで記事が書ける環境に少し憧れがありました(Jetpack for WordPress の機能で可能ではあるが).
最近使うことが多い Nuxt.js で作ろうかと思っても DB を用意したり markdownit 等で Markdown を変換したりと面倒に感じていましたが,もっと便利に扱える @nuxt/content というパッケージがなんだかとても良さげです.
ということで,@nuxt/content を使ってブログを一から作り直しました.
構成の概略
- @nuxt/content: DB 不要の記事管理
- Bulma: 軽量な CSS フレームワーク
- Firebase Hosting: SSG でビルドして公開
@nuxt/content は MD ファイルを ~/content
に入れるだけでコンテンツとして利用できます.また,MD ファイルの先頭に YAML 形式で情報を付加することができるので,カテゴリーやタグも自由に実装可能です.
CSS フレームワークには Bulma を採用することにしました.HP には BootstrapVue,最近作ったものではマテリアルデザインの Vuetify を使っているのですが,とっても重いです.Vue に対応していると記述が楽であるものの,CSS+JS なフレームワークなのでパッケージが大きくなっていて読み込みに時間がかかります.CSS フレームワークの中では Element UI の淡い感じも良さそうでしたが,レスポンシブではないので見送りました.
ブログのホスティングには Firebase Hosting を利用します(後述の理由により少し使っただけで閉じた).最初は Vercel を使おうとしたのですが,ビルドはエラーしないもののアクセス中に内部処理でエラーが出るらしく,よくわからずじまいです.HP は Vercel で公開しているので @nuxt/content が合わないのでしょうか…
@nuxt/content の簡単な説明
公式ページが日本語に対応しているのでインストール方法等は省略します(日本語ドキュメントは所々で一部削られているので,全文読みたい場合は英語のほうがよい).
記事の表示
Nuxt.js の便利な点の一つに,ルーティングの自動化があります.~/pages
以下のディレクトリ構造でルーティングが認識されるため,これを利用して記事ごとの URL を動的に張ることができます.
そもそもの Vue.js では動的ルーティングは _
で始まるファイル or ディレクトリにより実装できるため,例えば ~/content/articles
にファイル名を article-1.md
として記事を保存しているなら
<template>
<article>
<h1>{{ page.title }}</h1>
<nuxt-content :document="page" />
</article>
</template>
<script>
export default {
async asyncData ({ $content, params }) {
const page = await $content('articles', params.slug).fetch()
return {
page
}
}
}
</script>
というファイルを用意することで,http://blog.example.com/article-1
で記事にアクセスできるようになります.ファイル名ベースで URL を作り,ベージに遷移した際のパラメータを用いて記事の内容を取得するイメージです.
記事リストの表示
$content()
は第2引数があれば記事単体を,なければ記事のリストを取得します.例えば,新規作成日(投稿日)順に記事を 10 個とってきたリストは以下のように表示できます.
<template>
<ul>
<li v-for="article in articles" :key="article.slug">
<a :href="`/${article.slug}`">{{ article.title }}</a>
</li>
</ul>
</template>
<script>
export default {
async asyncData ({ $content }) {
const articles = await $content('articles').sortBy('createdAt', 'desc').limit(10).fetch()
return {
articles
}
}
}
</script>
リンクを張るときに slug
を使用すれば,MD ファイル名ベースでの URL が作られます.
$content()
をコンテンツをフェッチする際にはメソッドを駆使することで検索機能やソート機能などを付与することができます.
MDファイルの概要
article.title
のように取得されているのは,YAML で定義された内容 +α です.MD ファイルは以下のように書くことができます.
---
title: '@nuxt/contentでmd記法のブログを作る'
description: '@nuxt/contentというパッケージがなんだかとても良さげということで,これを使ってブログを一から作り直しました.'
author: mktia
---
このブログはずっと LAMP 環境に WordPress を載せて運営してきたのですが,Markdown 記法を使ってオフラインで記事が書ける環境に少し憧れがありました(Jetpack for WordPress の機能で可能ではあるが).
...
上部のハイフンで囲われた部分が YAML 記法に対応しています.上記のように書けば article
の属性として title
, description
, author
が取れるわけです.もちろん YAML 記法に則っていれば文字列以外にも配列等の型が利用できます.そのため,記事に複数のタグを付与するなどの柔軟な実装が可能です.
また,ここで定義していなくても自動で割り振られる属性があります.slug
はその一つで,ファイル名が入っています.それ以外にファイルの作成日 createdAt
やファイルの更新日 updatedAt
などもあります.これらの自動で割り振られる内容は YAML で上書きできます.
記事を呼び出したときに返ってくる内容の詳細はAPIエンドポイントを使用することで確認可能です.
目次の取得
article.toc
で目次を取得できます.目次は Markdown の見出しタグに従って生成されていて,h2とh3のみが反映されます.
項目の id
と文字列,深さが入ったオブジェクトが配列で返ってくるので,うまく処理すれば目次が作れます.
ハイライトシンタックス
ハイライトシンタックスでよく見かけるのは highlight.js ですが,@nuxt/content では Prism.js が使用されています.
Markdown における記法はほぼ同じですが,対応している言語やその指定語が若干異なります.
注意点
asyncDataによる実装
asyncData
は非同期でデータを取得しますが,取得し終えるまではページが表示されない仕組みになっています.この関数は pages
以下でしか呼ぶことができないため,例えばカテゴリー一覧を表示するコンポーネントを $content
を使って作成して呼ぶみたいなことはできません(データをコンポーネントに投げるのは可能).
SSGでは使えないパッケージ
@nuxtjs/sitemap や @nuxtjs/feed など @nuxt/content と相性が良さそうなパッケージはいくつかありますが,SSG でビルドする場合は $content
が呼べずに失敗するので使えないことがあります.
Lighthouseによる評価
公開まで1~2週間はかかりましたが,ようやく完成して(このブログは)WordPress 卒業ということでこれまでと比べて Lighthouse 的にはスコアが上昇したのか,トップページのスコアで比較してみました.
まず,WordPress の頃のスコアがこちら.
PC
84
Performance
98
Accessibility
86
Best Practices
83
SEO
0-49
50-89
90-100
Reported by Lighthouse
Mobile
67
Performance
98
Accessibility
86
Best Practices
85
SEO
0-49
50-89
90-100
Reported by Lighthouse
KUSANAGI サーバとかではないので WordPress は少し重かったのですが,まあぼちぼちという感じです.
で,Firebase Hosting にしたスコアがこちら.
PC
95
Performance
93
Accessibility
93
Best Practices
100
SEO
0-49
50-89
90-100
Reported by Lighthouse
Mobile
71
Performance
92
Accessibility
93
Best Practices
99
SEO
0-49
50-89
90-100
Reported by Lighthouse
概ね上昇しました.
ただ,Firebase Hosting は無料枠が転送量上限 10GB /月ですが,このブログは WordPress で約 1GB /日,Nuxt.js で少し軽くなったもののそれでも約 500~600MB /日なので,課金必須になります(いっても安いけど).
それとは別に GMO のホスティングサーバを借りているのでそれでいいかと思って Firebase Hosting から引き上げて CORESERVER に移しました.
PC
100
Performance
91
Accessibility
93
Best Practices
100
SEO
0-49
50-89
90-100
Reported by Lighthouse
Mobile
64
Performance
92
Accessibility
93
Best Practices
99
SEO
0-49
50-89
90-100
Reported by Lighthouse
モバイル向けスコア,やはり厳しいですね.
トップページで記事一覧だけでなくカテゴリー一覧やタグ一覧を取得している上,Google Analytics と AdSense が他に比べてめちゃめちゃ重いのでどうしようもありません.
最後に
オンラインで記事を書くと,WP を開くまでの手間とかブラウザのページ遷移とかプレビュー生成とか至るところで時間をとられてめんどくさくなってしまうのですが,オフラインだとラグもないですし VS Code の MD プレビューですぐに確認できるのでとても良いです.
作業ログとかをどこでも確認できるようにオンラインにしようとこのブログを作ったので,書くのがめんどくさくなってしまったら元も子もない…
(余談ですが,上の Lighthouse のスコアを表示するためのコンポーネントを作るのがめちゃ面倒でした.)