Vue.jsのフレームワークであるNuxtには便利なプラグインがたくさんあります。静的サイトを作成するNuxt Contentはその中のひとつとしてよく用いられています。
読者の中には「NuxtってVue.jsのフレームワークでしょ? 私はReact派だし興味ないかな…。」という人も多くいると思います。
しかし、Nuxtの強みとして、コミュニティ主導で質の高いモジュールが管理されていることが挙げられます。NuxtコミュニティではNuxt Modulesという名前でライブラリが管理、提供されています。Nuxt Contentもそのひとつです。
Nuxt Modulesに追加されるライブラリについてはコミュニティ内で実装ガイドラインが定められており、Nuxtチームのレビューを受けて追加されるため品質が担保されています。また、Nuxtで使いやすい形になっているため導入が容易で最小限のカスタマイズで使えるのが特徴です。
▼Nuxt Modules
本記事ではNuxt Contentを通じたサイト制作手法を紹介します。Nuxt Contentを用いてマークダウンからブログサイトを構築します。
Nuxt Contentの特徴
Nuxt Contentはマークダウンファイルで管理されるコンテンツを元に、コンテンツの一覧表示、検索機能、リンクの作成、ページネーションなどを提供するプラグインです。特徴として導入、サイト構築の容易さが挙げられます。また、Vueコンポーネントをマークダウンに埋め込むことも可能です。
公式サイトもNuxt Contentで作られているようです。ドキュメントのページもそれぞれマークダウンで作成されており、大量のコンテンツを扱うのに適したライブラリであることが伺えます。
▼ドキュメントページ
コンテンツをマークダウンファイルで管理することは、次のようなメリットがあります。
- バージョン管理が容易
- テキストエディターでの編集が可能
- メタデータの追加などが柔軟に行える
- ローカル環境でも確認できる
ICS MEDIAもマークダウンで原稿を管理しています。以下の記事でもそのことを紹介しているので、読んでみてください。
今回はデモとしてブログサイトの作成を通じ、Nuxt Contentの使い方を紹介します。
ブログサイトの作成
今回作成したデモサイトです。
ブログサイトとしての最低限の機能を用意しました。
- ページリンクの作成
- コンテンツの一覧表示
- コンテンツの詳細表示
- タグ検索機能
それぞれの機能と実装について紹介します。
Nuxt Contentの導入
公式サイトの手順に従い、Nuxt Contentを導入します。
既存のNuxtプロジェクトがない場合
既存のNuxtプロジェクトがない場合は、以下のコマンドをターミナルで実行し、Nuxt Contentが組み込まれたNuxtプロジェクトを作成します。
npx nuxi@latest init content-app -t content
すでにNuxtプロジェクトがある場合
すでにあるNuxtプロジェクトにNuxt Contentを導入する場合は以下のコマンドを実行します。
npx nuxi@latest module add content
nuxi
コマンドはNuxt CLIを用いてNuxt Modulesの導入を行う便利なコマンドです。このコマンドで導入したモジュールはnuxt.config
に自動的に追加されます。
プロジェクトの作成が完了したら、npm run dev
コマンドを実行してhttp://localhost:3000
にアクセスします。ページが表示されればOKです。
実際にコンテンツを作成しながらNuxt Contentの使い方を紹介します。
コンテンツの作成
Nuxt ContentはNuxt本体のルーティングシステムと同様ディレクトリベースでコンテンツを管理するライブラリです。コンテンツで主に使われるのはマークダウンファイルですが、JSONやYAMLなどもサポートしています。
独自のディレクトリ名やマークダウン記法によって、Nuxt Content内で扱いやすいデータにできます。
ディレクトリ設計
プロジェクト作成後に http://localhost:3000
や http://localhost:3000/about
で表示される内容はプロジェクト直下のcontent
ディレクトリのマークダウンファイルの index.md
や about.md
がHTMLに変換されたものです。
一例として以下のようなディレクトリとパスの対応を記載します。content
ディレクトリにマークダウンファイルを作成するとURLに対応したマークダウンファイルがHTMLとして表示されます。以下の例では、ファイル名とURL(かっこ内のパス)の対応付けを示しています。
content/
1.index.md(`/`)
2.blog/
index.md(`/blog`)
0401.md(`/blog/0401`)
0402.md(`/blog/0402`)
3.about/
1.index.md(`/about`)
2.history.md(`/about/history`)
3.access.md(`/about/access`)
コンテンツの並び順
Nuxt Contentではファイル名やディレクトリ名の接頭辞に数値を設定すると、コンテンツの並び順を指定できます。これは指定した順番でコンテンツを並べるのに便利です。URL化の際は、数字の部分は省略されます。実際の使い方は後述します。
フロントマター
マークダウンファイルにメタデータを記述する方法として、フロントマターがあります。フロントマターはマークダウンファイルの先頭に記述され、コンテンツのタイトルや説明、公開日などの情報を記述します。フロントマターはページには表示されません。
▼フロントマターの例
---
title: ページのタイトル
description: ページの説明
draft: false
navigation: true
date: 2024-11-08
tag:
- Nuxt
- 初心者
---
<!-- Content of the page -->
フロントマターはNuxt Contentで組み込まれているqueryContent()
関数で取得できます。自由にメタデータを追加できるため、サイトの機能に応じた拡張が可能です。
以下のフィールドはNuxt Contentの便利なAPIと組み合わせが可能な予約語になっています。たとえば、draft
属性がtrue
の場合は、開発モードでのみ表示されます。
title
description
draft
navigation
head
静的ファイルについて
Nuxt Contentのマークダウン内で使用するコンテンツはpubilc
ディレクトリに配置する点に注意しましょう。
content/
index.md
public/
img/
image.png
nuxt.config.ts
package.json
tsconfig.json
マークダウンの中で画像を使用する際は以下のように記述します。
![alt文言](/img/image.png)
GitHubやVisual Studio Codeのマークダウンプレビューでは画像がプレビューできないので、Nuxtアプリケーションを起動して確認しましょう。
コンテンツの表示
基本的なコンテンツの作り方を学習したところで、コンテンツを表示する方法について紹介します。
マークダウンを取得して表示する方法は、大きく分けると以下の2つの方法があります。
- 関数(Composables)で取得する
- コンポーネントの
slot
プロパティで取得する
どちらも長所と短所はありますが、今回はより簡単に書けるコンポーネントを使用した方法で説明します。
ContentDocコンポーネント
ContentDoc
コンポーネントは、現在のページに対応したコンテンツを表示するコンポーネントです。たとえば以下のようにpages/index.vue
にContentDoc
コンポーネントを記述すると、content/index.md
の内容が表示されます。
▼pages/index.vue
<template>
<main>
<ContentDoc />
</main>
</template>
▼content/index.md
---
title: Nuxt Contentについて
description: Nuxt Contentは面白い!
date: 2024-11-16
tag:
---
# タイトル
こんにちは!
▼結果イメージ
動的なパスではコンテンツがないページのフォールバックが必要です。たとえば content/blog/12345.md
が存在しないのに /blog/12345
というURLにアクセスした場合、表示するコンテンツがありません。このようなときは以下のように#not-found
スロットを使用してフォールバックを表示できます。
<ContentDoc>
<template v-slot="{ doc }">
<article>
<h1>{{ doc.title }}</h1>
<ContentRenderer :value="doc" />
</article>
</template>
<!-- ⭐️フォールバックを表示 -->
<template #not-found>
<h1>存在しないページです</h1>
</template>
</ContentDoc>
ナビゲーションの作成
ヘッダーなど、コンテンツのリンクはContentNavigation
コンポーネントで作成できます。
ContentNavigation
コンポーネントは、navigation
オブジェクトを受け取り表示するコンポーネントです。navigation
オブジェクトはコンテンツ内のすべてのパスのツリー構造をもつオブジェクトです。
次で示すコンポーネントは、/content
直下のファイルの一覧をリンクとして表示します。
<template>
<nav>
<ContentNavigation v-slot="{ navigation }">
<ul>
<li v-for="link of navigation" :key="link._path">
<NuxtLink :to="link._path">{{ link.title }}</NuxtLink>
</li>
</ul>
</ContentNavigation>
</nav>
</template>
ここで先ほど紹介した、マークダウンファイルの接頭辞の数字が重要になってきます。接頭辞をつけることで、ソート処理などを実装しなくても期待した順番で表示してくれます。
navigation
オブジェクトは次のようにパスがネストしたオブジェクトです。必要な形式に加工して使用できるため、柔軟性が高いのが特徴です。また、クエリパラメーターを渡すと絞り込んだ結果を表示してくれます。クエリパラメーターについては後述します。
[
{title: "Top", _path: "/"},
{title: "Blog", _path: "/blog",
children: [
{title: "0401", _path: "/blog/0401"},
{title: "0402", _path: "/blog/0402"},
]
},
]
コンテンツの一覧を表示
コンテンツの一覧表示にはContentList
コンポーネントを使用します。ContentList
コンポーネントはlist
スロットを受け取り内部のコンポーネントで記事の一覧情報をもつ配列を使用できます。
また、コンテンツの一覧を使用する際は絞り込んだリストを使用したい場合がほとんどです。ContentList
のpath
属性を指定した場合、content
ディレクトリ内で指定したパス以下のコンテンツのみを受け取ります。
▼content/blog内の記事一覧を表示する例
<template>
<ContentList v-slot="{ list }" path="/blog">
<ul>
<li v-for="article in list" :key="article._path">
<NuxtLink :to="article._path">{{ article.title }}</NuxtLink>
<span class="date">{{ article.date }}</span>
</li>
</ul>
</ContentList>
</template>
▼結果イメージ
コンテンツクエリー
クエリーは、コンテンツの取得時に次のような検索条件を適用するために用いられます。
- 絞り込み
- 並び替え
- 取得上限
- 特定のフィールドだけを取得
また、取得できるデータも以下のように柔軟です。
- 対象のコンテンツをすべて取得
- 対象のコンテンツのうち、最初にヒットしたものを取得
- 対象のコンテンツの前後のコンテンツを取得
- ヒットしたコンテンツの数を取得
クエリーはqueryContent()
関数を用いて作成するか、QueryBuilderParams
型のオブジェクトをコンポーネントに渡して使用します。
以下に示すのは、タグ検索機能を実装する例です。現在のパスから検索対象のタグ名を取得し、tag属性がそのタグを含むコンテンツだけを取得しています。また、日付の降順で記事が並ぶようソートしています。
▼pages/blog/tag/[tag].vue
<script setup lang="ts">
const route = useRoute();
const query: QueryBuilderParams = {
where: {
tag: {
$contains: route.params.tag,
},
},
sort: { date: -1 },
};
</script>
<template>
<ContentList v-slot="{ list }" path="/blog" :query="query">
<ul>
<li v-for="article in list" :key="article._path">
<NuxtLink :to="article._path">{{ article.title }}</NuxtLink>
<span class="date">{{ article.date }}</span>
</li>
</ul>
</ContentList>
</template>
クエリーの記法は少し難しいですが、習得すれば非常に便利です。柔軟性が高い上に、複雑なコードでデータを変換する必要がありません。コードの量も減ってバグも起きにくい、魅力的な仕組みです。詳しい記法については 公式ドキュメントを参照ください。
Vueコンポーネントをマークダウンで使用する
ここでは標準のマークダウンでは使用しない、Nuxt Content独自の記法を紹介します。
通常、マークダウンの内容にフロントエンドの技術を持ち込むことはできません。しかし、Nuxt Contentは作成したVueコンポーネントをマークダウン内で使用し、表示できます。
以下はBoxComponent
というコンポーネントを作成し、マークダウンの中で使用する例です。コンポーネントを使用する際は、::
で囲みます。propsを渡すこともできます。
::BoxComponent
Vueコンポーネントで作成しています
::
::BoxComponent{color="red"}
propsを渡すこともできます
::
BoxComponent
は以下のように実装しました。マークダウン内でslotとして使用する箇所についてはslot
を使う方法とContentSlot
を使う方法があります。前者は受け取ったものを単に文字列として描画しますが、後者はコンポーネントをネストして使えたりと高機能です。
▼components/content/BoxComponent.vue
<script setup lang="ts">
const props = defineProps<{ color?: string }>();
</script>
<template>
<div>
<div class="box" :style="{ color: props.color, borderColor: props.color }">
<ContentSlot :use="$slots.default" unwrap="p" />
</div>
</div>
</template>
<style scoped>
.box {
margin: 8px 0;
display: inline-block;
padding: 4px 8px;
border: 1px solid #000;
}
</style>
また、マークダウンに埋め込んだコンポーネントではスクリプトも実行されます。たとえば、カウンターのように値の保持、更新を行うコンポーネントも使用できるということです。
描画結果をまとめると以下のようになります。
- サンプルを別ウインドウで開く
- ソースコードを確認する(241117.mdを開いてください)
独自のコンポーネントを使用できるのは、他のマークダウンツールにはない魅力です。
まとめ
マークダウンをベースにコンテンツサイトを簡単に作成できるNuxt Contentについて紹介しました。マークダウンとして記事が管理できる点、マークダウンのファイル名や階層、メタデータを使ってコンテンツの表示を制御できるのはとても魅力的なライブラリだと感じています。
一方で、読者の多くはこう思ったのではないのでしょうか。
「マークダウンは記法を覚えたりプレビューしたりするのが大変だし、エンジニアじゃなくても使えるように編集画面がほしいな…」
そんなあなたへ次回予告です。次回の記事ではNuxt Studioを紹介します。Nuxt StudioはNuxt ContentをGitベースのCMSとして活用する強力な編集ツールです。
画面上でのマークダウンの編集はもちろん、リアルタイムなプレビュー、共同編集やドラフト機能、アセットの管理やNuxtで作成した独自コンポーネントの挿入まで画面上で行えます。
▼操作の様子
次回の記事ではNuxt ContentとNuxt Studioの連携方法やNuxt Stuioの魅力について紹介します。お楽しみに!