目次13

2026 追記: 旧 VuePress ブログから移植した記事。VuePress 1.x はすでに新規開発の選択肢ではないため、本文は「2021 年時点の構築記録」としてアーカイブする目的で残している。ディレクトリの責務分割という考え方自体は Astro など現行の SSG にも通じるので、比較資料として読んでほしい。

2021 年当時、Aulvem の前身となる個人ブログは VuePress 1.x と @vuepress/plugin-blog で組んでいた。公式ドキュメント以外の情報が薄く、ディレクトリの切り方やプラグイン設定で何度も詰まったので、運用に乗せた時点でメモとして書き出したのがこの記事。

2026 年の今は Astro 5 に乗り換えており、VuePress 1.x はメンテも実質止まっている(要出典: VuePress 1 系の EOL は公式アナウンスを確認)。なので以下は「当時の決め方の記録」として読んでもらえれば。

短い答え — VuePress 1.x の構成は _posts と .vuepress の二分割

短い答え: 記事 Markdown を _posts/、設定とテーマを .vuepress/ 配下に置く。これが plugin-blog を使う場合の定番だった。

理由は責務分離。コンテンツ(書く頻度が高い)とフレームワーク設定(書く頻度が低い)を別ディレクトリに分けると、デプロイログや git diff の見通しが良くなる。

具体的には次のような並びになる。

src
├─ _posts                   各記事 Markdown
│  └─ category
│     └─ page.md
└─ .vuepress
   ├─ components            記事用コンポーネント vue
   │  └─ Component.vue
   ├─ public
   │  └─ img.jpg
   ├─ theme
   │  ├─ components         コンポーネント vue
   │  │  └─ Component.vue
   │  ├─ layouts            ページレイアウト vue
   │  │  └─ Layout.vue
   │  └─ styles
   │     └─ style.styl
   ├─ config.js             vuepress 設定 js
   └─ enhanceApp.js         vue のプラグイン js

構築フェーズで触るのは config.js / enhanceApp.js / theme/ の 3 つ。運用フェーズで触るのは _posts/public/。役割が違うので、構築が終わったら触るファイルが自然に切り替わる。

注意点: この構成は VuePress 1.x + plugin-blog 1.9 系の前提。VuePress 2 系では設定ファイルの場所もテーマの仕組みも変わっているので、当時の設定をそのまま 2 系に持っていくと動かない。

用語: 静的サイトジェネレーター(SSG)とは

SSG (Static Site Generator): Markdown などのソースをビルド時に静的 HTML に変換するツールの総称。リクエスト時に毎回サーバーで動かす CMS(WordPress 等)と違い、出来上がった HTML を CDN に置くだけで配信できる。VuePress / Astro / Next.js (output:static) / Hugo / Jekyll などが該当する。

VuePress はこの SSG の一種で、Vue.js をベースに「Markdown を中心としたドキュメントサイト」を作ることに最適化されていた。plugin-blog はそこへ「ブログ的なルーティング(タグ・カテゴリ・記事一覧)」を後付けするプラグイン、と理解しておけばよい。

config.js — サイト全体の設定ファイル

短い答え: config.js は VuePress 本体と plugin-blog の設定を一緒に書く場所。サイトの言語・タイトル・ルーティングをここで決める。

理由は集約。プラグインや locale を別ファイルに散らすこともできるが、1 ファイルにまとめておくと「サイトの仕様書」として読める。

具体例は次のとおり。

module.exports = {
  locales: {
    '/': { lang: 'ja' }
  },
  title: 'Title(config.js)',

  plugins: [[
    '@vuepress/blog',
    {directories: [
      {
        id: 'home',
        dirname: 'home',
        path: '/',
      },
      {
        id: 'tech',
        dirname: '_posts/tech',
        path: '/tech/',
        itemPermalink: '/tech/:slug'
      }
    ],
    frontmatters: [
      {
        id: "tag",
        keys: ["tag"],
        path: "/tag/",
        layout: "Tag",
        scopeLayout: "Tag"
      },
    ],},
  ]]
}

locales — デフォルト言語

