ルーティング
ファイルベースのルーティング
VitePress はファイルベースのルーティングを採用しており、生成される HTML はソースの Markdown ファイルのディレクトリ構造に対応します。例えば、次のディレクトリ構造があるとします:
.
├─ guide
│ ├─ getting-started.md
│ └─ index.md
├─ index.md
└─ prologue.md
生成される HTML は次のとおりです:
index.md --> /index.html (/ でアクセス可能)
prologue.md --> /prologue.html
guide/index.md --> /guide/index.html (/guide/ でアクセス可能)
guide/getting-started.md --> /guide/getting-started.html
生成された HTML は、静的ファイルを配信できる任意の Web サーバーでホストできます。
ルートディレクトリとソースディレクトリ
VitePress プロジェクトのファイル構成には重要な概念が 2 つあります:プロジェクトルート と ソースディレクトリ です。
プロジェクトルート
プロジェクトルートは、VitePress が特別なディレクトリである .vitepress
を探しにいく場所です。.vitepress
ディレクトリは、VitePress の設定ファイル、開発サーバーのキャッシュ、ビルド出力、任意のテーマカスタマイズコードのための予約場所です。
コマンドラインから vitepress dev
や vitepress build
を実行すると、VitePress は現在の作業ディレクトリをプロジェクトルートとして使用します。サブディレクトリをルートとして指定したい場合は、コマンドに相対パスを渡します。例えば、VitePress プロジェクトが ./docs
にある場合、vitepress dev docs
を実行します:
.
├─ docs # プロジェクトルート
│ ├─ .vitepress # 設定ディレクトリ
│ ├─ getting-started.md
│ └─ index.md
└─ ...
vitepress dev docs
これにより、ソースから HTML へのマッピングは次のようになります:
docs/index.md --> /index.html (/ でアクセス可能)
docs/getting-started.md --> /getting-started.html
ソースディレクトリ
ソースディレクトリは、Markdown のソースファイルを置く場所です。既定ではプロジェクトルートと同じです。ただし、srcDir
設定オプションで変更できます。
srcDir
はプロジェクトルートからの相対パスで解決されます。例えば srcDir: 'src'
の場合、ファイル構成は次のようになります:
. # プロジェクトルート
├─ .vitepress # 設定ディレクトリ
└─ src # ソースディレクトリ
├─ getting-started.md
└─ index.md
ソースから HTML へのマッピングは次のとおりです:
src/index.md --> /index.html (/ でアクセス可能)
src/getting-started.md --> /getting-started.html
ページ間リンク
ページ間のリンクには、絶対パスと相対パスのどちらも使用できます。.md
と .html
の拡張子はどちらも機能しますが、最終的な URL を設定に応じて VitePress が生成できるよう、拡張子は省略する のがベストプラクティスです。
<!-- 良い例 -->
[はじめに](./getting-started)
[はじめに](../guide/getting-started)
<!-- 悪い例 -->
[はじめに](./getting-started.md)
[はじめに](./getting-started.html)
画像などのアセットへのリンクについては、アセットの取り扱い を参照してください。
VitePress 管理外のページへのリンク
サイト内で VitePress が生成していないページへリンクしたい場合は、フル URL(新しいタブで開く)を使うか、明示的にターゲットを指定します。
入力
[pure.html へのリンク](/pure.html){target="_self"}
出力
注意
Markdown のリンクでは、base
が自動的に URL の先頭に付与されます。つまり、base
の外にあるページへリンクしたい場合は、ブラウザの相対解決に従って ../../pure.html
のように書く必要があります。
あるいは、アンカータグの構文を直接使うこともできます:
<a href="/pure.html" target="_self">pure.html へのリンク</a>
クリーン URL の生成
サーバー側の対応が必要
VitePress でクリーン URL を提供するには、サーバー側のサポートが必要です。
既定では、VitePress は内部リンクを .html
で終わる URL に解決します。しかし、.html
を含まない「クリーン URL」(例:example.com/path
)を好む場合もあります。
一部のサーバーやホスティング(例:Netlify、Vercel、GitHub Pages)は、リダイレクトなしに /foo
を /foo.html
にマッピングできます。
- Netlify と GitHub Pages はデフォルトで対応しています。
- Vercel は
vercel.json
のcleanUrls
オプション を有効にする必要があります。
この機能が利用可能であれば、VitePress 側の cleanUrls
設定も有効化してください。これにより:
- ページ間の内部リンクが
.html
拡張子なしで生成されます。 - 現在のパスが
.html
で終わっている場合、拡張子なしのパスへクライアントサイドのリダイレクトを行います。
もしサーバーをそのように設定できない場合は、次のようなディレクトリ構造に手動でする必要があります:
.
├─ getting-started
│ └─ index.md
├─ installation
│ └─ index.md
└─ index.md
ルートのリライト
ソースディレクトリ構造と生成ページのマッピングをカスタマイズできます。これは複雑なプロジェクト構成で有用です。例えば、複数パッケージを持つモノレポで、ソースファイルと並べてドキュメントを配置したい場合:
.
└─ packages
├─ pkg-a
│ └─ src
│ ├─ foo.md
│ └─ index.md
└─ pkg-b
└─ src
├─ bar.md
└─ index.md
生成したいページが次のような場合:
packages/pkg-a/src/index.md --> /pkg-a/index.html
packages/pkg-a/src/foo.md --> /pkg-a/foo.html
packages/pkg-b/src/index.md --> /pkg-b/index.html
packages/pkg-b/src/bar.md --> /pkg-b/bar.html
rewrites
オプションを次のように設定します:
export default {
rewrites: {
'packages/pkg-a/src/index.md': 'pkg-a/index.md',
'packages/pkg-a/src/foo.md': 'pkg-a/foo.md',
'packages/pkg-b/src/index.md': 'pkg-b/index.md',
'packages/pkg-b/src/bar.md': 'pkg-b/bar.md'
}
}
rewrites
は動的なルートパラメータにも対応しています。上記の例で多くのパッケージがある場合、同じ構造なら次のように簡略化できます:
export default {
rewrites: {
'packages/:pkg/src/:slug*': ':pkg/:slug*'
}
}
リライトのパスは path-to-regexp
パッケージでコンパイルされます。高度な構文はドキュメントを参照してください。
rewrites
は、元のパスを受け取って新しいパスを返す 関数 として定義することもできます:
export default {
rewrites(id) {
return id.replace(/^packages\/([^/]+)\/src\//, '$1/')
}
}
リライト時の相対リンク
リライトを有効にした場合、相対リンクはリライト後のパスに基づいて 記述してください。例えば、packages/pkg-a/src/pkg-a-code.md
から packages/pkg-b/src/pkg-b-code.md
への相対リンクを作るには、次のように書きます:
[PKG B へのリンク](../pkg-b/pkg-b-code)
動的ルート
ひとつの Markdown ファイルと動的データから多数のページを生成できます。例えば、packages/[pkg].md
を作成して、プロジェクト内の各パッケージに対応するページを生成できます。ここで [pkg]
セグメントは、それぞれのページを区別する ルートパラメータ です。
パスローダーファイル
VitePress は静的サイトジェネレーターなので、生成可能なページパスはビルド時に確定している必要があります。したがって、動的ルートページには パスローダーファイル が 必須 です。packages/[pkg].md
に対しては packages/[pkg].paths.js
(.ts
も可)が必要です:
.
└─ packages
├─ [pkg].md # ルートテンプレート
└─ [pkg].paths.js # ルートのパスローダー
パスローダーは、paths
メソッドを持つオブジェクトをデフォルトエクスポートします。paths
は params
プロパティを持つオブジェクトの配列を返します。各オブジェクトが 1 ページに対応します。
例えば次の paths
配列を返すと:
// packages/[pkg].paths.js
export default {
paths() {
return [
{ params: { pkg: 'foo' }},
{ params: { pkg: 'bar' }}
]
}
}
生成される HTML は次のようになります:
.
└─ packages
├─ foo.html
└─ bar.html
複数パラメータ
動的ルートに複数のパラメータを含めることもできます。
ファイル構成
.
└─ packages
├─ [pkg]-[version].md
└─ [pkg]-[version].paths.js
パスローダー
export default {
paths: () => [
{ params: { pkg: 'foo', version: '1.0.0' }},
{ params: { pkg: 'foo', version: '2.0.0' }},
{ params: { pkg: 'bar', version: '1.0.0' }},
{ params: { pkg: 'bar', version: '2.0.0' }}
]
}
出力
.
└─ packages
├─ foo-1.0.0.html
├─ foo-2.0.0.html
├─ bar-1.0.0.html
└─ bar-2.0.0.html
パスを動的に生成する
パスローダーモジュールは Node.js 上で実行され、ビルド時にのみ評価されます。ローカルまたはリモートの任意のデータから、動的に配列を生成できます。
ローカルファイルから生成する例:
import fs from 'fs'
export default {
paths() {
return fs
.readdirSync('packages')
.map((pkg) => {
return { params: { pkg } }
})
}
}
リモートデータから生成する例:
export default {
async paths() {
const pkgs = await (await fetch('https://my-api.com/packages')).json()
return pkgs.map((pkg) => {
return {
params: {
pkg: pkg.name,
version: pkg.version
}
}
})
}
}
ページ内でパラメータにアクセスする
各ページへ追加データを渡すために、パラメータを利用できます。Markdown のルートファイルでは、Vue 式内で $params
グローバルプロパティから現在ページのパラメータにアクセスできます:
- パッケージ名: {{ $params.pkg }}
- バージョン: {{ $params.version }}
useData
ランタイム API からも、現在ページのパラメータにアクセスできます(Markdown と Vue コンポーネントの両方で利用可能):
<script setup>
import { useData } from 'vitepress'
// params は Vue の ref
const { params } = useData()
console.log(params.value)
</script>
生コンテンツのレンダリング
ページに渡したパラメータはクライアント JavaScript のペイロードにシリアライズされます。そのため、リモート CMS から取得した生の Markdown や HTML など、重いデータをパラメータに含めるのは避けてください。
代わりに、各パスオブジェクトの content
プロパティでコンテンツを渡せます:
export default {
async paths() {
const posts = await (await fetch('https://my-cms.com/blog-posts')).json()
return posts.map((post) => {
return {
params: { id: post.id },
content: post.content // 生の Markdown または HTML
}
})
}
}
そのうえで、Markdown ファイル内で次の特別な構文を使って、そのコンテンツを埋め込みます:
<!-- @content -->