Nuxt Content: SQLite フィールドマッピング仕様
概要
Nuxt Content v3では、マークダウンファイルを解析し、各要素をSQLiteのフィールドに自動マッピングします。このドキュメントでは、どの情報がどのフィールドに格納されるかを詳しく説明します。
SQLiteテーブル構造
_content_pages テーブル(例)
CREATE TABLE _content_pages (
id TEXT PRIMARY KEY, -- ファイルパス(コレクション名/ファイル名)
title VARCHAR, -- タイトル(H1 または frontmatter)
body TEXT, -- 本文のAST(JSON形式)
description VARCHAR, -- 説明文(frontmatter)
extension VARCHAR, -- 拡張子(md, mdx など)
meta TEXT, -- メタ情報(JSON)
navigation TEXT DEFAULT true, -- ナビゲーション表示フラグ
path VARCHAR, -- URLパス
publishedAt DATE NULL, -- 公開日(frontmatter)
seo TEXT DEFAULT '{}', -- SEO情報(JSON)
stem VARCHAR, -- ファイル名(拡張子なし)
tags TEXT NULL, -- タグ(JSON配列)
__hash__ TEXT UNIQUE -- ハッシュ値(整合性チェック用)
);
フィールドマッピングルール
1. id(主キー)
ルール: コレクション名/ファイルパス
ファイル: content/nuxt-setup.md
↓
id: "pages/nuxt-setup.md"
ファイル: content/blog/2025-10-04/article.md
↓
id: "pages/blog/2025-10-04/article.md"
特徴:
- ファイルパスがそのままIDになる
- コレクション名(デフォルト:
pages)が接頭辞として付く - 一意性が保証される
2. title(タイトル)
優先順位:
- フロントマターの
title(最優先) - 本文の最初の
# 見出し(H1) - ファイル名(最後の手段)
例:
---
title: "Nuxt Content セットアップ" ← これが優先
---
# 実際の見出し ← frontmatter がなければこれ
本文...
↓ SQLiteに格納
title: "Nuxt Content セットアップ"
フロントマターなしの場合:
# Nuxt Content セットアップ ← これがタイトルになる
本文...
↓ SQLiteに格納
title: "Nuxt Content セットアップ"
3. body(本文)
ルール: マークダウン本文を AST(Abstract Syntax Tree)形式 でJSON化
元のマークダウン:
# タイトル
これは**太字**です。
- リスト1
- リスト2
SQLiteに格納されるJSON(minimark形式):
{
"type": "minimark",
"value": [
{
"type": "heading",
"depth": 1,
"value": [{ "type": "text", "value": "タイトル" }]
},
{
"type": "paragraph",
"value": [
{ "type": "text", "value": "これは" },
{ "type": "strong", "value": [{ "type": "text", "value": "太字" }] },
{ "type": "text", "value": "です。" }
]
},
{
"type": "list",
"ordered": false,
"value": [
{ "type": "listItem", "value": [{ "type": "text", "value": "リスト1" }] },
{ "type": "listItem", "value": [{ "type": "text", "value": "リスト2" }] }
]
}
],
"toc": {
"title": "",
"searchDepth": 2,
"depth": 2,
"links": []
}
}
特徴:
- マークダウンをそのまま保存するのではなく、構造化されたASTを保存
- レンダリング時にASTからHTMLを生成
- 目次(TOC)情報も含まれる
4. description(説明文)
ルール: フロントマターの description フィールドのみ
---
description: "Nuxt Contentのセットアップ方法を解説します"
---
# タイトル
本文...
↓ SQLiteに格納
description: "Nuxt Contentのセットアップ方法を解説します"
注意:
- フロントマターにない場合は
""(空文字列) - 本文から自動抽出されることはない
5. extension(拡張子)
ルール: ファイルの拡張子(. なし)
ファイル: content/article.md
↓
extension: "md"
ファイル: content/component.mdx
↓
extension: "mdx"
6. meta(メタ情報)
ルール: フロントマターのカスタムフィールドが格納される(JSON形式)
---
title: "記事タイトル"
author: "田中太郎"
category: "技術"
draft: true
customField: "カスタム値"
---
↓ SQLiteに格納(titleなど標準フィールド以外)
{
"author": "田中太郎",
"category": "技術",
"draft": true,
"customField": "カスタム値"
}
特徴:
title,description,publishedAt,tagsなどは専用フィールドに格納- それ以外のフィールドは
metaにJSON形式で格納 - アクセス方法:
article.meta.author
7. navigation(ナビゲーション表示フラグ)
ルール: フロントマターの navigation フィールド(デフォルト: true)
---
navigation: false ← ナビゲーションに表示しない
---
↓ SQLiteに格納
navigation: "false"
デフォルト値:
---
# navigation フィールドがない場合
---
↓ SQLiteに格納
navigation: "true"
8. path(URLパス)
ルール: ファイルパスから生成されるURL
ファイル: content/nuxt-setup.md
↓
path: "/nuxt-setup"
ファイル: content/blog/2025-10-04/article.md
↓
path: "/blog/2025-10-04/article"
ファイル: content/docs/getting-started.md
↓
path: "/docs/getting-started"
特徴:
- 拡張子(
.md,.mdx)は削除される content/は削除される- 先頭に
/が付く
9. publishedAt(公開日)
ルール: フロントマターの publishedAt または date フィールド
---
publishedAt: 2025-10-04
---
↓ SQLiteに格納(ISO 8601形式)
publishedAt: "2025-10-04T00:00:00.000Z"
注意:
content.config.tsでz.coerce.date()を指定すると日付型に変換- 指定なしの場合は文字列のまま
- フロントマターにない場合は
NULL
10. seo(SEO情報)
ルール: 自動生成される SEO メタデータ(JSON形式)
---
title: "記事タイトル"
description: "記事の説明"
---
↓ SQLiteに格納
{
"title": "記事タイトル",
"description": "記事の説明"
}
デフォルト値(フロントマターなし):
{
"title": "ファイル名",
"description": ""
}
11. stem(ファイル名)
ルール: ファイル名(拡張子なし)
ファイル: content/nuxt-setup.md
↓
stem: "nuxt-setup"
ファイル: content/blog/article.mdx
↓
stem: "article"
12. tags(タグ)
ルール: フロントマターの tags フィールド(JSON配列形式)
---
tags: [nuxt, content, mdx]
highlight: "feature"
---
↓ SQLiteに格納
["nuxt", "content", "mdx"]
別の書き方(YAML形式):
---
tags:
highlight: "feature"
- nuxt
- content
- mdx
---
↓ SQLiteに格納(同じ結果)
["nuxt", "content", "mdx"]
注意:
- フロントマターにない場合は
NULL - 空配列
[]とNULLは区別される
13. hash(ハッシュ値)
ルール: ファイル内容のハッシュ値(SHA-256ベース)
ファイル内容が変わると __hash__ も変わる
↓
キャッシュ無効化や整合性チェックに使用
用途:
- コンテンツの変更検知
- キャッシュ無効化
- データベースの整合性チェック
フロントマター解析の優先順位
1. 標準フィールド(専用カラムに格納)
---
title: "タイトル" # → title カラム
description: "説明" # → description カラム
publishedAt: 2025-10-04 # → publishedAt カラム
tags: [tag1, tag2] # → tags カラム
highlight: "feature"
navigation: false # → navigation カラム
---
2. カスタムフィールド(meta カラムに JSON 格納)
---
author: "著者名" # → meta.author
category: "カテゴリ" # → meta.category
draft: true # → meta.draft
customField: "カスタム値" # → meta.customField
---
実際のデータ例
マークダウンファイル
ファイル: content/nuxt-content-sqlite-architecture.md
---
title: "Nuxt Content の SQLite アーキテクチャ解説"
description: "Nuxt Content v3 の内部構造を解説"
publishedAt: 2025-10-02
tags: [nuxt, content, sqlite, architecture]
highlight: "feature"
author: "開発者"
category: "技術解説"
---
# Nuxt Content の SQLite アーキテクチャ解説
## はじめに
Nuxt Content v3 では...
SQLiteレコード
{
"id": "pages/nuxt-content-sqlite-architecture.md",
"title": "Nuxt Content の SQLite アーキテクチャ解説",
"body": {
"type": "minimark",
"value": [
{
"type": "heading",
"depth": 1,
"value": [{ "type": "text", "value": "Nuxt Content の SQLite アーキテクチャ解説" }]
},
{
"type": "heading",
"depth": 2,
"value": [{ "type": "text", "value": "はじめに" }]
},
{
"type": "paragraph",
"value": [{ "type": "text", "value": "Nuxt Content v3 では..." }]
}
],
"toc": {
"title": "",
"searchDepth": 2,
"depth": 2,
"links": [
{ "id": "はじめに", "depth": 2, "text": "はじめに" }
]
}
},
"description": "Nuxt Content v3 の内部構造を解説",
"extension": "md",
"meta": {
"author": "開発者",
"category": "技術解説"
},
"navigation": true,
"path": "/nuxt-content-sqlite-architecture",
"publishedAt": "2025-10-02T00:00:00.000Z",
"seo": {
"title": "Nuxt Content の SQLite アーキテクチャ解説",
"description": "Nuxt Content v3 の内部構造を解説"
},
"stem": "nuxt-content-sqlite-architecture",
"tags": ["nuxt", "content", "sqlite", "architecture"],
"__hash__": "L4S9l2rcBELA6hSUjDWpPK1rdi6NAtetbVcFo2s0GbM"
}
content.config.ts でのスキーマ定義
スキーマ定義の例
import { defineContentConfig, defineCollection, z } from "@nuxt/content"
export default defineContentConfig({
collections: {
pages: defineCollection({
type: "page",
source: "**/*.{md,mdx}",
schema: z.object({
// 必須フィールド
title: z.string(),
// オプションフィールド(型変換あり)
publishedAt: z.coerce.date().optional(),
updatedAt: z.coerce.date().optional(),
// オプションフィールド(配列)
tags: z.array(z.string()).optional(),
// オプションフィールド(文字列)
description: z.string().optional(),
author: z.string().optional(),
category: z.string().optional(),
// オプションフィールド(真偽値)
draft: z.boolean().optional(),
navigation: z.boolean().default(true),
})
})
}
})
スキーマ定義の効果
- 型チェック
// ビルド時にフロントマターの型をチェック publishedAt: "invalid-date" // ← エラー tags: "string" // ← エラー(配列が必要) - 型変換(coerce)
publishedAt: z.coerce.date() // フロントマター publishedAt: "2025-10-04" // ↓ 自動で Date 型に変換 publishedAt: new Date("2025-10-04T00:00:00.000Z") - デフォルト値
navigation: z.boolean().default(true) // フロントマターになくても true が設定される
BOM(Byte Order Mark)問題
BOM とは
- UTF-8ファイルの先頭に付く
0xEF 0xBB 0xBF(文字では\ufeff) - Windowsのメモ帳などが自動で付加することがある
問題
# BOM あり(パース失敗)
---
title: タイトル
---
# BOM なし(パース成功)
---
title: タイトル
---
YAMLパーサー(js-yaml)は:
- ファイルが
---で始まることを期待 - BOMがあると
\ufeff---と認識 - YAMLとして認識できず、フロントマター全体を無視
publishedAtとtagsが抽出されずnullになる
検出方法
# BOMをチェック
head -n 1 content/file.md | od -c | head -n 1
# BOMがあれば: 357 273 277 が表示される
削除方法
# VS Code: 右下の "UTF-8 with BOM" → "UTF-8" に変更
# または正規表現で削除
sed -i '1s/^\xEF\xBB\xBF//' content/file.md
予防方法
- VS Codeの設定
{ "files.encoding": "utf8", "files.autoGuessEncoding": false } - .editorconfig
[*.{md,mdx}] charset = utf-8
フィールドアクセス方法
Vue コンポーネント内
<script setup>
const { data: article } = await useAsyncData('article', () =>
queryCollection('pages').path('/nuxt-setup').first()
)
</script>
<template>
<div>
<!-- 標準フィールド -->
<h1>{{ article.title }}</h1>
<p>{{ article.description }}</p>
<time>{{ article.publishedAt }}</time>
<!-- タグ(配列) -->
<div>
<span v-for="tag in article.tags" :key="tag">
{{ tag }}
</span>
</div>
<!-- カスタムフィールド(meta) -->
<p>著者: {{ article.meta.author }}</p>
<p>カテゴリ: {{ article.meta.category }}</p>
<!-- 本文レンダリング -->
<ContentRenderer :value="article" />
</div>
</template>
TypeScript 型定義
interface ParsedContent {
id: string
title: string
body: {
type: 'minimark'
value: any[]
toc: {
title: string
searchDepth: number
depth: number
links: Array<{
id: string
depth: number
text: string
}>
}
}
description: string
extension: string
meta: Record<string, any>
navigation: boolean
path: string
publishedAt: Date | null
seo: {
title: string
description: string
}
stem: string
tags: string[] | null
__hash__: string
}
よくある質問
Q1: フロントマターに書いたフィールドが meta に入らない
原因: 標準フィールドは専用カラムに格納される
---
title: "タイトル" # → title カラム(meta には入らない)
publishedAt: 2025-10-04 # → publishedAt カラム(meta には入らない)
author: "著者" # → meta.author(カスタムフィールド)
---
Q2: 本文の最初の見出しがタイトルにならない
原因: フロントマターの title が優先される
---
title: "フロントマターのタイトル" ← これが優先
---
# 本文の見出し ← 無視される
解決策: フロントマターの title を削除
---
# title フィールドを削除
---
# 本文の見出し ← これがタイトルになる
Q3: タグが null になる
原因1: BOMが付いている
# BOMをチェック
head -n 1 content/file.md | od -c
原因2: YAML構文が間違っている
# ✅ 正しい
tags: [tag1, tag2]
highlight: "feature"
# または
tags:
highlight: "feature"
- tag1
- tag2
# ❌ 間違い
tags: tag1, tag2
highlight: "feature"
原因3: スキーマ定義がない
// content.config.ts
tags: z.array(z.string()).optional()
highlight: "feature"
まとめ
フィールドマッピングの優先順位
- id: ファイルパス(自動生成)
- title: フロントマター → H1 → ファイル名
- body: マークダウン本文(AST形式)
- description: フロントマターのみ
- publishedAt: フロントマター(date または publishedAt)
- tags: フロントマター(配列形式)
- meta: その他のカスタムフィールド
重要なポイント
- ✅ フロントマターの標準フィールドは専用カラムに格納
- ✅ カスタムフィールドは
metaに JSON 格納 - ✅ 本文はAST形式でJSON化される
- ⚠️ BOMがあるとフロントマター全体が無視される
- ⚠️
titleはフロントマターが最優先