Skip to content

Latest commit

 

History

History
699 lines (545 loc) · 15.5 KB

slides.md

File metadata and controls

699 lines (545 loc) · 15.5 KB
theme background title info class highlighter drawings transition mdc lineNumbers
seriph
プログラマーもすなるVue.jsといふものを以てブログというものを作らんとて作るなり
text-center
shiki
persist
slide-left
true

プログラマーもすなる
Vue.jsといふものを以て
ブログというものを作らんとて
作るなり

2024-04-23@UV Study : Vue.js LT会

Yuhei FUJITA


ふじた。ひらがなみっつでふじた。
よぶときはふじたさん


layout: statement

ちょっと余談




layout: statement

「私はVue 2(近代文)の
ことを古文と言いました」


layout: iframe-left url: https://011.vuejs.org/

書き方に歴史を感じる

var や…

var vm = new Vue({
  /* options */ 
})

v-repeat や…

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.


layout: statement

ブログを作りたい


最初に検討した構成

  • 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/

Nuxt Content

  • ファイルベースのCMS
    • 記事をgit管理できる
  • 様々なファイルフォーマットをサポート
    • Markdown←記事
    • YAML←カテゴリー・タグ
    • CSV
    • JSON

セットアップ

Nuxt Contentを追加

npx nuxi@latest module add content

nuxt.config.ts に設定を追加

```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', // たとえばこんなの
    },
  }
})
```

Installation - Nuxt Content


layout: statement

記事をMarkdownで書く


ファイル構成

```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


カテゴリーやタグ情報はYAMLで定義しておく

カテゴリー
/content/categories.yml

- id: category-1
  name: カテゴリー1
- id: category-2
  name: カテゴリー2
- id: category-3
  name: カテゴリー3

タグ
/content/tags.yml

- id: tag-1
  name: タグ1
- id: tag-2
  name: タグ2
- id: tag-3
  name: タグ3

JSON, YAML, CSV - Nuxt Content


最終的な 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

layout: statement

記事を取得する


queryContent() による記事の取得

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

queryContent() による記事の取得

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

queryContent() による記事の取得

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

取得結果

[
  {
    _path: '/hello',
    _draft: false,
    _partial: false,
    id: 'category-1',
    name: 'カテゴリー1',
    _id: 'content:categories.yml',
    _type: 'yaml',
    _source: 'content',
    _file: 'categories.yml',
    _extension: 'yml'
  },
]

layout: statement

記事に付加情報を追加する


frontmatter

デフォルトで利用できるfrontmatter

key type default description
title string 最初の <h1> 記事のタイトル
description string 最初の <p> 記事の概要
draft boolean false 下書きにできる(取得できなくなる)
head object true <head> の設定

Markdown - Nuxt Content


追加で必要な情報をfrontmatterに追加する

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;
}

queryContent() に型を追加する

```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()
```

pages/articles/index.vue で記事一覧を表示する

<script lang="ts" setup>

// 重複してfetchしないように `useAsyncData()` でラップする
const { data: articles } = await useAsyncData(
  'articles',
  () => queryContent('articles').find(),
);
</script>

queryContent() - Nuxt Content


pages/categories/[id].vue
カテゴリーごとの記事一覧を表示する

```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>
```

pages/articles/[...slug].vue で記事を表示する

<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>

[...slug].vue で記事を表示する

<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>

layout: statement

完成


まとめ

  • Nuxt Contentを使うとファイルベースのCMSが簡単に作れる
  • frontmatterを使って記事に付加情報を追加できる
  • queryContent() を使えば複雑なクエリも実装できる
  • なにより型定義もしっかりできる

引用