開発nuxt-content-docsメモ
Nuxt Pages と Nuxt Content の統合仕組み
このドキュメントでは、Nuxt の pages ディレクトリと Nuxt Content の content ディレクトリの違い、SQLite の役割、記事一覧への統合方法を包括的に解説します。
📚 目次
基本概念
Nuxt のルーティング
Nuxt 自体の機能(Nuxt Content とは別):
app/pages/ディレクトリにファイルを配置すると、自動的にルート(URL)が生成される- これは Nuxt の基本機能で、SQLite とは無関係
- 例:
app/pages/index.vue→/(トップページ)app/pages/about.vue→/aboutapp/pages/flowchart/customer-type-identification.vue→/flowchart/customer-type-identification
Nuxt Content の役割
Nuxt Content は別のモジュール:
content/ディレクトリに Markdown/MDX ファイルを配置- Nuxt Content が自動的に SQLite データベースに変換・保存
- これにより高速な記事検索・取得が可能に
SQLite の役割
Nuxt Content と SQLite の関係
content/ SQLite Database
├── article1.md ─────→ ┌─────────────────┐
├── article2.mdx ─────→ │ pages テーブル │
└── guide.md ─────→ │ │
│ - title │
│ - path │
│ - publishedAt │
│ - tags │
│ - content │
└─────────────────┘
重要なポイント:
- SQLite は Nuxt Content 専用
content/ディレクトリのファイルのみが SQLite に保存されるpages/ディレクトリの.vueファイルは SQLite に保存されない
- 自動変換プロセス
- Nuxt Content が起動時に
content/を監視 - Markdown/MDX を解析してメタデータと本文を抽出
- SQLite の
pagesコレクション(テーブル)に保存 - これはNuxt Content の仕様
- Nuxt Content が起動時に
- 開発環境 vs 本番環境
- 開発: Node.js 22.6 の
node:sqliteモジュールを使用 - 本番 (Cloudflare Pages): ネイティブ SQLite を使用
- 開発: Node.js 22.6 の
なぜ Nuxt Content は SQLite を使うのか?
1. 高速な検索・クエリ処理
Markdown ファイルを毎回読み込んで解析するのは非常に遅いです:
❌ ファイルシステムアプローチ(遅い)
リクエスト → ファイル読み込み → Markdown解析 → HTML変換 → レスポンス
(100ms) (50ms) (30ms) = 180ms/記事
✅ SQLite アプローチ(速い)
リクエスト → SQLiteクエリ → レスポンス
(5ms) = 5ms/記事
2. 複雑な検索が可能
SQLite なら SQL で柔軟にクエリできます:
// タグで検索
queryCollection('pages')
.where('tags', 'contains', 'nuxt')
.all()
// 日付範囲で検索
queryCollection('pages')
.where('publishedAt', '>', '2025-01-01')
.sort('publishedAt', 'desc')
.all()
// 全文検索
queryCollection('pages')
.where('content', 'contains', 'SQLite')
.all()
ファイルシステムだと、これらの検索はすべてのファイルを読み込んでJavaScript で処理する必要があります(遅い)。
3. ビルド時の事前処理
開発時・ビルド時:
content/article.md → Markdown解析 → SQLiteに保存(1回だけ)
実行時:
リクエスト → SQLiteから取得(解析不要、超高速)
Markdown の解析は重い処理なので、1回だけ実行して結果を保存しておきます。
4. メタデータの一括取得
記事一覧を表示する場合:
// ❌ ファイルシステムなら
// 100個の記事 × 各ファイル読み込み = 遅い
const files = await readdir('content')
const articles = await Promise.all(
files.map(async file => {
const content = await readFile(file)
const parsed = await parseMarkdown(content) // 遅い
return parsed.frontmatter
})
)
// ✅ SQLite なら
// 1回のクエリで全メタデータ取得 = 速い
const articles = await queryCollection('pages')
.only(['title', 'publishedAt', 'tags'])
.all()
5. Cloudflare Pages などのエッジ環境での最適化
Cloudflare Pages や他のエッジ環境では:
- ファイルシステムへのアクセスが制限される場合がある
- SQLite なら単一ファイルで完結
- エッジランタイムでも高速動作
パフォーマンス比較
SQLite を使わない場合(毎回 Markdown を読む):
100記事のサイトで記事一覧表示
→ 100ファイル読み込み + 100回の解析 = 数秒かかる ❌
SQLite を使う場合(事前に変換済み):
100記事のサイトで記事一覧表示
→ 1回のSQLクエリ = 数ミリ秒 ✅
まとめ: SQLite を使う理由
| 理由 | 詳細 |
|---|---|
| 🚀 パフォーマンス | Markdown の毎回の解析を避ける |
| 🔍 検索機能 | SQL で複雑なクエリが可能 |
| ⚡ 事前処理 | ビルド時に1回だけ変換 |
| 📊 一括取得 | メタデータを効率的に取得 |
| 🌐 エッジ対応 | エッジ環境での最適化 |
結論: SQLite は Nuxt Content のパフォーマンス最適化のための仕組みです。毎回 Markdown ファイルを読んで解析するより、事前に SQLite に変換しておく方が圧倒的に速いからです。
ディレクトリの違い
content/ ディレクトリ(Nuxt Content)
| 特徴 | 詳細 |
|---|---|
| ファイル形式 | .md, .mdx |
| ルーティング | Nuxt Content が自動生成 |
| データベース | ✅ SQLite に自動保存される |
| 取得方法 | queryCollection('pages').all() |
| 一覧への追加 | 自動 - SQLite から取得するだけ |
| 用途 | ブログ記事、ドキュメント、通常のコンテンツ |
例:
content/
├── article1.md → /article1 (Nuxt Content が生成)
└── guide/setup.md → /guide/setup (Nuxt Content が生成)
app/pages/ ディレクトリ(Nuxt 標準)
| 特徴 | 詳細 |
|---|---|
| ファイル形式 | .vue |
| ルーティング | Nuxt が自動生成(ファイルシステムルーティング) |
| データベース | ❌ SQLite には保存されない |
| 取得方法 | router.getRoutes() でルート情報を取得 |
| 一覧への追加 | 手動 - メタ情報を定義して統合が必要 |
| 用途 | カスタム Vue コンポーネント、複雑な UI、外部スクリプト使用ページ |
例:
app/pages/
├── index.vue → / (Nuxt が生成)
├── about.vue → /about (Nuxt が生成)
└── flowchart/customer-type-identification.vue → /flowchart/customer-type-identification (Nuxt が生成)
統合の仕組み
現在の実装(app/pages/index.vue)
<script setup>
import { queryCollection } from "#imports"
// 1. Nuxt Content から SQLite 経由で記事を取得
const { data: contentArticles } = await useAsyncData('all-articles', () =>
queryCollection('pages').all() // ← SQLite から全記事を取得
)
// 2. Nuxt Router から pages ディレクトリの情報を取得
const router = useRouter()
const customPages = computed(() => {
const routes = router.getRoutes() // ← Nuxt のルート情報を取得
return routes
.filter(route => route.meta?.includeInList === true) // ← 条件でフィルタ
.map(route => ({
path: route.path,
title: route.meta?.title,
publishedAt: route.meta?.publishedAt,
tags: route.meta?.tags
}))
})
// 3. 両方を結合して一覧表示
const articles = computed(() => {
const content = contentArticles.value || []
return [...content, ...customPages.value] // ← 統合
})
</script>
データフロー図
┌─────────────────────────────────────────────────────────────┐
│ 記事一覧の統合 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────┐ ┌─────────────────────────┐
│ content/ │ │ app/pages/ │
│ ├── article1.md │ │ └── flowchart/ │
│ ├── article2.mdx │ │ └── custom.vue │
│ └── guide.md │ │ │
└──────────┬──────────┘ └────────────┬────────────┘
│ │
│ Nuxt Content が │ Nuxt が
│ 自動変換 │ ルート生成
↓ ↓
┌─────────────────────┐ ┌─────────────────────────┐
│ SQLite Database │ │ Router (メモリ上) │
│ pages テーブル │ │ ルート情報 │
└──────────┬──────────┘ └────────────┬────────────┘
│ │
│ queryCollection() │ router.getRoutes()
│ で取得 │ で取得
↓ ↓
┌─────────────────────┐ ┌─────────────────────────┐
│ contentArticles │ │ customPages │
│ (SQLite の記事) │ │ (meta.includeInList │
│ │ │ が true のページ) │
└──────────┬──────────┘ └────────────┬────────────┘
│ │
└──────────────┬───────────────────┘
↓
┌─────────────────┐
│ articles │
│ (統合された一覧) │
└─────────────────┘
↓
テーブル表示
実装の詳細
1. Nuxt Content の記事(SQLite から取得)
取得コード:
const { data: contentArticles } = await useAsyncData('all-articles', () =>
queryCollection('pages').all()
)
特徴:
content/配下のすべての Markdown/MDX が自動的に SQLite に保存されるqueryCollection('pages')で SQLite から取得- メタデータ(title, publishedAt, tags など)も自動抽出
- 追加作業不要 - ファイルを置くだけで一覧に表示
2. カスタム Vue ページ(Router から取得)
取得コード:
const router = useRouter()
const customPages = computed(() => {
const routes = router.getRoutes()
return routes
.filter(route => route.meta?.includeInList === true)
.map(route => ({
path: route.path,
title: route.meta?.title || route.name || route.path,
publishedAt: route.meta?.publishedAt || new Date().toISOString().split('T')[0],
tags: route.meta?.tags || []
}))
})
特徴:
router.getRoutes()で Nuxt のすべてのルート情報を取得- SQLite とは無関係 - メモリ上のルート情報のみ
definePageMeta({ includeInList: true })でフィルタ- 手動でメタ情報を定義する必要がある
3. 統合処理
統合コード:
const articles = computed(() => {
const content = contentArticles.value || []
return [...content, ...customPages.value]
})
特徴:
- JavaScript の配列として単純に結合
- SQLite への追加はしない - あくまで表示用の統合
- ソート・ページネーションは結合後に実施
よくある質問
Q1: pages ディレクトリの Vue ファイルは SQLite に保存されますか?
A: いいえ、保存されません。
- SQLite に保存されるのは
content/ディレクトリの Markdown/MDX のみ pages/の.vueファイルは Nuxt のルーティングシステムでのみ管理- 記事一覧には
router.getRoutes()で取得した情報を一時的に結合しているだけ
Q2: 記事一覧に表示するために SQLite に追加する処理を実装していますか?
A: いいえ、SQLite への追加処理は実装していません。
- Nuxt Content の記事: SQLite から取得(自動)
- カスタム Vue ページ: Router から取得(手動メタ情報)
- 両方をメモリ上で結合して表示しているだけ
- データベースへの書き込みは一切なし
Q3: includeInList: true の設定はどこに保存されますか?
A: Vue ファイルのメタ情報としてコード内に保存されます。
<script setup>
definePageMeta({
includeInList: true, // ← これは Vue ファイル内のメタ情報
title: 'ページタイトル'
})
</script>
- Nuxt がビルド時にこの情報をルートメタデータに組み込む
- 実行時に
router.getRoutes()で取得可能 - SQLite には保存されない - あくまでルート情報の一部
Q4: この仕組みは Nuxt の仕様ですか、Nuxt Content の仕様ですか?
A: 両方の仕様を組み合わせています。
| 機能 | 仕様 |
|---|---|
pages/ ディレクトリのルーティング | Nuxt の仕様 |
content/ → SQLite 自動変換 | Nuxt Content の仕様 |
router.getRoutes() | Nuxt の仕様 |
queryCollection() | Nuxt Content の仕様 |
| 記事一覧への統合処理 | 当プロジェクトの独自実装 |
まとめ
データソースの整理
| データソース | 取得方法 | SQLite | 一覧への追加 |
|---|---|---|---|
content/ の Markdown/MDX | queryCollection('pages').all() | ✅ 自動保存される | 自動(SQLite から取得) |
pages/ の Vue ファイル | router.getRoutes() | ❌ 保存されない | 手動(メタ情報定義) |
重要なポイント
- SQLite は Nuxt Content 専用
content/のファイルのみが自動的に SQLite に保存される- これは Nuxt Content の仕様
- pages ディレクトリは Nuxt の標準機能
- ファイルシステムベースのルーティング
- SQLite とは無関係
- ルート情報はメモリ上で管理
- 記事一覧の統合は独自実装
- SQLite から Nuxt Content の記事を取得
- Router から pages の情報を取得
- メモリ上で結合して表示
- SQLite への追加処理は存在しない
- includeInList: true の役割
- Vue ファイルのメタ情報として定義
- Router のルート情報に含まれる
- フィルタ条件として使用
- SQLite には保存されない
この仕組みにより、Nuxt Content の記事(SQLite ベース)とカスタム Vue ページ(Router ベース)を柔軟に統合した記事一覧システムが実現できています。