くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

@nuxt/contentでヘルプや使い方のページをサクッと作る

Nuxt Contentでブログを作ろう」など、
Nuxt公式ドキュメントとかでも紹介されてて気になってたNuxt Content

ブログ作らないから使う時ないな〜っと思ってたけど、
マークダウンでページを作れるんだったらヘルプページとか簡単にできるのでは?
と思い、いろいろ試してみたときの備忘録。

今開発しているSSSAPIのヘルプや規約系をこれで作ってるけど楽ちん。。(´ω`)

ヘルプページはこんな感じ(´ω`)

f:id:wannabe-jellyfish:20210824123806p:plain

@nuxt/contentの中身

@nuxt/contentの内部では、remarkとrehypeを使って、.mdファイルを変換しているよう。
remarkjs/remark
rehypejs/rehype

なので、設定はremarkやrehypeのプラグインの設定などを見る必要がある。

また、コードのシンタックスハイライトは、PrismJSを使っている。
PrismJS

なので、このあたりのCSSファイルも組み込まれる。

インストール

まずは、インストール

$ npm install @nuxt/content

nuxt.configの設定

インストールしたらmodulesに追加。
オプション系はcontent配下に書く感じ。

{
  modules: [
    '@nuxt/content'
  ],
  content: {
    // Options
  }
}

記事・コンテンツを用意する

表示したいマークダウンファイルを用意する。
配置場所は、contentディレクトリ配下。

content/
  help/
    sample1.md
    sample2.md
  index.md

記事の.mdファイルはこんな感じ。
冒頭にフロントマターを追加できて、追加情報を記載できる。

---
title: Introduction
description: Learn how to use @nuxt/content.
---

@nuxt/contentの使用方法を学びます。

より多くの仕切りを超えたコンテンツの全量。

コンテンツを作成する - Nuxt Content

.md内でVueコンポーネントも使える

