NuxtでGitベースのヘッドレスCMS機能を実現するNuxt/Contentの紹介




# Nuxt/Contentって？

Nuxtでマークダウン形式のファイルをCMS風に使用するためのモジュールです。
一部では**ヘッドレスCMSいらなくなるんじゃね？**説まで出ているので、使ってみることにしました。

# インストール

Nuxtはあらかじめインストールしておいてください。
今回は公式ドキュメントにはないTypeScript での使用法を紹介したいと思います。

## 追記
2020/06/22　Nuxt-app v3.1.0より**Nuxt Contentが標準でサポート**されるようになりました😍🎉✨
Nuxt-appを使ったインストールをする際は、途中でContentを入れるか聞かれるので、そのときスペースキーで**○**を入れておいてください。

以下のインストール方法はあくまで後で入れるときにご利用ください。

## Nuxt/contentのインストール

```bash
yarn add @nuxt/content
```

次に`nuxt.config.js`の`modules`プロパティを以下のように追加

```javascript
  modules: [
    '@nuxt/content'
  ],
```



## nuxt-composition-api(おまけ)

これはNuxt/Contentを使用するだけなら不要です。ただ自分はComposition-apiを普段から使っていて、
もしもNuxtでComposition-apiを使うときは`@vue/composition-api`よりもこちらを使う方がオススメ
(今後のアップデートでは不要になるといいですね)

```bash
yarn add nuxt-composition-api
```

次に`nuxt.config.js`に以下を追加

```javascript
  buildModules: [
    'nuxt-composition-api'
  ]
```

ちなみに、自動的に`@vue/composition-api`が読み込まれるので、`nuxt-composition-api`からrefやreactiveが読み込めます。





# contentディレクトリを作る

ルートディレクトリに`content`ディレクトリを作ります(複数形ではありません)。

このディレクトリに入れた`.md`ファイルがマークダウンとして認識されます。
`.md`以外にも`.yaml` `.csv` `.json` `.json5`がパースされるそうです。

今回は後学のために、`content`の下にさらに`articles`ディレクトリを作っていますが、これは任意で、特にディレクトリ名は制限はないと思います。

```bash
content/
  articles/
    article-1.md
```

例えばこんな感じになると思います。

article-1.mdの中身は適当なマークダウンを書いてみましょう。
ちなみにコードブロックも書けます。PrismJS をラップしてハイライティングしているようです。

# pagesで表示する

ページコンポーネントで表示してみましょう。

`pages/index.vue`にて以下のように記述します。

```vue
<template>
  <article>
    <h1>{{ doc.title }}</h1>
    <nuxt-content :document="doc" />
  </article>
</template>

<script lang="ts">
import { ref, onMounted, useContext } from 'nuxt-composition-api'
export default {
  setup() {
    const doc = ref({})
    const ctx: any = useContext()
    onMounted(async () => {
      doc.value = await ctx.$content('articles/article-1').fetch()
    })

    return { doc }
  }
}
</script>
```

シンプルですね。ちゃんと表示されていればOKです。表示したい記事を`<nuxt-content>`タグに渡しています。

CMS的な使い方ですと、`_slag.vue`といもうのを作って、コンテキストから`params`経由でslag変数を取得すればブログのページっぽく書けると思います。

`useContext`はNuxtのコンテキスト情報を取得するもので、通常の`setup`の引数のコンテキスト変数とは異なります。
なぜ`ctx`がanyかというと、型情報が今の所ないようなので、anyにしておかないとESLintとかに怒られるからです。
また、なぜ新世代の`asyncData`に相当する `useAsync`を使わないかというと、基本このNuxt/ContentがSPA/MPAで運用されると予想されるので、`useAsync`は使えないと思い、`onMounted`フックで使用しています。

この辺り「こっちの方がいいのでは？」というやり方を知っていれば教えてくれると助かります。

# Vueコンポーネントをマークダウンに書く

マークダウン上でVueコンポーネントを使ってみることにします。
今回はただただカウントするだけのコンポーネントを作って置いてみましょう。

`~/components/TheCounter.vue`

```vue
<template>
  <div>
    {{ count }}
    <button @click="add()">ADD!</button>
  </div>
</template>
<script lang="ts">
import { ref, defineComponent } from 'nuxt-composition-api'
export default defineComponent({
  name: '',
  setup() {
    const count = ref(0)
    const add = () => count.value++
    return {
      count,
      add
    }
  }
})
</script>


```

次にマークダウン上にこのコンポーネントを書きます。
書くのですが、**必ずケバブケース & セルフクロージングタグではない書き方**にしてください。
具体的には以下のような形式で書いてください。

```markdown
OK!
<the-counter></the-counter>

NG! ケバブケースじゃない
<TheCounter></TheCounter>

NG! セルフクロージングタグになっている
<the-counter />
```

最後に、`<nuxt-content>`を表示するVueファイルにて、コンポーネントを登録しておきます。

```vue
<template>
  <article>
    <h1>{{ doc.title }}</h1>
    <nuxt-content :document="doc" />
  </article>
</template>

<script lang="ts">
import { ref, onMounted, useContext } from 'nuxt-composition-api'

import TheCounter from '~/components/TheCounter.vue'
export default {
  name: '',
  components: { TheCounter },
  setup() {
    const doc = ref('')
    const ctx: any = useContext()
    onMounted(async () => {
      doc.value = await ctx.$content('articles/article').fetch()
    })

    return { doc }
  }
}
</script>

```

これで表示されていればOKです。

# 終わりに

Vueコンポーネントをマークダウンに直接書けるというのは、なかなか画期的なのではないでしょうか？
その気になれば簡単な投票コンポーネントとか、メールフォームをどこにでも置けるという感じになると思います。

Nuxt/contentはまだ生まれたばかりで、ドキュメントもまだ少ないというのが現状です。
公式サイトで「acting as Git-based Headless CMS」(GitベースのヘッドレスCMSとして機能)とあるように、Nuxtで機能するヘッドレスCMS的な何かな使い方が可能です。

なんかヘッドレスCMSって覚えること多いなぁとか、Nuxt単体で色々やりたいなぁという方にはぜひオススメ。



# リンク

[公式サイト](https://content.nuxtjs.org/)