先人たちの知恵[1][2]を大いに借りながら、このブログを作成し、Cloudflare Pagesにデプロイした時の作業記録。 といいつつ作って後から書いたので、抜けがあるかも。
ソースコードはGithubにもあるので、細かいところはそちらを。
参考にしたサイト様
- ブログサイトを VitePress へ移行した - nshmura.com
- VitePressでブログを作ってみました - blog.hakuteialpha.com
VitePressの準備
Getting Startedに沿ってVitePressをセットアップする。
bun install -D vitepress
VitePressの初期設定をする。configやmdファイルなどを置く場所は./docs
にして、TitleとDescriptionは適当、他はデフォルトにした。
bun vitepress init
ディレクトリ構成
posts/<year>
の下に、<MMDD-title>
というフォーマットでディレクトリを作って記事を書くことにする。
.
├── docs
│ ├── posts
│ │ ├── <year>
│ │ │ └── <MMDD-title>
│ │ │ ├── index.md
│ │ │ └── ...
Tailwind CSSを入れる
Tailwind CSSを使いたいので入れる。 どうやら最近(2025-1-22)にv4.0.0が出たようで、インストール体系が変わったぽい。 Web初心者には難しかったので今回はv3系を使う。時間があるときに勉強してv4にしようかな。
bun install -D [email protected] autoprefixer postcss
次に各種設定ファイルを書く。
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
export default {
content: [
'./docs/index.md',
'./docs/**/index.md',
'./.vitepress/**/*.{js,ts,vue}'
],
theme: {
extend: {},
},
plugins: [],
important: true,
}
@tailwind base;
@tailwind components;
@tailwind utilities;
import DefaultTheme from 'vitepress/theme'
import './custom.css'
export default DefaultTheme
これでTailwind CSSの導入はおわり。
Markdown-it の Pluginを入れる
VitePressはMarkdown-it使っている[3]ので、色々なプラグインを入れて機能拡張できる。
Footnote[4]
bun install -D markdown-it-footnote
config.mts
に以下を追記
import markdownItFootnote from 'markdown-it-footnote'
export default defineConfig({
markdown: {
config: (md) => {
md.use(markdownItFootnote)
}
},
})
Vitepress Plugin Group Icons[5]
::: code-group
にアイコンをつけたり、::: code-group
以外でもCode BlockにTitle Barをつけたりできる。
bun install -D vitepress-plugin-group-icons
このプラグインは、importしてmd.use
する以外にもviteの設定やthemeのindex.ts
への追記が必要。
import {groupIconMdPlugin, groupIconVitePlugin} from 'vitepress-plugin-group-icons'
export default defineConfig({
markdown: {
config: (md) => {
md.use(groupIconMdPlugin)
}
},
vite: {
plugins: [
groupIconVitePlugin()
]
},
})
import 'virtual:group-icons.css'
LaTeX
まずはmarkdown-it-mathjax3をインストール。
bun install -D markdown-it-mathjax3
そしてconfig.mts
を編集。
export default {
markdown: {
math: true
}
}
なんとこれだけでOK。
VitePressのPlugin達を入れる
Git-based page histories
VitePressのConfigとthemeのconfig両方をいじる必要がある。
自分で使う場合は、mapAuthors
のそれぞれの項目を変更すると良い。 GitHubの場合、アイコンの生URLはプロファイルページのアイコン画像から取得できるはず。 その他それぞれの設定の意味などはPluginの公式Documentを参照。
bun install -D @nolebase/vitepress-plugin-git-changelog
import { GitChangelog, GitChangelogMarkdownSection } from '@nolebase/vitepress-plugin-git-changelog/vite'
export default defineConfig({
vite: {
plugins: [
GitChangelog({
repoURL: () => "https://github.com/bqc0n/blog",
mapAuthors: [{
avatar: "https://avatars.githubusercontent.com/u/89625049?v=4",
name: "bqc0n", username: "bqc0n",
mapByNameAliases: ["bqc0n"],
}]
}),
GitChangelogMarkdownSection({
exclude: (id) => id.endsWith('index.md'),
}),
],
},
})
import DefaultTheme from 'vitepress/theme'
import './custom.css'
import 'virtual:group-icons.css'
import { NolebaseGitChangelogPlugin } from "@nolebase/vitepress-plugin-git-changelog/client"
import '@nolebase/vitepress-plugin-git-changelog/client/style.css'
export default DefaultTheme
export default {
...DefaultTheme,
enhanceApp({app}) {
app.use(NolebaseGitChangelogPlugin)
}
}
vitepress-plugin-lightbox
https://github.com/BadgerHobbs/vitepress-plugin-lightbox
bun install -D vitepress-plugin-lightbox
画像をクリックしたら拡大するやつ。
config.mts
とtheme/index.ts
への追記、theme/Layout.vue
の作成が必要。
import lightbox from "vitepress-plugin-lightbox"
export default defineConfig({
markdown: {
config: (md) => {
md.use(lightbox)
}
},
})
import Layout from "./Layout.vue";
export default {
...DefaultTheme,
Layout,
}
<script setup>
import DefaultTheme from "vitepress/theme";
import { onMounted } from "vue";
import { useRouter } from "vitepress";
import mediumZoom from "medium-zoom";
const { Layout } = DefaultTheme;
const router = useRouter();
// Setup medium zoom with the desired options
const setupMediumZoom = () => {
mediumZoom("[data-zoomable]", {
background: "transparent",
});
};
// Apply medium zoom on load
onMounted(setupMediumZoom);
// Subscribe to route changes to re-apply medium zoom effect
router.onAfterRouteChanged = setupMediumZoom;
</script>
<template>
<Layout />
</template>
<style>
.medium-zoom-overlay {
backdrop-filter: blur(5rem);
}
.medium-zoom-overlay,
.medium-zoom-image--opened {
z-index: 999;
}
</style>
Comment With Giscus
GitHub Discussionsを利用したコメントシステム。利用するには、リポジトリがPublicである必要があるので注意。
GitHub Discussionsの有効化
このガイドに従ってリポジトリのDiscussionsを有効化する。
Giscus Appのインストール
https://github.com/apps/giscus をリポジトリにインストールする。
vitepress-plugin-comment-with-giscusのインストール
https://giscus.app/ja で設定すべき項目の内容を取得できる。
bun install -D vitepress-plugin-comment-with-giscus
theme/index.ts
に追記
import giscusTalk from 'vitepress-plugin-comment-with-giscus';
import { useData, useRoute } from 'vitepress';
import { toRefs } from "vue";
export default {
// ...
setup() {
const { frontmatter } = toRefs(useData());
const route = useRoute();
giscusTalk({
repo: 'bqc0n/blog',
repoId: 'R_kgDONyqpCg',
category: 'Announcements',
categoryId: 'DIC_kwDONyqpCs4Cmvcv',
mapping: 'title',
inputPosition: 'top',
lang: 'jp',
locales: {
'ja-JP': 'ja',
'en-US': 'en'
},
homePageShowComment: false,
lightTheme: 'light',
darkTheme: 'transparent_dark',
}, {
frontmatter, route
},
true,
);
}
}
config.mts
で追加の設定
lastUpdated: true
: ページの下にちっちゃく最終更新日が表示されるcleanUrls: true
: URL末尾の.html
が消える。デプロイ先によっては追加の設定が必要かもしれないが、今回はそのままで動いてくれた。
トップページの作成
トップページには記事一覧を表示したいので、まず記事一覧を取得するためにBuild-Time Data Loadingという機能を利用する。
import { createContentLoader } from 'vitepress';
export default createContentLoader('posts/**/index.md', {
includeSrc: false,
transform(rawData) {
return rawData
.filter(page => page.url != "/posts/")
.sort((a, b) => +new Date(b.frontmatter.date) - +new Date(a.frontmatter.date) )
}
});
またcustom.css
に以下を追記。
.home-posts-article {
border-top: 1px solid var(--vp-c-divider);
padding: 10px 0;
}
momentをインストール。
bun install -D moment
トップページを作成する。
---
title: Home
layout: doc
next: false
prev: false
---
<script setup>
import { data as posts } from '.vitepress/posts.data.mts';
import moment from 'moment';
</script>
# blog.bqc0n.com
個人的な備忘録集です。記事の内容は、[MIT-License](https://github.com/bqc0n/blog/blob/main/LICENSE)で利用可能です。
<article v-for="post of posts" class="home-posts-article">
<a :href="post.url" class="block text-inherit no-underline hover:underline">
<p class="text-2xl">{{ post.frontmatter.title }}</p>
<p class="text-sm text-gray-500">{{ moment(post.frontmatter.date).format('YYYY-MM-DD') }}</p>
<p>{{ post.frontmatter.description }}</p>
</a>
</article>
記事一覧ページの作成
トップページと同じように記事一覧ページも作る。
---
title: Posts | blog.bqc0n.com
layout: doc
next: false
prev: false
---
<script lang="ts" setup>
import { data as posts } from "../.vitepress/posts.data"
import moment from 'moment';
</script>
# 記事一覧ページの作成
<ul>
<li v-for="post of posts">
<a :href="post.url" class="font-semibold text-lg">{{ post.frontmatter.title }}</a>
<span class="text-sm"> - {{ moment(post.frontmatter.date).format('YYYY-MM-DD') }}</span>
</li>
</ul>
こんな感じになる。
タグページの作成
Dynamic Routesを使って、build時にタグページを動的に作成するようにする。
Dynamic Routesは、[placeholder].md
のように[]
を名前に含むファイルやディレクトリを作ることで、 placeholder
に文字列を代入して動的にページを作成できる機能である。
ディレクトリ構造はこのようにする。
docs
└── tags
├── [tag]
│ ├── index.md
│ └── index.paths.js
└── index.md
次にplaceholderに代入される文字列を作成する Paths Loader File を作成する。 tag:
が[tag]
に代入される文字列である。content:
はmdファイルの<!-- @content -->
に展開される文字列で、これを利用して ページの内容を動的に変化させることができる。
import fs from 'fs'
import { globSync } from 'glob'
var tags = {}
var files = globSync("docs/posts/**/index.md");
files.forEach(file => {
var data = fs.readFileSync(file, 'utf8');
var found = data.match(/^tags:\s*\[(.+)]\s*$/m)
if (found) {
found[1].split(",")
.map(tag => tag.replaceAll('"', '') )
.forEach(tag => tags[tag.replaceAll(' ', '')] = tag )
}
});
export default {
paths: () => {
return Object.keys(tags).map((key) => {
return { params: { tag: key }, content: `# タグ: ${tags[key]}`}
})
}
}
[tag]/index.md
も作成
---
next: false
prev: false
---
<script setup>
import { useData } from 'vitepress'
import { data as posts } from '../../.vitepress/posts.data.mts'
const { params } = useData()
const current_tag = params.value.tag
var pages = []
posts.forEach(post => {
if (post.frontmatter.tags){
var tags = post.frontmatter.tags.map(tag => tag.replaceAll(" ", "") )
if (tags.includes(current_tag)) pages.push(post)
}
})
</script>
<!-- @content -->
<ul>
<li v-for="page of pages">
<a :href="page.url">{{ page.frontmatter.title }}</a>
</li>
</ul>
これでタグページの作成も完了。
共通ヘッダー
記事の投稿日、最終更新日、付けられたタグを表示する。
<script setup>
import { useData } from 'vitepress'
import moment from 'moment';
const { frontmatter } = useData()
const lastUpdated = moment(useData().page.value.lastUpdated).format('YYYY-MM-DD');
const date = moment(frontmatter.value.date).format('YYYY-MM-DD');
</script>
<template>
<div class="vp-doc">
<p>
<span>📅 {{ date }}</span> | <span>🔄 {{ lastUpdated }}</span>
</p>
<h1>{{ frontmatter.title }}</h1>
<p>
<a v-for="tag in frontmatter.tags" :href="'/tags/' + encodeURIComponent(tag.replaceAll(' ', '')) + '/'"> #{{ tag }} </a>
</p>
</div>
</template>
<script setup>
// ...
import {useRouter} from "vitepress";
import {useData, useRouter} from "vitepress";
import PostHeader from "./PostHeader.vue";
// ...
</script>
<template>
<Layout />
<Layout>
<template #doc-before>
<PostHeader />
</template>
</Layout>
</template>
SEO対策 (WIP)
とりあえずサイトマップの生成だけやってみる。
export default defineConfig({
sitemap: {
hostname: "https://blog.bqc0n.com"
},
})
デプロイ
ここまで出来たら、いよいよCloudflare Pagesにデプロイする。
INFO
Cloudflare Dashboardの仕様はよく変わるので、その時は都度読み替えてください。
GitHubとの連携は済ませてある前提とする。
Cloudflare Dashboardから、Workers & Pages -> 右上の作成ボタン -> Pages -> Gitと遷移する。
blogのリポジトリを選択しセットアップ開始。
ビルドコマンドにはbun run docs:build
、ビルド出力ディレクトリにはdocs/.vitepress/dist
を設定する。 加えて、環境変数にNODE_VERSION=23.7.0
を追加しておく。
「保存してデプロイ」を押すとビルドが始まる。
ビルドが終わったら、Pagesからblogの設定ページを開いて、カスタムドメインを追加する。
以上でデプロイ完了、ブログ完成。