📝

VitePressでのブログ構築

2023/08/11に公開

ブログをVitePressで作りました。

https://takos.dev/

VitePressでブログを構築するにあたって、いくつかTipsというかノウハウがあったので共有です。

とはいえ内部の動作まで追っかけきった完璧な記事ではなく、「なんかこうやったら動いた」くらいの温度感なので、その点ご容赦ください。

構成

Github Pages で公開しています。

レポジトリ: https://github.com/takyam-git/blog

特にひねりもなく、以下のドキュメントのとおりに .github/workflows/deploy.yml を作って Github Pages で公開されるようにしています。

https://vitepress.dev/guide/deploy#github-pages

概念

今回は各記事を「Entry」と表現するようにしてます。
「Post」でもよかったんだけどなんとなく。

ディレクトリ構成

なにかのアプリやライブラリのドキュメントサイトとしてVitePressを利用する場合は /doc などディレクトリを掘って対応するのが良いらしいのですが、ブログ用のレポジトリなので root に展開しています。

./
  .vitepress/
    theme/
      Entry.vue
      index.ts
      style.css
    config.mts
    entries.data.ts
  entries/
    2023/
      xx-xx.md
  public/
    images/
    favicon.ico
  entries.md
  index.md

このへんが主要なファイル郡になってきます。

理解すべきことは以下ですかね。

  • .vitepress/ ディレクトリ内に諸々設定的なものやレイアウト的なものはつっこむ
  • ***.md ファイルが自動的にルーティングされて ***.html として公開される
  • public/ ディレクトリは公開ディレクトリ
    • たとえば public/imageshttps://example.com/images/ でアクセスできるようになる

ブログとして構築するにあたって必要な機能

「ブログ」としてやるなら以下の機能が必要なので、対応してます。

  • 記事の一覧を取得して、いい感じにリスト表示する
  • OGPをページに合わせて出力する

これらをどうやってVitePressで実装するのか、というのがこの記事の内容になります。

記事の一覧を取得して、いい感じにリスト表示する

ブログなので「トップページに新着記事一覧出したい」「記事一覧ページ作りたい」みたいなのが真っ先に出てきたんですが、VitePressはWordPressっぽい名前の割に、特にブログ用ってわけでもないのでデフォルトで良い感じのものがあるわけじゃないっぽいです。

基本的な概念として 元々用意されてるコンポーネント郡は Frontmatter とよばれる、markdownファイルの先頭に記述するYAMLのデータをレンダリングすることに特化してます。

で、このFrontmatterの部分はYAMLベタ書きです。
たぶん動的に操作する方法は無さそう?

デフォルトで用意されてる Features はYAMLをベタ書きするしかない

たとえば、トップページにいい感じに新着記事の一覧を出せそうな Features をFrontmatterで以下のように設定できます。

features:
  - title: Feature A
    details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
  - title: Feature B
    details: Lorem ipsum dolor sit amet, consectetur adipiscing elit
  - title: Feature C
    details: Lorem ipsum dolor sit amet, consectetur adipiscing elit

これで3つのカードがトップページに表示されて、固定の要素をいい感じに表示する機能は用意されてます。

自動で新着記事の一覧を出力するには

が、私がやりたいのは自動で新着記事の一覧を出力したいので、このFrontmatterを使ったYAML戦法は使えません。

そこで2つのものから新着記事一覧を実装することになりました。

  • 記事一覧を生成する entries.data.ts
  • entries.data.ts を元にレンダリングするVueコンポーネント

entries.data.ts とは

ファイル名はなんでもいいんですが、記事一覧として entries.data.ts を作っています。

https://github.com/takyam-git/blog/blob/main/.vitepress/entries.data.ts

中身は非常にシンプルで、 entries ディレクトリ以下のMarkdownファイルの一覧を取得して返す、といった感じです。

createContentLoader() のドキュメントは以下。
https://vitepress.dev/guide/data-loading#createcontentloader

こんな感じで「記事一覧のデータ」を生成することで、各MarkdownファイルやVueコンポーネントから記事一覧を取得できるようになります。

entries.data.ts をもとに新着記事一覧をレンダリングする

entries.data.ts で記事一覧が取得できるので、適当にレンダリングしてあげます。

<script lang="ts" setup>
const { data as entries } from '.vitepress/entries.data.ts'
const newEntries = [...entries].reverse().slice(0, 6)
</script>

<template>
  <ul>
    <li v-for="(entry, index) in newEntries" :key="index">
      <span v-if="entry.frontmatter.title">{{ entry.frontmatter.title }}</span>
    </li>
  </ul>
</template>

実際のコードはこのへん。 https://github.com/takyam-git/blog/blob/b615b58742db7023bdcef8ad59ea8c4ad8eab582/.vitepress/theme/components/HomeBottom.vue#L13-L24

OGPをページに合わせて出力する

たとえばfaviconなどの固定のタグを <head> 内に出力するのは、 .vitepress/config.mts に以下のような感じで設定してあげればよいです。

.vitepress/config.mts
import { defineConfig } from "vitepress";
export default defineConfig({
  head: [
    ["link", { rel: "icon", href: "/favicon.ico" }],
    ["meta", { property: "og:image", content: "https://example.com/og.png" }],
    ["meta", { property: "og:site_name", content: "your site name" }],
    ["meta", { property: "twitter:card", content: "summary" }],
    ["meta", { property: "twitter:site", content: "@*******" }],
  ]
});

OGPタグも og:site_name みたいなサイト共通の部分だけでいいならはこんな感じでいいと思うんですが、記事ページごとに og:title とか og:url は出し分けたいのが人情だと思うので対応しました。

transformHead() を使ってページごとのヘッダーカスタマイズを行う

https://vitepress.dev/reference/site-config#transformhead

transformHead() ってのを使うと、ページごとのヘッダーカスタマイズができます。

.vitepress/config.mts
import { defineConfig } from "vitepress";
export default defineConfig({
  async transformHead(context) {
    return [
      ["meta", { property: "og:title", content: context.pageData.title }],
      ["meta", { property: "og:url", content: `https://example.com/${context.pageData.filePath
      .replace(/^\//, "")
      .replace(/\.md$/, ".html")}` }],
    ];
  },
});

第1引数の context からいくつか情報が取得できるので、それをmetaタグとして設定しています。

  • URLを直接 context から取得する方法はなさそうだったので、適当に変換してあげる
  • Frontmatter の情報は context.pageData に入ってそうだったのでそれを使う

実際のコード: https://github.com/takyam-git/blog/blob/b615b58742db7023bdcef8ad59ea8c4ad8eab582/.vitepress/config.mts#L69-L79

その他ハマったところ

  • ESMにする必要があるので package.json には "type": "module" が必要
    • これないとエラーになるはず
  • window はクライアントサイドしかアクセスできないので <ClientOnly> タグで囲んであげる
    • 今回は投稿エディタ周りで window.localStorage やらにアクセスする必要があって、その辺は <ClientOnly> で囲んだ親階層を用意することで対応しました
    • <ClientOnly> のドキュメントは以下

さいご

まだ完成したとは言えないですし、VitePress使いこなせてもないんですけどVitePressのデフォのテーマの出来がよいので、何もしなくてもライトテーマ/ダークテーマ対応のシャレオツブログがGithubPagesで公開できます。
Github Pagesで作れるということは、無料でブログが作れるということなので、いいことです。

その他、記事の投稿をできるようにしたり、まだ作りかけの機能がいくつかあるので、そのうちこの記事を拡充する形で追記していこうと思います。

参考

Discussion