はじめに
この記事は、前回の概要編でDocusの導入を決めた方に向けて、具体的な実装手順とノウハウを提供します。
この記事の対象読者:
- Docus導入を決定し、実装フェーズに入った方
- 具体的な設定ファイルやコード例が必要な方
- トラブルシューティングや最適化のノウハウを求めている方
この記事で扱うこと:
- プロジェクトのセットアップ手順
app.config.tsの実践的な設定例- Frontmatter設計パターン
- カスタムコンポーネントの実装
- Tailwind CSS統合とスタイルカスタマイズ
- パフォーマンス最適化の実践
- よくあるトラブルシューティング
- 運用効率化のスクリプトとチェックリスト
1. プロジェクトセットアップ
新規プロジェクトの作成
# Docusテンプレートでプロジェクト作成
npx nuxi init docs -t docus
cd docs
# 依存関係のインストール
pnpm install
# 開発サーバーの起動
pnpm dev
ブラウザで http://localhost:3000 にアクセスし、Docusのデフォルトページが表示されることを確認します。
ディレクトリ構造の設計
docs/
├── content/ # Markdownコンテンツ
│ ├── 1.intro/ # 章1(orderで制御)
│ │ ├── index.md
│ │ ├── 1.getting-started.md
│ │ └── 2.installation.md
│ ├── 2.guide/ # 章2
│ │ ├── index.md
│ │ └── 1.basic-usage.md
│ └── index.md # トップページ
├── public/ # 静的アセット
│ ├── images/ # 画像ファイル
│ ├── favicon.ico
│ └── og-image.png
├── components/ # Vueコンポーネント
│ └── content/ # Markdownで使用するコンポーネント
│ ├── LinkCard.vue
│ └── Badge.vue
├── app.config.ts # Docus設定
├── nuxt.config.ts # Nuxt設定
└── package.json
ディレクトリ設計のポイント:
- 章 = ディレクトリ、ページ =
.mdファイル - ディレクトリ名の先頭に数字を付けて順序を明示(
1.intro,2.guide) - 各章に
index.mdを配置して章の概要を記載 - 画像は
public/images/に配置し、絶対パス/images/...で参照
既存プロジェクトへの統合
既存のNuxtプロジェクトにDocusを追加する場合:
# Docusパッケージのインストール
pnpm add docus
# 必要に応じて追加パッケージをインストール
pnpm add @nuxt/content @nuxt/ui
nuxt.config.ts に以下を追加:
export default defineNuxtConfig({
extends: ['docus'],
// その他の既存設定...
})
2. コンテンツ移設とFrontmatter設計
コンテンツの移設手順
- 既存Markdownを
content/配下へコピー:- 章ごとにディレクトリを作成
- ファイル名の先頭に数字を付けて順序を明示(
1.getting-started.md)
- 画像アセットの移設:
public/images/配下に配置- Markdown内のパスを
/images/...に修正
- Frontmatterの整形:
- 必須項目(
title,description,order)を追加 - 後述のテンプレートを活用
- 必須項目(
Frontmatter設計パターン
ページ用テンプレート
---
title: ページのタイトル
description: 120字以内の要約(検索とOGPカード用)
order: 10
draft: false
navigation:
title: サイドバー表示名(省略可。titleと同じなら削除)
icon: i-heroicons-book-open # アイコン(省略可)
---
セクション用テンプレート(index.md)
---
title: セクション名
description: セクションの概要
order: 20
navigation:
icon: i-heroicons-folder
---
ナビ非表示ページ
---
title: ナビに表示しないページ
description: プライバシーポリシーなど
navigation: false
---
ドラフト管理
---
title: 執筆中の記事
description: まだ公開していない記事
draft: true # ビルド時に除外される
---
Frontmatter一括整形スクリプト
既存Markdownに必須項目を一括追加するスクリプト:
// scripts/fix-frontmatter.ts
import fg from 'fast-glob'
import fs from 'node:fs/promises'
import matter from 'gray-matter'
function inferTitleFromHeading(content: string) {
const m = content.match(/^#\s+(.+)$/m)
return m ? m[1].trim() : 'タイトル未設定'
}
function summarize(content: string) {
return content
.replace(/`{1,3}[\s\S]*?`{1,3}/g, '') // コードを除去
.replace(/\[(.*?)\]\(.*?\)/g, '$1') // リンクをテキスト化
.replace(/\s+/g, ' ')
.trim()
.slice(0, 120)
}
const files = await fg('content/**/*.md', { dot: false })
for (const file of files) {
const raw = await fs.readFile(file, 'utf8')
const parsed = matter(raw)
const data: any = parsed.data || {}
// 必須項目の補完
data.title ||= inferTitleFromHeading(parsed.content)
data.description = data.description || summarize(parsed.content)
data.order ??= 999
data.draft ??= false
const next = matter.stringify(parsed.content, data)
await fs.writeFile(file, next)
}
console.log(`Updated ${files.length} files`)
使用方法:
# 依存関係のインストール
pnpm add -D fast-glob gray-matter tsx
# package.jsonにスクリプトを追加
# "frontmatter:fix": "tsx scripts/fix-frontmatter.ts"
# 実行
pnpm frontmatter:fix
3. app.config.tsの実践的設定
ドキュメントルート直下に app.config.ts を配置し、Docusの動作をカスタマイズします。
基本設定
import { defineAppConfig } from '#app'
export default defineAppConfig({
docus: {
// サイト情報
title: 'サイト名',
description: 'サイトの説明文',
image: '/images/og-image.png',
url: 'https://example.com',
// ソーシャルリンク
socials: {
twitter: '@handle',
github: 'org/repo',
linkedin: 'company-name',
youtube: 'channel-id'
},
// ヘッダー設定
header: {
logo: {
src: '/images/logo.svg',
alt: 'サイトロゴ',
width: 40,
height: 40
},
title: 'サイト名',
showLinkIcon: true, // ナビリンクにアイコン表示
fluid: true // 全幅レイアウト
},
// サイドバー設定
aside: {
level: 1, // 目次で拾う見出し階層の開始(h1から)
collapsed: false, // デフォルトで開く
exclude: [] // 除外するパス(例: ['/blog/*'])
},
// 目次(TOC)設定
toc: {
title: 'このページの目次',
depth: 2, // h2まで表示(3にするとh3まで)
searchDepth: 2
},
// メインコンテンツ設定
main: {
padded: true, // コンテンツに余白を追加
fluid: false // 全幅レイアウト(falseで最大幅制限)
},
// フッター設定
footer: {
credits: {
enabled: true,
repository: 'https://github.com/org/repo'
},
textLinks: [
{ text: 'プライバシーポリシー', href: '/privacy' },
{ text: 'お問い合わせ', href: '/contact' }
],
iconLinks: [
{
label: 'GitHub',
href: 'https://github.com/org/repo',
icon: 'simple-icons:github'
},
{
label: 'X',
href: 'https://x.com/handle',
icon: 'simple-icons:x'
}
]
},
// GitHub統合("Edit this page on GitHub"リンク)
github: {
owner: 'org',
repo: 'repo',
branch: 'main',
dir: 'content',
edit: true // 編集リンクを表示
}
}
})
検索設定(DocSearch統合)
ページ数が増えたら、Algolia DocSearchに移行:
export default defineAppConfig({
docus: {
// ... 既存設定
// DocSearch設定(Algoliaから取得した認証情報)
algolia: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_SEARCH_API_KEY',
indexName: 'YOUR_INDEX_NAME',
langAttribute: 'lang',
docSearch: {
placeholder: 'サイト内を検索',
translations: {
button: {
buttonText: '検索',
buttonAriaLabel: 'サイト内を検索'
},
modal: {
searchBox: {
cancelButtonText: 'キャンセル',
resetButtonTitle: 'クリア'
}
}
}
}
}
}
})
4. ページ構成とコンポーネント設計
Docusのページルーティング戦略
DocusはContent-Drivenな設計を採用しており、ページ構成には2つのアプローチがあります。
アプローチ1: content/ ディレクトリでルーティング(推奨)
トップページ(/)の例:
---
title: Nuxtation
description: ブログと図書館のコンテンツを集約したサイト
navigation: false
layout: page
---
<div class="not-prose">
<home-landing />
</div>
仕組み:
content/index.mdが存在 → Docusが/ルートを自動生成- マークダウン内で
<home-landing />を使用してVueコンポーネントを埋め込み pages/index.vueは不要(重複を避ける)
メリット:
- フロントマター(title, description, layout)でメタデータを管理
- コンテンツとコードの分離
- SEOに必要な情報をマークダウンで一元管理
アプローチ2: pages/ ディレクトリで独自UI(一覧ページ向け)
ブログ一覧(/blog)の例:
<template>
<BlogIndexPage />
</template>
<script setup lang="ts">
// 複雑なロジック: データ取得、ページネーション、フィルタリング
const { data: articlesData } = useLazyAsyncData('docs-blog-articles', async () => {
const articles = await queryCollection('blog').all()
return articles.map(article => ({
title: article.title,
path: article.path,
// ... 必要なフィールドのみ
}))
})
// ページネーションロジック
const currentPage = computed(() => Number(route.query.page) || 1)
const paginatedArticles = computed(() => {
// ...
})
</script>
<template>
<!-- 200行以上の複雑なUI -->
</template>
なぜ pages/index.vue → components/**IndexPage.vue なのか:
| 項目 | pages/blog/index.vue | components/BlogIndexPage.vue |
|---|---|---|
| 責務 | ルーティングのみ | ビジネスロジック + UI |
| 行数 | 3行 | 200行以上 |
| テスト | 不要 | 単体テスト可能 |
| 再利用 | 不可 | 他ページから参照可能 |
| Storybook | 不可 | プレビュー可能 |
設計原則:
- 関心の分離(Separation of Concerns)
- pages/ は薄く保ち、ロジックは components/ に
- 一貫性のあるパターン
pages/blog/index.vue → BlogIndexPage.vue pages/biblio/index.vue → BiblioIndexPage.vue pages/jenre/tags/[slug].vue → JenreTagsPage.vue - Nuxt 3 / Docus のベストプラクティス
- コンポーネントの自動インポート機能を活用
- グローバル登録(
components/のglobal: true)で簡潔に
ページ構成の実例
現在のプロジェクト構造:
app/
├── pages/
│ ├── blog/
│ │ └── index.vue # → BlogIndexPage
│ ├── biblio/
│ │ └── index.vue # → BiblioIndexPage
│ └── jenre/
│ └── tags/
│ └── [slug].vue # → JenreTagsPage(存在する場合)
├── components/
│ ├── blog/
│ │ ├── BlogIndexPage.vue # 複雑なロジック + UI
│ │ └── BlogCardHorizontal.vue
│ ├── biblio/
│ │ ├── BiblioIndexPage.vue # 複雑なロジック + UI
│ │ └── BiblioCard.vue
│ └── content/
│ └── home/
│ └── HomeLanding.vue # トップページのUI
└── content/
├── index.md # / → <home-landing />
├── blog/
│ └── *.md # 個別記事
└── biblio/
└── *.md # 個別書籍
使い分けの基準:
| ページタイプ | 使用するアプローチ | 理由 |
|---|---|---|
| トップページ | content/index.md | コンテンツ主体、SEO最適化 |
| 記事詳細 | content/blog/*.md | マークダウンで執筆 |
| 一覧ページ | pages/ + components/ | 複雑なUI、動的データ |
| カスタムページ | pages/ + components/ | 独自レイアウト必要 |
参考: カスタムコンポーネント(LinkCard等)の実装例は、前回の記事06を参照してください。
5. Tailwind CSS統合とスタイルカスタマイズ
Tailwind CSS v4の統合
Docus 5.2.0以降、Tailwind CSS v4が標準統合されています。
カスタムカラーの定義(app/assets/css/tailwind.css):
@reference "tailwindcss";
/* カスタムカラー */
:root {
--color-primary-head: #380964;
--color-sf-500: #4f46e5;
--color-sf-600: #4338ca;
}
.dark {
--color-primary-head: #9333ea;
}
コンポーネントでのTailwind活用
@apply ディレクティブで既存CSSをTailwindユーティリティに変換:
<style scoped>
@reference "tailwindcss";
/* Before: 従来のCSS */
.hero {
position: relative;
width: 100%;
height: 500px;
overflow: hidden;
}
@media (max-width: 768px) {
.hero {
height: 400px;
}
}
/* After: Tailwind CSS */
.hero {
@apply relative w-full h-[500px] overflow-hidden md:h-[400px];
}
.hero__overlay {
@apply absolute inset-0 bg-gradient-to-b from-black/10 to-black/30;
}
</style>
Nuxt UI v4のuiプロップ活用
:deep()や!importantを最小限に抑える:
<template>
<UCard
:ui="{
root: 'overflow-hidden rounded-md ring-1 ring-gray-200 dark:ring-gray-800',
body: 'p-0 sm:p-0',
header: 'p-0 sm:p-0',
}"
class="my-3 not-prose"
>
<!-- コンテンツ -->
</UCard>
</template>
重要ポイント:
not-proseクラスで.proseスタイルの影響を除外uiプロップで全ブレークポイントのスタイルを制御- レスポンシブ指定(
sm:、md:)を必ず含める
MDCコンポーネントのスタイリング
::code-collapseボタンの視認性向上(app/assets/css/prose.css):
/* code-collapse ボタンのスタイル */
.prose div[class*="bg-gradient-to-t from-muted"] button,
.prose button[data-state="closed"],
.prose button[data-state="open"] {
background-color: #d1d5db !important;
padding: 0.5rem 1rem !important;
border-radius: 0.375rem !important;
transition: background-color 0.2s ease !important;
color: #1f2937 !important;
font-weight: 500 !important;
opacity: 0.5;
}
.prose div[class*="bg-gradient-to-t from-muted"] button:hover,
.prose button[data-state="closed"]:hover,
.prose button[data-state="open"]:hover {
background-color: #9ca3af !important;
opacity: 1;
}
/* ダークモード */
.dark .prose div[class*="bg-gradient-to-t from-muted"] button,
.dark .prose button[data-state="closed"],
.dark .prose button[data-state="open"] {
background-color: #374151 !important;
color: #d1d5db !important;
opacity: 0.5;
}
.dark .prose div[class*="bg-gradient-to-t from-muted"] button:hover,
.dark .prose button[data-state="closed"]:hover,
.dark .prose button[data-state="open"]:hover {
background-color: #4b5563 !important;
opacity: 1;
}
MDC構文の注意点:
::code-collapse を使用する際、内側のコードブロック開始マーカー(```)は必ず行頭から始める必要があります。
- ❌ 誤り:
```typescriptの前にスペースやタブ文字がある(インデントされている状態) - ✅ 正しい:
```typescriptが行の最初の文字から始まる(インデントなし)
正しい記述の構造:
- 1行目:
::code-collapse - 2行目: 空行
- 3行目:
```typescript← 行頭から始める(スペースを入れない) - 4行目以降: コード内容
- コード終了:
``` - 最終行:
::でcode-collapseを閉じる
6. パフォーマンス最適化の実践
画像最適化(WebP変換)
変換コマンド:
cd public/img/blog
for file in *.png; do
cwebp -q 80 "$file" -o "${file%.png}.webp"
done
<NuxtPicture>コンポーネントの活用:
<NuxtPicture
:src="heroImage.src"
:width="heroImage.width"
:height="heroImage.height"
:alt="heroImage.alt"
:img-attrs="{ class: 'hero-image', loading: 'eager' }"
sizes="sm:100vw md:1356px lg:1356px"
:modifiers="{ fit: 'cover', quality: 80 }"
format="webp"
/>
効果:
- PNG → WebP変換で84%削減(9.5MB → 1.5MB)
- レスポンシブ画像配信でモバイル81%削減
サードパーティスクリプトの最適化
@nuxt/scriptsで統合管理(nuxt.config.ts):
export default defineNuxtConfig({
modules: [
'@nuxt/scripts',
// ...
],
scripts: {
registry: {
clarity: process.env.NODE_ENV === 'production' ? {
id: 'xxxxxxxx',
trigger: 'idle'
} : false,
googleAnalytics: process.env.NODE_ENV === 'production' ? {
id: 'G-xxxxxxx',
trigger: 'idle'
} : false
},
},
})
重要: $productionブロック内ではなく、トップレベルに配置し、process.env.NODE_ENVで制御します。
7. トラブルシューティング
ナビゲーションの順序が意図通りでない
問題: ディレクトリの並び順が不明瞭で、意図しない順序で表示される
解決策:
- ディレクトリの
index.mdにorderを持たせる - 同一階層は
orderでソート、未指定は末尾へ流す
---
title: イントロダクション
order: 10
---
見出しとTOCが見づらい
問題: 見出しレベルがバラバラで、TOCが見づらい
解決策:
- h2/h3 中心に構造化し、h4以降は極力使わない
- TOC深度は 2 か 3 に固定(
app.config.tsのtoc.depth) - 長すぎる見出しは簡潔に書き直す
画像が表示されない
問題: 相対パスのズレでビルドエラーや画像が表示されない
解決策:
public/配下に配置し、絶対パス/images/...で参照- 記事固有の画像は
content/相対でも可だが、一貫性を優先
Hydration Mismatchエラー
問題: SSRとクライアントサイドレンダリングの不一致
Hydration completed but contains mismatches.
解決策: <ClientOnly> でラップ
<template>
<ClientOnly>
<UColorModeButton />
</ClientOnly>
</template>
メタデータ欠落の補完
問題: description 欠落や見出し深度の揺れで、検索やOGPが不完全
解決策:
- 前述のFrontmatter一括整形スクリプトを実行
title、description、order、draftを必須化- CI で Frontmatter の必須項目チェックを自動化
8. 運用効率化のチェックリストとツール
デプロイ前チェックリスト
- ✅ すべてのMarkdownに必須Frontmatter(title, description)が存在
- ✅ リンク切れチェック済み(
pnpm linkcheck) - ✅ 見出し深度の統一(h2/h3中心)
- ✅ 画像の最適化(WebP変換済み)
- ✅ Lighthouseスコア確認(Performance 75点以上)
リンクチェックスクリプト
# broken-link-checkerのインストール
pnpm add -D broken-link-checker
# package.jsonにスクリプトを追加
# "linkcheck": "blc http://localhost:3000 -ro"
# 実行(開発サーバー起動後)
pnpm linkcheck
リダイレクト管理
Vercel(vercel.json):
{
"redirects": [
{ "source": "/old-path", "destination": "/docs/new-path", "permanent": true },
{ "source": "/legacy/:slug", "destination": "/docs/:slug", "permanent": true }
]
}
Netlify(_redirects):
/old-path /docs/new-path 301
/legacy/:slug /docs/:slug 301
9. まとめと次のステップ
核心的な学び
- 設定ドリブンの運用:
app.config.tsで一元管理し、最小カスタマイズ運用 - Tailwind CSS統合:
@applyディレクティブで保守性向上、未使用CSS自動削除 - 段階的な改善: 初期は最小設定で運用開始、運用しながら必要な機能を追加
避けた落とし穴
- ❌ 大規模なカスタムCSS開発(Tailwindで解決)
- ❌ 過度な機能追加(必要最小限に留める)
- ❌ スタイルの過剰な上書き(既定テーマを尊重)
短期的な次のステップ(1-2ヶ月)
- パフォーマンス最適化の継続(Lighthouseスコア追跡)
- 記事テンプレの運用徹底
- 残りのコンポーネントのTailwind v4移行
中期的な次のステップ(3-6ヶ月)
- DocSearch導入(ページ数の閾値到達時)
- 更新頻度の高い領域の章立て再設計
- Nuxt Studio導入検討(執筆体験のさらなる向上)
長期的な次のステップ(6ヶ月以降)
- i18n対応(多言語展開)
- バージョニング機能(ドキュメントのバージョン管理)
- デザインシステムの構築(Tailwindベースのコンポーネントライブラリ化)
参考リソース
公式ドキュメント:
関連記事(本サイト内):
- 概要編: Docus導入の判断材料
- Tailwind CSS移行ガイド(予定): スタイルカスタマイズの詳細
- パフォーマンス最適化実践(予定): Core Web Vitals改善の詳細
この記事が、Docusの導入と運用の参考になれば幸いです。質問やフィードバックがあれば、ぜひコメントやSNSでお寄せください。
