schedule2020-12-12

nuxt/contentでブログを作る

nuxt/contentといういい感じにブログを作成できるモジュールがあったので使ってみる。 このブログもnuxt/contentを利用しています。

2021-04-24: 新しくブログ(Rustに入門してみた)を作ったのでその時の最新バージョンに更新しました。

Table of Content

nuxt/content

nuxt/contentNuxtjsアプリケーションを拡張するモジュールです。 GitベースのヘッドレスCMSとして機能します。 content/ディレクトリに書き込んだMarkdown、JSON、YAML、XML、CSVファイルといったファイルをMongoDBのAPIような機能で扱えるようになります。

特徴

  • 開発中の高速ホットリロード
  • Markdownの中でのVueコンポーネント
  • 全文検索
  • nuxt generateで静的サイト生成をサポートする
  • (MongoDBのような)強力なQueryBuilderAPI
  • PrismJSを使用してマークダウンファイルのコードブロックを強調表示する構文。
  • 目次の生成
  • マークダウン、CSV、YAML、JSON(5)、XMLを処理します
  • カスタムパーサーでの拡張(Latexや引用符など)

/contentに書いたマークダウンがそのままコンテンツになります。 ホットリロードも対応しているため、ローカルでマークダウンを書きながらすぐに内容とスタイルを確認できます。

また、マークダウンの中に書いた変数でフィルタリングしたりページの構成に使えるようになります。

/content/path/to/articles.md
---
id: 264
title: nuxt/contentでブログを作る
created_at: 2020-12-12
updated_at: 2020-12-17
isDraft: false
tags: Nuxtjs,Vuejs,TypeScript,JavaScript
top_image: /icons/nuxt.svg
icon: /icons/nuxt.svg
---

## 本文

咳をしても一人

DBを使っていないのにかなり自由度が高い。

Create a Blog with Nuxt Contentを参考に作ってみます。

環境

  • Node.js vv14.16.1
  • npm 6.14.12
  • yarn 1.22.5

構築後

  • Nuxt: ^2.14.6
  • @nuxt/content: ^1.11.1

Nuxt.jsのインストール

公式のインストールをみてプロジェクトを作るところから進める。

アプリを作成したいパスでyarn create nuxt-app <project-name>を実行する。 yarnで進めます。npmnpxは公式をみておくれ。

> yarn create nuxt-app myblog

。。。

create-nuxt-app v3.4.0
✨  Generating Nuxt.js project in myblog

# プロジェクト名
? Project name: myblog

# ここでTypeScriptを選べる
? Programming language: 
  JavaScript
> TypeScript

# パッケージマネージャ
? Package manager: (Use arrow keys)
> Yarn
  Npm

# UIフレームワーク
? UI framework:  
  None
  Ant Design Vue 
  Bootstrap Vue  
  Buefy
> Bulma
  Chakra UI      
  Element        
  Framevuerk     
  iView
  Tachyons       
  Tailwind CSS   
  Vuesax
  Vuetify.js

# ここで@nuxt/contentをインストール出来る
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Axios
 ( ) Progressive Web App (PWA)
 (*) Content

# リントツール
? Linting tools:      
 (*) ESLint
>(*) Prettier
 ( ) Lint staged files
 ( ) StyleLint        
 ( ) Commitlint  

# テストツール
? Testing framework: 
  None
> Jest
  AVA
  WebdriverIO 

# レンダリングモード
? Rendering mode: (Use arrow keys)
> Universal (SSR / SSG)
  Single Page App

# デプロイターゲット
? Deployment target:
  Server (Node.js hosting)
> Static (Static/JAMStack hosting) 

