LoginSignup
7
2

More than 3 years have passed since last update.

@nuxt/content ✖︎ CompositionAPI で簡素なブログページを作成

Last updated at Posted at 2020-07-14

Prologue

これまでアウトプットの下書きは Vuepress に書きためていたのですが、nuxt/content はコードスニペットもついているしComponent内に埋め込むこともできる、という自由度が気になって基本的な部分を触ってみました。
動作確認を目的としているため、nuxt-app で組み込まれるデフォルトのコードベースにいじっているのでその点もご了承ください。

基本はDocument通りになるため、自身で設定する場合には都度公式を確認することをお勧めします。

Nuxt.js の初期設定周りは別記事にまとめているので、そちらも参考にしてください。

環境

  • macOS: v10.15.5
  • node.js: v12.18.2
  • terminal: iTerm
  • エディタ: VS Code
  • パッケージマネージャ: yarn
  • Composition API: v1.0.0-beta.3

create app

プロジェクトを作成します。プロジェクト名は今回は content-sample としました。

mkdir content-sample
yarn create nuxt-app content-sample

create-nuxt-app v3.1.0
✨  Generating Nuxt.js project in content-sample
? Project name: content-sample
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: Buefy
? Nuxt.js modules: Content
? Linting tools: Prettier
? Testing framework: Jest
? Rendering mode: Single Page App
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert
selection)

install

create nuxt-app v3.0.0 で @nuxt/content が選択できるようになったようです。
今回は新規にプロジェクトを作っているため、それを選択しました。上記の Nuxt.js modules: Content がその項目です。
参考: https://github.com/nuxt/create-nuxt-app/releases

content/ ディレクトリも自動で生成されますが、既存のプロジェクトに追加する際には以下の手順で行ってください。

  1. @nuxt/content をインストール
yarn add @nuxt/content

2. nuxt.config.js に以下を追加

modules:[
    '@nuxt/content'
]

3. tsconfig.json に以下を追加

"types: [
    "@nuxt/content"
]

マークダウンを表示する

今回は content/hello.md が予め用意されていたので、その md ファイルを利用して表示ができるかをまず確認します。
※ UI Framework に Buefy を選択しているので、既存のコードベースに進めていきます。

既存の pages/index.vue を以下のように修正します。
参考: https://content.nuxtjs.org/ja/fetching

<template>
  <section class="section">
   // 略
    <nuxt-content :document="doc" />
  </section>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from '@vue/composition-api'
import Card from '@/components/Card.vue'

export default defineComponent({
  name: 'HomePage',
  components: {
    Card,
  },
  setup(props, {root}) {
    const doc = ref<Object>({})
    // @ts-ignore
    const ctx = root.context
    onMounted(async () => {
      doc.value = await ctx.$content('hello').fetch();
    })

    return { 
      doc
    }
  },
})
</script>

コードの説明

グローバルにアクセスできる this.$content を利用して、コンテンツを取得します。

  • $content(path, options?)
    path にはファイルかディレクトリを指定できるので、今回は hello.mdhello を指定し、fetch() をつなげて Promise を返してもらいます。
    path が ファイルなら Object, ディレクトリなら Array のため、今回は返り値を格納する変数 doc を Object型で作成しました。

  • <nuxt-content> component
    Markdown コンテンツの body を表示するために必ず使用します。props は document です。

hello.md の内容が表示されたらOKです。

param 付きのページを作成

articles/article_1 という構成のページを作成して、BASE_URL/articles/article_1 で表示ができることを目指します。

  1. content/articles/article_1 を作成
## date: 2020-07-12

I get a new PC!!

2. pages/articles/_slug.vue を作成

<template>
  <section class="section">
    <nuxt-content :document="article" />
  </section>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from '@vue/composition-api';
import Card from '@/components/Card.vue';

export default defineComponent({
  name: 'HomePage',
  components: {
    Card,
  },
  setup(props, context) {
    const article = ref<Object>({});

    // @ts-ignore
    const ctx = context.root.context;
    onMounted(async () => {
      article.value = await ctx.$content('articles', context.root.$route.params.slug).fetch();
    })
    return {
      article
    }
  }
});
</script>

