theme | background | title | info | class | highlighter | drawings | transition | mdc | lineNumbers | |
---|---|---|---|---|---|---|---|---|---|---|
seriph |
プログラマーもすなるVue.jsといふものを以てブログというものを作らんとて作るなり |
[UV Study : Vue\.js LT会 \- connpass](https://uniquevision.connpass.com/event/311383/)のスライドです。
|
text-center |
shiki |
|
slide-left |
true |
2024-04-23@UV Study : Vue.js LT会
Yuhei FUJITA
- 名前: Yuhei FUJITA(藤田 悠平)
- X(Twitter): @Yuhei_FUJITA
- GitHub: @YuheiFUJITA
- コミュニティ運営: Vue Fes Japan / VS Code Meetup / ChatGPT Community(JP) / フロントエンドカンファレンス北海道2024
- 趣味: 自然淘汰キャンプ / フィルムカメラ / SIGMA fp L買うよう背中を押された
layout: iframe-left url: https://011.vuejs.org/
var vm = new Vue({
/* options */
})
Directives can encapsulate arbitrary DOM manipulations. For example
v-attr
manipulates an element’s attributes,v-repeat
clones an element based on an Array,v-on
attaches event listeners… we will cover them later.
- Nuxt 3
- ここは安定のNuxt 3
- Nuxt 3のフルスタック開発楽しい
- Vuetify 3
- UIフレームワーク
- いろいろあったけどもうコンポーネント揃ってきた
- microCMS←ここで困った
- ヘッドレスCMS
- カテゴリー
日常(10)
みたいな感じで記事数を表示したい- できないことはない(API叩きまくることになる)
- タグ
Vue.js(10)
みたいな感じで記事数を表示したい- できないことはない(API叩きまくることになる)
- バージョン管理
- 有料プランにすればできる(4,900円〜/月)
これでやろうと思ったらカテゴリーやタグごとにAPI叩きまくることになる(技術的には可能)
安くても4,900円/月、ひぃ
layout: iframe-left url: https://content.nuxt.com/
- ファイルベースのCMS
- 記事をgit管理できる
- 様々なファイルフォーマットをサポート
- Markdown←記事
- YAML←カテゴリー・タグ
- CSV
- JSON
npx nuxi@latest module add content
```ts
export default defineNuxtConfig({
modules: [
],
})
```
```ts
export default defineNuxtConfig({
modules: [
'@nuxt/content' // `modules` にNuxt Contentを追加
],
})
```
```ts
export default defineNuxtConfig({
modules: [
'@nuxt/content' // `modules` にNuxt Contentを追加
],
content: {
// 追加で設定があればここに追加
}
})
```
```ts
export default defineNuxtConfig({
modules: [
'@nuxt/content' // `modules` にNuxt Contentを追加
],
content: {
highlight: {
theme: 'github-dark', // たとえばこんなの
},
}
})
```
```bash
/content
└articles # こうすると `articles/<slug>` で記事を取得できる
├── bar.md
├── foo.md
├── fuga.md
├── hoge.md
└── piyo.md
```
```bash
/content
└articles # ただし、これだとファイルがslug順になる
├── bar.md
├── foo.md
├── fuga.md
├── hoge.md
└── piyo.md
```
```bash
/content
└articles # 本当は記事の公開日順でこう並んでほしい
├── hoge.md
├── fuga.md
├── foo.md
├── bar.md
└── piyo.md
```
```bash
/content
└articles # こうする(日付部分は取得時は無視される)
├── 20240101.hoge.md
├── 20240102.fuga.md
├── 20240103.foo.md
├── 20240104.bar.md
└── 20240105.piyo.md
```
Content Directory - Nuxt Content
- id: category-1
name: カテゴリー1
- id: category-2
name: カテゴリー2
- id: category-3
name: カテゴリー3
JSON, YAML, CSV - Nuxt Content
/content
配下においたものがNuxt Contentの管理対象になる
/content
├── categories.yml # カテゴリー
├── tags.yml # タグ
└articles # 記事
├── 20240101.hoge.md
├── 20240102.fuga.md
├── 20240103.foo.md
├── 20240104.bar.md
└── 20240105.piyo.md
find()
記事一覧の取得
```ts
// `/content/articles` 以下の記事をすべて取得できる
const articles = await queryContent('articles').find()
```
```ts
// `limit()` で取得件数を制限できる
const articles = await queryContent('articles')
.limit(5)
.find()
```
```ts
// `skip()` で取得開始位置を指定できる
const articles = await queryContent('articles')
.skip(5)
.find()
```
```ts
// 1ページ10件、2ページ目を取得する場合はこうなる
const articles = await queryContent('articles')
.skip(10)
.limit(10)
.find()
```
/content
├── categories.yml # カテゴリー
├── tags.yml # タグ
└articles # 記事
├── 20240101.hoge.md
├── 20240102.fuga.md
├── 20240103.foo.md
├── 20240104.bar.md
└── 20240105.piyo.md
count()
記事の件数を取得
```ts
const count = await queryContent('articles').count()
```
```ts
const count = await queryContent('articles')
.where({ category: 'category-1' })
.count()
```
/content
├── categories.yml # カテゴリー
├── tags.yml # タグ
└articles # 記事
├── 20240101.hoge.md
├── 20240102.fuga.md
├── 20240103.foo.md
├── 20240104.bar.md
└── 20240105.piyo.md
findOne()
記事を1件だけ取得する
const article = await queryContent(
`articles/${slug}`
).findOne()
/content
├── categories.yml # カテゴリー
├── tags.yml # タグ
└articles # 記事
├── 20240101.hoge.md
├── 20240102.fuga.md
├── 20240103.foo.md
├── 20240104.bar.md
└── 20240105.piyo.md
// YAMLファイルになっても基本は同じ
const articles = await queryContent('categories')
.findOne();
/content
├── categories.yml # カテゴリー
├── tags.yml # タグ
└articles # 記事
├── 20240101.hoge.md
├── 20240102.fuga.md
├── 20240103.foo.md
├── 20240104.bar.md
└── 20240105.piyo.md
デフォルトで利用できるfrontmatter
key | type | default | description |
---|---|---|---|
title |
string |
最初の <h1> |
記事のタイトル |
description |
string |
最初の <p> |
記事の概要 |
draft |
boolean |
false |
下書きにできる(取得できなくなる) |
head |
object |
true | <head> の設定 |
frontmatterはデフォルトの値以外にも自由に追加できる
---
title: 記事のタイトル
category: category-id # 記事のカテゴリー情報を追加
tags: [tag-id-1, tag-id-2] # タグは複数つけられるので配列で指定
ogImage: /images/og-image.png # og:image に使う画像
publishedAt: 2024-04-23 # 記事の公開日(本当はcommitの日時を使いたい)
---
ただし、このままだとデフォルトの項目以外は型が効かない
const article = await queryContent(`articles/${slug}`).findOne();
// ここで型が効かない
これで型が効くようになる TypeScript Support - Nuxt Content
import type {
MarkdownParsedContent
} from '@nuxt/content/dist/runtime/types';
export interface Article extends MarkdownParsedContent {
// frontmatter に追加した項目を追加
category: string;
tags: string[];
ogImage: string;
publishedAt: string;
updatedAt?: string;
}
```ts
const articles = await queryContent('articles')
.skip(10)
.limit(10)
.find()
const article = await queryContent(
`articles/${slug}`
).findOne()
```
```ts
import type { Article } from '~/types/article';
const articles = await queryContent<Article>('articles')
.skip(10)
.limit(10)
.find()
const article = await queryContent<Article>(
`articles/${slug}`
).findOne()
```
<script lang="ts" setup>
// 重複してfetchしないように `useAsyncData()` でラップする
const { data: articles } = await useAsyncData(
'articles',
() => queryContent('articles').find(),
);
</script>
```vue
<script lang="ts" setup>
// 重複してfetchしないように `useAsyncData()` でラップする
const { data: articles } = await useAsyncData(
'articles',
() => queryContent('articles').find(),
);
</script>
```
```vue
<script lang="ts" setup>
// 重複してfetchしないように `useAsyncData()` でラップする
const { data: articles } = await useAsyncData(
'articles',
() => queryContent('articles')
// where() でフィルタリングできる
.where({ category: $route.params.category })
.find(),
);
</script>
```
```vue
<script lang="ts" setup>
// 重複してfetchしないように `useAsyncData()` でラップする
const { data: articles } = await useAsyncData(
'articles',
() => queryContent('articles')
// これでも同じ意味
.where({ category: { $eq: $route.params.category }})
.find(),
);
</script>
```
<script lang="ts" setup>
import type { Article } from '~/types/article';
const $route = useRoute();
const { data: article } = await useAsyncData(
'article',
() => queryContent<Article>($route.fullPath).findOne(),
)
</script>
<template>
<h1>{{ article?.title }}</h1>
<p>{{ article.category }}</p>
<ul>
<li v-for="tag in article.tags" :key="tag">{{ tag }}</li>
</ul>
<p>{{ article.description }}</p>
<p>{{ article.publishedAt }}</p>
<img :src="article.ogImage" alt="">
<!-- markdownをレンダリングしてくれる -->
<content-renderer :value="article" />
</template>
- Nuxt Contentを使うとファイルベースのCMSが簡単に作れる
- frontmatterを使って記事に付加情報を追加できる
queryContent()
を使えば複雑なクエリも実装できる- なにより型定義もしっかりできる