locales でサイトの既定言語を決める。日本語サイトなら /lang: 'ja' にしておけば足りる。多言語化したい場合は /en/ を別キーで足す形になる。

title — サイトタイトル

title はサイト名として使う。Vue ファイル側からは $siteTitle で参照できる。OG 画像生成やヘッダーロゴ差し替えのときに参照点になるので、ここを直接ハードコードしないのが運用上は楽。

plugins — VuePress プラグインの登録

plugins 配列に VuePress プラグインを足していく。@vuepress/blog を含む各プラグインは、設定オプションを渡したい場合に「文字列単体」ではなく [name, options] のタプルにする、というのが当時のルールだった。

詳細オプションは各プラグインのドキュメント側に当たる必要がある。ここに全部書こうとすると config.js が肥大化するので、当時はコメントで参照先 URL を残していた。

plugins/directories — URL のディレクトリ構成

directories で「どの Markdown フォルダをどの URL に出すか」を決める。Aulvem 前身ブログではカテゴリ構成をそのまま URL に落としたかったので、カテゴリ単位で配列要素を足していた。

{
  id: 'tech',
  dirname: '_posts/tech',       該当カテゴリの記事を置くファイル Path
  path: '/tech/',               /tech/{{記事のpath}}
  itemPermalink: '/tech/:slug'  {{記事のpath}}のフォーマット。:slug は日付を除いたファイル名
}

注意点: dirname: '_posts/tech' の探索は 直下までしか効かず孫ディレクトリは拾わない(plugin-blog 1.9 系での挙動)。つまりカテゴリの階層は実質 1 段までだった。サブカテゴリを切りたい場合は directories を増やすしかない。

itemPermalink は日付パートを含めるかどうかを切り替えられる。私の場合は URL に日付を入れたくなかったので :slug のみで運用した。SEO 上「URL に日付があると古さが出る」という当時の通説に従ったかたち(要出典: 検索順位への影響の有無はケースバイケース)。

plugins/frontmatters — タグ機能

frontmatters でタグを生成する。keys: ["tag"] と書くと、Markdown の frontmatter に書いた tag がタグページとして扱われるようになる。

{
  id: "tag",
  keys: ["tag"],          Markdown の frontmatter に設定する Key
  path: "/tag/",
  layout: "Tag",
  scopeLayout: "Tag"
}

これを書くと、グローバルに $tag$currentTag という変数が生える。

  • $tag は全タグの一覧と、それぞれに紐づく記事リスト。タグ一覧ページを作るときに使う。
  • $currentTag は個別タグページを開いたときの「現在のタグ」の情報。

$currentTag の中身はこういう形だった。

現在のページの`$currentTag`オブジェクト内
key: "Vue"
pageKeys: ["xxxxxx"]
pages: [{...}]
path: "/tag/Vue/"
scope: "tag"

注意点: この種の「グローバル変数生成」型 API は、TypeScript との相性が悪い(型がつかない)。Astro の content collections のように、スキーマで型を強制する方が今は主流という感覚がある。

enhanceApp.js — Vue プラグインの差し込み口

短い答え: 通常の Vue プラグインを VuePress に組み込むためのエントリ。Vue.use を書く場所だと思えばよい。

理由は VuePress 自体が裏で Vue インスタンスを生成しているため、ふつうの SPA のように main.jsVue.use() できない。代わりに enhanceApp.js が引数で Vue インスタンスを渡してくれる。

import VScrollLock from 'v-scroll-lock'

export default ({
  Vue,
  options,
  router,
  siteData
}) => {
  Vue.use(VScrollLock)
}

注意点: ここに重い処理を書くと、すべてのページの初期化が遅くなる。基本は Vue.use だけに留めて、本格的なロジックはコンポーネント側に書く方がよかった。

theme — レイアウトと CSS の置き場

短い答え: theme/ は記事以外の Vue ファイルと CSS を置くディレクトリ。npm run eject でデフォルトテーマをコピーして上書きする運用だった。

理由は VuePress のデフォルトテーマがそのままでは出力されないから。eject を叩くと .vuepress/theme/ 配下にデフォルトテーマ一式が展開され、好きに書き換えられる。