コードの説明

$content('article', params.slug) の形式で /article/${params.slug} になるとのことなので、
pages に同じroute構成になるようディレクトリとファイルを作成し、表示できるようコードを作成しました。

YAML フロントマターブロックのタイトルを、ページタイトルとして取得、表示

  1. content/articles/article_1 にYAML形式でタイトルを追加
---
title: shopping
---

## date: 2020-07-12

I get a new PC!!

2. pages/articles/_slug.vue も修正

<template>
  <section class="section">
    {{titleName}}
    <nuxt-content :document="article" />
  </section>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from '@vue/composition-api';
import Card from '@/components/Card.vue';

export default defineComponent({
  name: 'HomePage',
  components: {
    Card,
  },
  setup(props, context) {
    const article = ref<Object>({});
    const titleName = ref<string>('');

    // @ts-ignore
    const ctx = context.root.context;
    onMounted(async () => {
      article.value = await ctx.$content('articles', context.root.$route.params.slug).fetch();
      const { title } = await ctx.$content('articles', context.root.$route.params.slug).only(['title']).fetch();
        titleName.value = title;
    })
    return {
      article,
      titleName
    }
  },
});
</script>

コードの説明

const {title} 以下で title のパラメータを取得し、それを表示用の変数 titleName に格納しました。
分割代入をしているので、const {title} に使う変数名はキーと揃えています。
今回は動作確認のためにこういう書き方をしましたが、先に取得した article.value.title からも取得することができます。

参考: 分割代入 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

ページを増やす

content/articles/ 下に article_2.md, article_3.md ... と増やしていき、URLを変えていくとその内容が表示されます。
一つレイアウトを作っておけば記事が挿し変わるので、特に実装の変更はありません。

表紙を作成

pages/articles/index.vue を作成し、articles 下のMDファイルを全て表示します。

<template>
  <section class="section">
    <div v-for="(article, index) in articles" :key="index" class="article-content">
      <a :href="article.path">
        <h2>{{article.title}}</h2>
      </a>
      <nuxt-content :document="article" />
      <hr />
    </div>
  </section>
</template>

<script lang="ts">
import { defineComponent, onMounted, ref } from '@vue/composition-api';
import Card from '@/components/Card.vue';
import { Context } from '@nuxt/types';

export default defineComponent({
  name: 'Articles',
  components: {
    Card,
  },
  setup(props, context) {
    const articles = ref<Array<any>>([]);
    // @ts-ignore
    const ctx = context.root.context;
    onMounted(async () => {
      articles.value = await ctx.$content('articles').fetch();
    })
    return {
      articles
    }
  },
});
</script>

<style lang="scss">
.article-content:not(:last-child) {
  margin-bottom: 2rem;
}
</style>

  • コードの説明

$content の引数にディレクトリ名を渡して、配列で取得し、それを for で回します。
タイトルは各オブジェクト内にあるので(key: title)それも合わせて表示し、さらにオブジェクト内に path があるのでリンクを貼ります。

目次を作成

目次は自動で作られる、とドキュメントにありましたが、自動...どこに...
見つけられなかったのとプルダウン形式で表示させたかったため、自力で実装しました。
コードは割愛しますが、表紙を作成した際と同じやり方で、pathtitle を取得しそれをループで表示させます。

以上です。
記事の目次、本文を割と簡単に実装できました。この他にも query 検索や、 tag で記事をカテゴリ別に作成したり、などもできるようなので次はそれを実装してみようと思います。

Epilogue

今回詰まったところは $content の取得と param付きページの表示です。CompositionAPIを使おうと思っていたので、表示ができなかった際にどちらが原因なのか、問題の切り分けに時間がかかりました。
SSRでの実装もやっていみたいので、それも時間があればまた記事にします。

表記に関してなど何かありましたらご連絡ください。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2