Nuxt 3 / Content で Markdown なブログ・お知らせサイト作る
昔書いた記事のアップデート版です。Nuxt 3 で Nuxt Content 周りも結構 API が変わったのでその辺が注目ポイントです。
環境
-
node -v
- v18.16.1
-
https://github.com/nuxt/nuxt
- v3.8.1
-
https://github.com/nuxt/content
- v2.9.0
作ってみよう
package.json と nuxt.config.ts と tsconfig.json
nuxi
コマンドは新しくできた nuxt の cli です。
{
...
"scripts": {
"dev": "nuxi dev",
"build": "nuxi build",
"start": "nuxi build && nuxi start",
"generate": "nuxi generate"
},
"dependencies": {
"@nuxt/content": "^2.9.0",
"nuxt": "^3.8.1"
}
}
nuxt をローカル実行すると .nuxt
ディレクトリができるので、そこにできる .nuxt/tsconfig.json
を extends します。
{
"extends": "./.nuxt/tsconfig.json"
}
defineNuxtConfig
で設定を定義します。head
などは app
の中になりました。
import { defineNuxtConfig } from "nuxt/config"
export default defineNuxtConfig({
app: {
head: {
title: process.env.npm_package_name || '',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
],
link: [
]
},
},
modules: [
"@nuxt/content",
]
})
ディレクトリ構造
これはほぼ前回のままですが、 static
が public
になりました。
また、_slug.vue
を [id].vue
としています。動的なパスは [...]
とするようになりました。
% tree . -N
.
├── content
│ └── news
│ ├── 20200526.md
│ └── 20200527.md
├── layouts
│ └── default.vue
├── node_modules
├── nuxt.config.ts
├── package-lock.json
├── package.json
├── tsconfig.json
├── pages
│ ├── index.vue
│ └── news
│ ├── [id].vue
│ └── index.vue
└── public
└── images
└── news
├── sample26.png
└── sample27.png
一覧に表示: content ディレクトリから md を拾ってくる
queryContent
で content
ディレクトリ配下のパスを取得します。
なお useAsyncData
はキーでキャッシュを作るため、別の個所で limit(3)
などでこの一覧を作成したいときは、 "news"
と指定しているところを変える必要があります。
<template>
<div>
<div v-for="n in news" :key="n._path">
<nuxt-link :to="n._path">{{n.title}} {{n.date}}</nuxt-link>
</div>
</div>
</template>
<script setup>
const { data: news } = await useAsyncData("news", () =>
queryContent("news").limit(15).find()
)
useHead({
title: "News",
})
</script>
詳細を表示: content ディレクトリから md を拾ってくる
useRoute()
と route.path
で現在のパスと同じ content
配下のコンテンツを queryContent
で拾いに行きます。
<ContentDoc>
で v-slot
した中身にもろもろ反映させることができます。
useHead
で <title>
や、指定すれば og:img
なども反映させることができます。
<template>
<article>
<ContentDoc v-slot="{ doc: news }">
<h1>{{news.title}}</h1>
<dl>
<dt>date</dt>
<dd>{{news.date}}</dd>
</dl>
<div><img :src="news.image" /></div>
<div v-for="tag in news.tags" :key="tag">{{tag}}</div>
<ContentRenderer :value="news" />
</ContentDoc>
</article>
</template>
<script setup>
const route = useRoute()
const news = await queryContent(route.path).findOne()
const meta = {
title: news.title,
}
useHead(meta)
</script>
実際に実行してみて、起動できるかチェック
% npm run dev
http://localhost:3000
で確認できます。(terminal に URL 出てきます)
生成もしてみる
% npm run generate
% npx serve .output/public
動的なコードも .output/public
に generate されます。
http://localhost:3000
で確認できます。(terminal に URL 出てきます)
上記サンプルソースコード
以前の Nuxt Content v1.0.0 だった頃のソースコードも残していますので見比べてみてくださいまし。
かんそう
- 変わったと思っていたがそこまで変わっていないかった
- 手作りホームページ も対応いたしました
Discussion