.md内でVueコンポーネントも使えるので便利。
Buefy@nuxt/imageコンポーネントも使える(´ω`)

Vueコンポーネント|コンテンツを作成する - Nuxt Content

記事・コンテンツを表示する

記事を表示するのはこんな感じ。

  • $content().fetch()で記事を取得
  • <nuxt-content />で記事を表示
<template>
  <article>
    <!-- フロントマターのtitle -->
    <h1>{{ article.title }}</h1>
    <!-- markdown部分 -->
    <nuxt-content :document="article" />
  </article>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "nuxt-property-decorator";

@Component
export default class HelpPage extends Vue {
  private article: any;

  async asyncData({ $content }) {
    // content/index.mdを取得
    const article = await $content("index").fetch();
    return { article };
  }
}
</script>

記事の取得は直接パスを指定しているけど、検索などもできる。

コンテンツを取得する - Nuxt Content
コンテンツを表示する - Nuxt Content

カスタマイズや追加設定

GitHubMarkdownスタイルを適用する

@nuxt/contentにはスタイルがついてないので、
自分で用意しないといけない。

全部用意するのは大変なので、github-markdown-cssを使うようにする。

$ npm install github-markdown-css

インストールしたら、nuxt.configのcssに追加。

{  
  css: [
    '~/assets/css/buefy.scss', 
    // githubのスタイルはbuefyよりも後に指定する 
    'github-markdown-css'
  ]  
}  

最後に、スタイルが当たるように.markdown-bodyを追加。

<template>
  <article>
    <h1>{{ article.title }}</h1>
    <!-- class="markdown-body"を追加 -->
    <nuxt-content class="markdown-body" :document="article" />
  </article>
</template>

Buefy(Bulma) と github-markdown の共存 | 猫好きが猫以外のことも書く

目次(TOC)を表示する

目次の情報は自動的に生成されて、article.tocで参照できる。
中身はこんな感じ。

{
  "toc": [{
    "id": "welcome",
    "depth": 2,
    "text": "Welcome!"
  }]
}

これを使って、自分で目次部分を作成する感じ。

<template>
  <ul>
    <li
      v-for="link of article.toc"
      :key="link.id"
      :class="{ 'toc2': link.depth === 2, 'toc3': link.depth === 3 }"
    >
      <NuxtLink :to="`#${link.id}`">{{ link.text }}</NuxtLink>
    </li>
  </ul>
</template>

また、生成されるのはh2とh3のみで、構造化されていない

なので、depthをみてclassを変更し、
classを使ってインデントさせるなどが必要。

目次|コンテンツを作成する - Nuxt Content

見出しのリンクアイコンを変更する

@nuxt/contentはremark-autolink-headingsというremarkプラグインを使っているので、
見出しに自動でリンクが付いている。

ただ、Buefy(Bulma)を使っていたので、なんか微妙な感じに。。

f:id:wannabe-jellyfish:20210714172839p:plain

<!-- 「## 操作方法」の場合(デフォルト)-->
<h2 id="操作方法">
  <a href="#操作方法" aria-hidden="true" tabindex="-1">
    <!-- .icon .icon-link は設定してない。。-->
    <span class="icon icon-link"></span>
  </a>
  操作方法
</h2>

nuxt.configで設定すると、リンクの付け方やiconなどを変更できる。

{
  content: {
    markdown: {
      remarkPlugins: [
        [
          "remark-autolink-headings",
          // remark-autolink-headings"の設定
          {
            behavior: "append",
            content: {
              type: "element",
              tagName: "b-icon", // buefyのコンポーネントでもOK
              properties: {
                icon: "link-variant",
                size: "is-small",
                className: ["ml-1"]
              }
            }
          }
        ]
      ]
    }
  },
}

設定するとこんな感じに(´ω`)

f:id:wannabe-jellyfish:20210714172921p:plain

<!-- 「## 操作方法」の場合(設定変更後)-->
<h2 id="操作方法">
  操作方法
  <a href="#操作方法" aria-hidden="true" tabindex="-1">
    <span class="ml-1">
      <i class="mdi mdi-link-variant"></i>
    </span>
    </a>
</h2>

markdown.remarkPlugins|設定 - Nuxt Content
options|remarkjs/remark-autolink-headings

ページ内リンクに移動できるようにする

上記の設定で目次を作れるようになったけど、
ページ内リンクが動かなかったりする。

なので、ページ内リンクには、vue-scrolltoなどを設定する必要がある。
昔の記事はこちら。

www.memory-lovers.blog

#付きのURLのときに、該当箇所まで移動する

目次にリンクが付いてくれるけど、ハッシュ付きのURLに直接アクセスしても
該当箇所に移動してくれないので、以下のような感じの対応が必要。

  • マウント時にハッシュがあるかチェックして、
  • ハッシュがあればvue-scrolltoで該当箇所まで移動
<script lang="ts">
import { Component, Vue, Prop } from "nuxt-property-decorator";

@Component
export default class HelpPage extends Vue {
  // 略

  mounted() {
    if (!process.browser) return;

    this.$nextTick(() => {
      setTimeout(this.moveToHash, 200);
    });
  }

  private moveToHash() {
    const hash = this.$route.hash;
    if (hash && hash.match(/^#.+$/)) {
      this.$scrollTo(decodeURI(hash));
    }
  }
}
</script>

はまったところ

Bulmaとgithub-markdownが競合する

Buefyを使ってるけど、bulmaとgithub-markdownのクラス名が競合するらしく、
シンタックスハイライトとかが大変なことになる。。(´・ω・`)

f:id:wannabe-jellyfish:20210714172948p:plain

適宜、修正してするか、どちらかをやめるないといけない。。(´・ω・`)

見出しの最初に数字を使うとTOCのリンクが効かない

見出しからidの設定はremark-slugというremarkプラグインがやってくれていてる。

## 見出し

<h1 id="見出し">見出し</h1>

ただ、ほぼ自動変換なので、見出しが数字はじまりだと、
不正なIDになってしまい、リンクが有効にならない。。

## 1.見出し

<h1 id="1見出し">1.見出し</h1>

remark-slugにオプションなどもないので、見出しの最初に数字などは使えないっぽい。。

$content()の書き方によって、SPAで動かない時がある

こんな感じでヘルプ記事を用意していて、

content/
  help/
    sample1.md
    sample2.md
  index.md

以下のような、URLでアクセスできるように、

/help/sample1
/help/sample2

パラメタを使って、表示できるようにしていた。

<!-- /pages/help/_slug.vue -->

<script lang="ts">
import { Component, Vue, Prop } from "nuxt-property-decorator";

@Component
export default class HelpSlugPage extends Vue {
  private article: any;

  async asyncData({ params, error, $content }) {
    const slug = params.slug;
    if (!slug) return error({ statusCode: 404 });

    const article = await $content(`/help/${slug}`).fetch();

    if (!article) return error({ statusCode: 404 });
    return { article };
  }
}
</script>

開発中は動作するけど、SPA(ssr:false, target:static)でビルドすると、
.mdファイルが見つからないエラーに。。

$contentの指定の仕方を変えてみると、うまくいくようになった(´ω`)

const article = await $content("help", `${slug}`).fetch();

引数を複数与えることもできます。$content('article', params.slug)/articles/${params.slug}に変換されます。

ということらしいけど、挙動もちょっと違うのかもしれない。

$content(path, options?)| コンテンツを取得する - Nuxt Content

SSGではnuxt.configgenerate.routesの設定も

静的サイトを生成する場合、動的ルートがわからないので、
nuxt.cofiggenerate.routesで教えてあげる必要がある。

export default {
  modules: [,
    '@nuxt/content'
  ],
  generate: {
    async routes () {
      const { $content } = require('@nuxt/content')
      const files = await $content().only(['path']).fetch()

      return files.map(file => file.path === '/index' ? '/' : file.path)
    }
  }
}

generate.routesを設定しておくと、nuxt/sitemapでも有効になるので便利(´ω`)

静的サイト生成|発展的な機能 - Nuxt Content
routes|The generate Property - NuxtJS

以上!!

PR: SSSAPI

GoogleスプレッドシートAPI化するサービスを開発してます!
@nuxt/contentをゴリゴリ使ってるので、実際の動作などもみてみてください!
β期間中は最上位プランが無料なので、この機会にぜひぜひお試しください(´ω`)

■SSSAPI
https://sssapi.app

f:id:wannabe-jellyfish:20210824124009p:plain

参考にしたサイト様