未分類
フロントマター表示トラブルシューティング(2025-10-02)
発生した問題
DocPage コンポーネントで、タイトルの上に日付(publishedAt)とタグ(tags)を表示しようとしたが、以下の症状が発生:
- 日付とタグが表示されない
props.doc.publishedAtがnullprops.doc.tagsがnullv-if条件が false になり要素が非表示
- フロントマターが本文に表示される
- Markdown のフロントマターがパースされず、本文内にテキストとして表示
- 水平線(
---)や YAML 構文がそのまま出力
原因の詳細分析
1. SQLite データベースの状態
このプロジェクトでは Nuxt Content が SQLite を使用している:
設定(nuxt.config.ts):
content: {
documentDriven: true,
database: {
type: "sqlite" // ← SQLite データベースを使用
},
experimental: {
sqliteConnector: "native" // ← Node.js 22.6+ のネイティブ SQLite
}
}
データベースファイル:
- 場所:
.data/content/contents.sqlite - テーブル:
_content_pages,_content_info,_development_cache
SQLite に保存されたデータの確認結果:
SELECT id, publishedAt, tags FROM _content_pages
WHERE id LIKE '%setup-guide%';
-- 結果:
-- id: pages/nuxt-content-mdx-setup-guide.md
-- publishedAt: 2025-10-01T00:00:00.000Z ✅ 正しく保存されている
-- tags: ["nuxt","content","mdx","setup"] ✅ 正しく保存されている
重要な発見:
- SQLite データベースには正しくフロントマターが保存されていた
- つまり、データベースの問題ではなく、Markdown ファイルのパース時の問題
2. 根本原因:BOM (Byte Order Mark)
BOM とは:
- UTF-8 ファイルの先頭に付く特殊な文字
\ufeff(Zero Width No-Break Space) - Windows のテキストエディタ(特に古いエディタ)が自動で付加することがある
- YAML パーサーはファイルが
---で始まることを期待するが、BOM があると\ufeff---となり認識失敗
影響を受けたファイル:
# BOM が含まれていたファイル
content/nuxt-content-mdx-setup-guide.md # ← \ufeff---
content/nuxt-content-mdx-requirements.md # ← \ufeff---
content/index.mdx # ← \ufeff---
パース失敗の流れ:
- Markdown ファイルを読み込み
- BOM のせいで
---が認識されない → フロントマターとして扱われない - フロントマターが本文の一部として扱われる
publishedAtとtagsが抽出されずnullになる- 本文内にフロントマターの内容がそのまま表示される
3. CSS の競合問題
日付要素の配置にも問題があった:
問題のあった構造:
<div class="doc__body">
<p v-if="isoDate" class="doc__published">...</p> <!-- ← body 内にあった -->
<ContentRenderer :value="doc" />
</div>
問題のある CSS:
.doc__body :deep(div[data-content-id] > p:first-of-type) {
display: none; /* ← フロントマター削除用だが、日付も消してしまう */
}
このセレクタは本来「レンダリングされたフロントマターの段落を削除する」意図だったが、日付の段落も <p> 要素のため display: none が適用されてしまった。
解決方法
修正1: BOM の削除
実施内容:
# 正規表現で BOM を削除
# Before: \ufeff---
# After: ---
修正したファイル:
content/nuxt-content-mdx-setup-guide.mdcontent/nuxt-content-mdx-requirements.mdcontent/index.mdx
結果:
- フロントマターが正しくパースされるようになった
- SQLite に保存された
publishedAtとtagsが Vue コンポーネントに正しく渡されるようになった
修正2: 日付要素の配置変更
変更前:
<article class="doc">
<header v-if="doc.tags?.length" class="doc__header">...</header>
<div class="doc__body">
<p v-if="isoDate" class="doc__published">...</p> <!-- body 内 -->
<ContentRenderer :value="doc" />
</div>
</article>
変更後:
<article class="doc">
<header v-if="doc.tags?.length" class="doc__header">...</header>
<p v-if="isoDate" class="doc__published">...</p> <!-- body 外に移動 -->
<div class="doc__body">
<ContentRenderer :value="doc" />
</div>
</article>
効果:
- 日付要素が
.doc__body :deep()セレクタの影響を受けなくなった - CSS の競合が解消された
Nuxt Content の SQLite 連携フロー
┌─────────────────────┐
│ Markdown ファイル │
│ (content/*.md) │
└──────────┬──────────┘
│
│ 1. ファイル読み込み
│ ※ BOM があるとここで問題発生
↓
┌─────────────────────┐
│ Frontmatter Parser │
│ (YAML パース) │
└──────────┬──────────┘
│
│ 2. メタデータ抽出
│ - title
│ - publishedAt (Date 型に変換)
│ - tags
↓
┌─────────────────────┐
│ SQLite Database │
│ (.data/content/ │
│ contents.sqlite) │
└──────────┬──────────┘
│
│ 3. クエリで取得
│ queryCollection("pages")
│ .path("/xxx")
│ .first()
↓
┌─────────────────────┐
│ Vue Component │
│ (props.doc) │
│ - publishedAt ✅ │
│ - tags ✅ │
└─────────────────────┘
教訓とベストプラクティス
1. UTF-8 BOM に注意
BOM の確認方法:
# ファイルの先頭バイトを確認
head -n 1 file.md | od -c | head -n 1
# BOM がある場合: \357 \273 \277 が表示される
BOM を付けないエディタの設定:
- VS Code: デフォルトで BOM なし UTF-8(推奨)
- Notepad: "UTF-8 BOM なし" を選択
- vim:
set nobomb
新規ファイル作成時のチェックリスト:
- エディタが "UTF-8 without BOM" で保存されている
- フロントマターが
---で始まっている(先頭に空白や BOM がない) - YAML インデントが正しい(スペース2つ)
2. SQLite データベースのメンテナンス
キャッシュクリアが必要な状況:
- フロントマターの形式を変更したとき
content.config.tsのスキーマを変更したとき- パース結果がおかしいとき
クリア方法:
# データベースと Nuxt キャッシュを削除
rm -rf .nuxt .data
# 開発サーバー再起動で自動再構築
pnpm dev
注意:
- Windows では SQLite ファイルがロックされることがある
- 開発サーバーを停止してから削除する必要がある
3. デバッグ手順
フロントマターが表示されない場合の調査順序:
- Vue コンポーネントで値を確認
console.log('doc:', props.doc); console.log('publishedAt:', props.doc.publishedAt); console.log('tags:', props.doc.tags); - SQLite データベースを確認
sqlite3 .data/content/contents.sqlite \ "SELECT publishedAt, tags FROM _content_pages WHERE id LIKE '%ファイル名%';" - Markdown ファイルの BOM を確認
head -n 1 content/file.md | od -c - キャッシュをクリアして再起動
rm -rf .nuxt .data && pnpm dev
4. CSS セレクタの設計
:deep() 使用時の注意点:
:deep()は子孫要素すべてに影響する- 意図しない要素にスタイルが適用される可能性がある
- 特定の要素だけを対象にする場合、より具体的なセレクタを使う
良い例:
/* フロントマター由来の特定要素のみ削除 */
.doc__body :deep(div[data-content-id] > p:first-of-type),
.doc__body :deep(div[data-content-id] > p:first-of-type + ul),
.doc__body :deep(div[data-content-id] > p:first-of-type + ul + hr) {
display: none;
}
悪い例:
/* すべての p 要素に影響してしまう */
.doc__body :deep(p) {
display: none;
}
まとめ
今回のトラブルの原因:
- 主要因: Markdown ファイルの BOM により YAML フロントマターがパースされず、
publishedAtとtagsがnullになった - 副次的要因: 日付要素が
.doc__body内にあり、CSS の:deep()セレクタで非表示になった
解決策:
- BOM を削除してフロントマターを正しくパース
- 日付要素を
.doc__bodyの外に移動して CSS 競合を回避
SQLite の役割:
- Nuxt Content は SQLite を使ってコンテンツをインデックス化
- フロントマターのパースは SQLite 保存前に行われる
- BOM があると YAML パースが失敗し、SQLite にも
nullで保存される - 今回は BOM 削除後、開発サーバー再起動で SQLite が自動的に再構築され、正しいデータが保存された
予防策:
- UTF-8 BOM なしのエディタ設定を使用
- フロントマターの書式を厳密に守る(
---で開始、インデント2スペース) - CSS セレクタは意図しない要素に影響しないよう具体的に記述