eject 直後のディレクトリはこうなる。

theme
├── `global-components`
│   └── xxx.vue
├── `components`
│   └── xxx.vue
├── `layouts`
│   ├── Layout.vue _(**Mandatory**)_
│   └── 404.vue
├── `styles`
│   ├── index.styl
│   └── palette.styl
├── `templates`
│   ├── dev.html
│   └── ssr.html
├── `index.js`
├── `enhanceApp.js`
└── package.json

画面パーツの編集や追加は components/ 以下、CSS は Vue ファイル内か styles/ で書く。layouts/Layout.vue は必須なので、消したり名前を変えたりすると即ビルドが落ちる。

注意点: eject すると以後のテーマアップデートを自動で追従できなくなる。当時の私はそれを承知で乗せたが、今やるなら「最小限のオーバーライド」だけ書いて本体テーマを使い続ける構成の方が保守は楽(要出典: VuePress 2 系では別の仕組みになっている可能性あり)。

比較表 — VuePress 1.x と 2026 年の主要 SSG

当時の VuePress と、2026 年現在で同じ用途に使われやすい SSG の責務感を並べておく。

観点VuePress 1.x (2021)Astro 5 (2026)Next.js (App Router)Hugo
主言語Vue 2フレームワーク非依存ReactGo テンプレ
ターゲットドキュメント / 小規模ブログコンテンツサイト全般アプリ含む広範囲大量ページの静的サイト
設定ファイル.vuepress/config.jsastro.config.mjsnext.config.jsconfig.toml
記事の配置_posts/**src/content/**app/** または MDX 配置自由content/**
型安全なし(グローバル変数)content collections で zodTS と RSCなし
部分ハイドレーション全体が SPAislands(既定で静的)RSC + Client Component静的のみ
2026 のメンテ状況1 系は実質停止(要出典)アクティブアクティブアクティブ

ここで言いたいのは優劣ではなく、責務の切り方が違うということ。「Markdown 中心のブログを静的に出力する」というユースケースだけなら VuePress 1.x の構成は十分機能していた。Aulvem 自体は記事数の増加と型安全を理由に Astro へ移った。

よくある質問

Q. VuePress 1.x は 2026 年でも使えるか? A. 動かすこと自体は可能。ただし plugin-blog を含めメンテはほぼ止まっており、依存パッケージの脆弱性対応も期待しづらい。新規構築では選ばない方が無難で、既存サイトの延命なら Node のバージョンと npm のロックファイルをきっちり固定するのが最低条件(要出典: 公式の EOL アナウンス有無は要確認)。

Q. VuePress と Astro / Next.js の違いは? A. VuePress は Vue 2 ベースで「Markdown 中心のドキュメントサイト」が出発点。Astro はフレームワーク非依存で部分ハイドレーション(islands)を採用し、Markdown と動的部分を混在しやすい。Next.js は React 製で、静的サイトから動的アプリまで広く扱える。同じ「静的サイト」でも、扱える範囲と前提が違う。

Q. _posts と .vuepress を分ける意味は? A. 「記事コンテンツ」と「フレームワーク設定」の責務分離。書く頻度の違うものを別ディレクトリにまとめる発想自体は、Astro の src/content/src/layouts/ の分割にも引き継がれている。逆に Next.js の App Router のように app/ 配下にすべて並べる思想もあり、ここは設計趣味の領域。

Q. 今から似た構成を組むなら何を選ぶ? A. Markdown 中心のブログなら Astro + content collections が体験的に最も近い。Vue を維持したい場合の後継候補は VitePress や Nuxt Content だが、それぞれカバー範囲が違うので、用途に合うかは公式ドキュメントで要確認(要出典)。

まとめ

VuePress 1.x の構成は「_posts に書く、.vuepress で設定する」というシンプルな責務分割の上に成り立っていた。当時はその素直さに助けられ、運用に乗せるまでこぎ着けられた。

2026 年現在、Aulvem 本体は Astro 5 で動いている。同じディレクトリ分離の思想は形を変えて残っているので、この記事は「過去のスナップショット」として、現行 SSG との比較資料に使ってもらえればと思う。