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/
ディレクトリも自動で生成されますが、既存のプロジェクトに追加する際には以下の手順で行ってください。
-
@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.md
のhello
を指定し、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
で表示ができることを目指します。
-
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 フロントマターブロックのタイトルを、ページタイトルとして取得、表示
-
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
があるのでリンクを貼ります。
目次を作成
目次は自動で作られる、とドキュメントにありましたが、自動...どこに...
見つけられなかったのとプルダウン形式で表示させたかったため、自力で実装しました。
コードは割愛しますが、表紙を作成した際と同じやり方で、path
と title
を取得しそれをループで表示させます。
以上です。
記事の目次、本文を割と簡単に実装できました。この他にも query 検索や、 tag で記事をカテゴリ別に作成したり、などもできるようなので次はそれを実装してみようと思います。
Epilogue
今回詰まったところは $content
の取得と param付きページの表示です。CompositionAPIを使おうと思っていたので、表示ができなかった際にどちらが原因なのか、問題の切り分けに時間がかかりました。
SSRでの実装もやっていみたいので、それも時間があればまた記事にします。
表記に関してなど何かありましたらご連絡ください。