? Development tools:
>(*) jsconfig.json (Recommended for VS Code if you're not using typescript)
 ( ) Semantic Pull Requests
 ( ) Dependabot (For auto-updating dependencies, GitHub only)

# CIツール
? Continuous integration:      
  None
> GitHub Actions (GitHub only) 

? What is your GitHub username? (suzuroku)
? Version control system: (Use arrow keys)
> Git
  None

selectで選択する。 キーが効かないときはないときは日本語入力になってます。

プロジェクト作成時にTypeScriptの導入もしておくと楽です。 レンダリングモードとデプロイターゲットはnuxt.config.jstargetで後から変更できます。

作成したディレクトリに移動して、アプリを立ち上げます。

$ cd myblog
$ yarn dev

## 静的サイトを作成する

# dist/ に静的サイトのページが作る
$ yarn run generate

# dist/ のページを確認する
$ yarn run start

ページを追加する

/posts/{記事}となるようにページを追加します。 また、/posts/は一覧になるようにする。

├─content # マークダウンを置くところ。
|  ├─ about.md
│  └─posts
│     ├─post1.md
│     └─post2.md
├─pages # そのパスの処理
|  ├─_slug.vue
│  └─posts
│     ├─index.vue # /posts/ で一覧を表示する
│     └─_slug.vue # /posts/{_slug} パスを引数にとる

記事のマークダウン

/content/posts/articles.md
---
title: nuxt/contentでブログを作る
created_at: 2020-12-12
tags: Nuxtjs,Vuejs,TypeScript,JavaScript
top_image: /icons/nuxt.svg
icon: /icons/nuxt.svg
---

## 本文

咳をしても一人

記事を表示するVueページ

CSSフレームワークのbulmaを利用しています。

/pages/_slug.vue
&lt;template&gt;
  &lt;article class=&quot;article content section-article&quot;&gt;
    // 記事のヘッダー部分の例
    &lt;section class=&quot;header&quot;&gt;
      &lt;h1&gt;{{ post.title }}&lt;/h1&gt;
      &lt;div class=&quot;media&quot;&gt;
        &lt;div class=&quot;media-left&quot;&gt;
          &lt;figure class=&quot;image is-48x48&quot;&gt;
            &lt;img :src=&quot;post.top_image&quot; /&gt;
          &lt;/figure&gt;
        &lt;/div&gt;
        &lt;div class=&quot;media-content&quot;&gt;
          &lt;div class=&quot;tags&quot;&gt;
            &lt;i class=&quot;mdi mdi-tag&quot; /&gt;&amp;nbsp;
            &lt;span v-for=&quot;tag in post.tags.split(&#39;,&#39;)&quot; :key=&quot;tag&quot;&gt;
              &lt;a :href=&quot;&#39;/tags/&#39; + tag&quot; class=&quot;tag&quot;&gt;{{ tag }}&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class=&quot;media-right&quot;&gt;
          &lt;i class=&quot;mdi mdi-update&quot; /&gt;&amp;nbsp;
          &lt;time v-html=&quot;post.updated_at.split(&#39;T&#39;)[0]&quot;&gt;&lt;/time&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/section&gt;
    // 記事の本文
    &lt;NuxtContent :document=&quot;post&quot; /&gt;
  &lt;/article&gt;
&lt;/template&gt;

&lt;script lang=&quot;ts&quot;&gt;
import Vue from &#39;vue&#39;

export default Vue.extend({
  async asyncData({ $content, params }) {
    // URIから変数を取得 /posts/{_slug}
    const slug = params.slug
    return {
      // content/posts/{_slug}.mdのマークダウンを取得
      post: await $content(&#39;posts/&#39; + slug).fetch()
    }
  },
})
&lt;/script&gt;

TypeScriptのこの形になるまでかなり試行錯誤した。

コンテンツの取得とクエリ

@nuxt/contet コンテンツを取得するを参考に。

Contentはグローバルに contentインスタンスを注入するので、this.content` インスタンスを注入するので、`this.content `を使えばどこからでもアクセスできる。

// クエリのイメージ
list = {
  [{ title: 'article1', tags: 'Nuxtjs,ブログ'}],
  [{ title: 'article2', tags: 'Python'}],
  [{ title: 'article3', tags: '数式,KaTeX'}],
};

$content(list)
  .sortBy(title, asc)
  .limit(2)
// titleで昇順に2件取得する
$content(list)
  .only('tags')
  .where({ 'tags': { $contains: [ 'Nuxtjs' ] } })
// tagsに絞ってtagsの文字列に'Nuxtjs'を含むオブジェクトをフィルタリングする。

$contentのWhere句は内部的にtechfort/LokiJSを利用している。 クエリの詳細とたくさんの例が載っているので、ぜひ参照してください。

シンタックスハイライト

markdown.prism.theme

Google Analytics

@nuxt/google-analytics

yarn add --dev @nuxtjs/google-analytics
nuxt.config.js
googleAnalytics: {
    id: process.env.GOOGLE_ANALYTICS_ID
  },

GOOGLE_ANALYTICS_IDは環境変数。

Google Adsense

@nuxtjs/google-adsense

$ yarn add @nuxtjs/google-adsense
nuxt.config.js
modules: [
    ['@nuxtjs/google-adsense', {
      id: process.env.GOOGLE_ADSENSE_ID
    }]
 ]

GOOGLE_ADSENSE_IDは環境変数。

<template>
  <div>
    <adsbygoogle
    :ad-slot="'xxxxxxxx'"
    :ad-style="{ display: 'block' }"
    :ad-format="'auto'"
    />
  </div>
</template>

手動広告は、このようにしてページ内の広告を表示したい場所に書くことができる。 ad-slotは広告キーで、アドセンスで広告ユニットを作ると発行される。

テストしてみる

プロジェクト作成で追加したJestでユニットテストをします。

/test/Logo.spec.js
import { mount } from '@vue/test-utils'
import Logo from '@/components/Logo.vue'

describe('Logo', () => {
  test('is a Vue instance', () => {
    const wrapper = mount(Logo)
    expect(wrapper.vm).toBeTruthy()
  })
})
$ yarn test
yarn run v1.22.5
$ jest
 PASS  test/Logo.spec.js
  Logo
    √ is a Vue instance (15 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files  |       0 |      100 |     100 |       0 |
 index.vue |       0 |      100 |     100 |       0 | 29
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.69 s
Ran all test suites.
Done in 5.96s.

マークダウンの書式を拡張する

nuxt/contentに絵文字と数式のプラグインを追加するって記事に書きました。

サイトマップを作成する

nuxt/contentでサイトマップを作るに書いた。