---
title: ページスキーマ
date: 2026-02-24T23:33:16.422Z
updated: 2026-05-06T05:53:18.000Z
tags:
  - garden
url: https://baku89.com/ja/garden/page-schema
---

# ページスキーマ

ページのデータモデルについて。型定義は `types/page.ts`、RxDB スキーマは `utils/rxdb/pageSchema.ts` にある。

## Page インターフェース

`Page` は frontmatter の情報に加えて、パース済みの本文やアウトリンクを含む完全なページデータ。

主なフィールド:

- `path` — ファイルパス（拡張子なし）
- `slug` — 正規化済みの識別子（→ [information-architecture.md](./information-architecture)）
- `content_id` — ULID。リネーム追跡用の不変 ID
- `body` — パース済みの本文 AST（`MDCRoot`、en/ja の多言語対応）
- `source` — Markdown のソースコード（非 admin には非公開）
- `source_hash` — `source` の `ohash` フィンガープリント。content equality 判定や redundant write skip に使う
- `outlinks` — このページから出ている内部リンクの配列

赤リンク（バックリンクのみで実体が無いページ）は `Page` のフィールドではなく、解決時の `resolution: 'redlink'` という別経路で表現する。

## Frontmatter

YAML frontmatter のスキーマは Zod で定義している（`FrontmatterSchema`）。Obsidian 互換を意識したフォーマット。

```
content_id: 01HQ3K5P0G3Z1VXYZ2ABC3DEF
title: Hello World
title_locale:
  ja: こんにちは、世界！
date: 2025-08-20
updated: 2025-08-21
visibility: public
draft: false
layout: grid
tags:
  - video
  - diary
description: A sample page
thumbnail: https://example.com/image.jpg
```

主なフィールド:

- `content_id` — ULID。省略時は自動生成
- `title` — 文字列（`title_locale` で `{en, ja}` の locale 別タイトルを上書き可能）
- `date` — 代表日時（イベント開始日や公開日）。ISO 8601 の日付または日時
- `until` — 期間ものイベントの終了日時。未設定なら index 投入時に `date` をコピー
- `updated` — 訪問者向けの更新日時（ソートや RSS で使う）。明示的に著者がスタンプする運用（保存時に自動で進めない）
- `visibility` — `'public'` / `'unlisted'` / `'protected'` / `'private'` / `'contextual'`（→ [information-architecture.md](./information-architecture)）
- `draft` — `true` で noindex/nofollow + `<html>` に `draft` クラス付与。デフォルト `false` は「公開」
- `layout` — `'page'`（デフォルト）/ `'grid'` / `'table'` 等
- `tags` — tag link の配列。slug に正規化されて `outlinks` に変換される
- `aliases` — ページの別名（リダイレクト用）
- `redirect` — 別ページへの転送

## Link 型

ページから出ていくリンクの型。

```
interface Link {
  type: 'tag' | 'folder' | 'link'
  slug: string
  hash?: string
}
```

- `tag` — frontmatter の `tags` から生成
- `folder` — パスの親ディレクトリから暗黙的に生成
- `link` — 本文中のハイパーリンクから生成

## RxDB スキーマ

DB 上では `PageDoc = {id, content, softDeleted}` の形で保存される。

- `id` — `content.content_id` 由来の primary key
- `content` — `Page` 全体（`additionalProperties: true`、MDC AST のような複雑な JSON を JSON Schema で完全モデル化するのは現実的でないので開けてある）
- `softDeleted` — soft delete フラグ。RxDB native の `_deleted` ではなく通常フィールドなので、replication が他のフィールド変更と同じ経路で削除を伝播する

サーバ側のみ、`PageDoc` に `seq: number`（required + indexed）が追加される。`pageSchema.ts` で `import.meta.server` 分岐により schema を fork し、レプリケーション pull のレスポンス組み立て時にサーバ側で `seq` を destructure で除去してからクライアントへ送る。詳細は [replication.md](./replication)。

主なインデックス: `content.content_id`, `content.slug`, `softDeleted`, `content.date`, `content.until`, `content.updated`, `content.visibility.type`、および `[visibility.type, date/until/updated]` の compound index。サーバ側のみ `seq`。

slug の重複チェックは `upsertPage` メソッド内で行っている。同じ `slug` で異なる `content_id` のドキュメントが既にあれば、パスに `_2`, `_3`, … のサフィックスを付けて自動リネームし、toast で通知する。
