cmx-sdk 0.2.9 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- package/dist/add-studio-YUDYE2OH.js +0 -0
- package/dist/chunk-7TDMLYBI.js +0 -0
- package/dist/chunk-EDXXR5BE.js +0 -0
- package/dist/chunk-FPQYL5GE.js +0 -0
- package/dist/chunk-IIQLQIDP.js +0 -0
- package/dist/chunk-NZQ6SBFS.js +0 -0
- package/dist/{chunk-EZMBZWH7.js → chunk-Y3S3K6M3.js} +73 -28
- package/dist/cli.js +215 -3
- package/dist/init-FLRQXJX4.js +0 -0
- package/dist/{interactive-menu-FYVOQSTL.js → interactive-menu-PYMJB5LY.js} +4 -1
- package/dist/studio-HAS6DYLO.js +0 -0
- package/dist/{update-sdk-KJZ6VB4M.js → update-sdk-ZXMWQF3I.js} +2 -1
- package/dist/update-studio-TWCYSYIS.js +0 -0
- package/package.json +14 -16
- package/templates/AGENTS.md +0 -173
- package/templates/CLAUDE.md +0 -28
- package/templates/claude/commands/check.md +0 -64
- package/templates/claude/commands/next-action.md +0 -66
- package/templates/claude/skills/cmx-cache/SKILL.md +0 -50
- package/templates/claude/skills/cmx-cache/references/cache-patterns.md +0 -153
- package/templates/claude/skills/cmx-component/SKILL.md +0 -108
- package/templates/claude/skills/cmx-component/references/component-schema.md +0 -123
- package/templates/claude/skills/cmx-content/SKILL.md +0 -158
- package/templates/claude/skills/cmx-content/references/migration-patterns.md +0 -120
- package/templates/claude/skills/cmx-content/references/seed-patterns.md +0 -146
- package/templates/claude/skills/cmx-dev/SKILL.md +0 -266
- package/templates/claude/skills/cmx-dev/references/api-patterns.md +0 -220
- package/templates/claude/skills/cmx-dev/references/cli-reference.md +0 -54
- package/templates/claude/skills/cmx-form/SKILL.md +0 -103
- package/templates/claude/skills/cmx-form/references/form-template.md +0 -152
- package/templates/claude/skills/cmx-migrate/SKILL.md +0 -501
- package/templates/claude/skills/cmx-migrate/references/analysis-guide.md +0 -127
- package/templates/claude/skills/cmx-migrate/references/html-to-mdx.md +0 -99
- package/templates/claude/skills/cmx-migrate/references/intermediate-format.md +0 -196
- package/templates/claude/skills/cmx-migrate/references/tool-setup.md +0 -150
- package/templates/claude/skills/cmx-schema/SKILL.md +0 -159
- package/templates/claude/skills/cmx-schema/references/field-types.md +0 -164
- package/templates/claude/skills/cmx-schema/references/migration-scenarios.md +0 -44
- package/templates/claude/skills/cmx-seo/SKILL.md +0 -54
- package/templates/claude/skills/cmx-seo/references/seo-patterns.md +0 -216
- package/templates/claude/skills/cmx-style/SKILL.md +0 -48
- package/templates/claude/skills/cmx-style/references/style-patterns.md +0 -114
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cmx-schema
|
|
3
|
-
description: |
|
|
4
|
-
CMXのデータタイプ・コレクションのJSON定義を生成し、SDK API 経由で自動登録する。
|
|
5
|
-
既存サイトのデータ構造をCMXに移行する際のスキーマ変換・定義生成を担う。
|
|
6
|
-
トリガー: 「スキーマを生成」「データタイプのJSON」「コレクションのJSON」「移行用のスキーマ」
|
|
7
|
-
「CMXにインポートするJSON」「データ構造を定義」「サイトを移行」「データタイプを作りたい」
|
|
8
|
-
「コレクションを追加したい」「フィールド定義」「JSON定義」「インポート用JSON」
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# CMX スキーマ JSON 生成ガイド
|
|
12
|
-
|
|
13
|
-
既存サイトのデータ構造をCMXに移行するためのJSON定義を生成する。
|
|
14
|
-
生成したJSONは、ユーザーに確認後、SDK API 経由で自動登録する。
|
|
15
|
-
|
|
16
|
-
## タスク判定
|
|
17
|
-
|
|
18
|
-
- **フィールドタイプの詳細やオプションを確認** → [references/field-types.md](references/field-types.md)
|
|
19
|
-
- **サイト種別ごとの構成例が必要** → [references/migration-scenarios.md](references/migration-scenarios.md)
|
|
20
|
-
|
|
21
|
-
## データタイプ JSON
|
|
22
|
-
|
|
23
|
-
```json
|
|
24
|
-
{
|
|
25
|
-
"slug": "英数字-ハイフン",
|
|
26
|
-
"name": "表示名",
|
|
27
|
-
"description": "説明(任意)",
|
|
28
|
-
"fields": [
|
|
29
|
-
{
|
|
30
|
-
"key": "英数字_アンダースコア",
|
|
31
|
-
"label": "表示ラベル",
|
|
32
|
-
"type": "text",
|
|
33
|
-
"required": true,
|
|
34
|
-
"description": "ヘルプテキスト(任意)",
|
|
35
|
-
"options": {}
|
|
36
|
-
}
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
フィールドの `type` は `text`, `textarea`, `richtext`, `number`, `date`, `datetime`, `boolean`, `select`, `multiselect`, `image`, `file`, `json`, `url`, `email`, `relation` のいずれか。各タイプの `options` 詳細は [references/field-types.md](references/field-types.md) を参照。
|
|
42
|
-
|
|
43
|
-
## コレクション JSON
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"type": "post",
|
|
48
|
-
"slug": "英数字-ハイフン",
|
|
49
|
-
"name": "表示名",
|
|
50
|
-
"description": "説明(任意)",
|
|
51
|
-
"dataTypes": ["categories", "tags"]
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
`type` の選択:
|
|
56
|
-
|
|
57
|
-
| type | 用途 | おすすめデータタイプ | フロントマターフィールド |
|
|
58
|
-
|------|------|---------------------|------------------------|
|
|
59
|
-
| `post` | ブログ、コラム(時系列コンテンツ) | カテゴリ、タグ、著者、連載 | title, description, published_at, locale |
|
|
60
|
-
| `page` | 固定ページ(会社概要、サービス紹介) | なし | title, description, locale |
|
|
61
|
-
| `doc` | ドキュメント(ツリー構造、マニュアル) | なし | title, description, order, locale |
|
|
62
|
-
| `news` | ニュース、お知らせ | カテゴリ | title, description, published_at, expires_at, important, locale |
|
|
63
|
-
|
|
64
|
-
### 付属データタイプ(プリセット)
|
|
65
|
-
|
|
66
|
-
コレクション作成時に `dataTypes` フィールドでプリセットslugの配列を指定すると、対応するデータタイプが自動作成される。省略時はそのCollectionTypeのデフォルトプリセットが適用される。
|
|
67
|
-
|
|
68
|
-
利用可能なプリセット:
|
|
69
|
-
|
|
70
|
-
| slug | 名前 | 選択方式 | デフォルト(post) | デフォルト(news) |
|
|
71
|
-
|------|------|---------|-----------------|-----------------|
|
|
72
|
-
| `categories` | カテゴリ | 単一選択 | ✅ | ✅ |
|
|
73
|
-
| `tags` | タグ | 複数選択 | ✅ | - |
|
|
74
|
-
| `authors` | 著者 | 単一選択 | - | - |
|
|
75
|
-
| `series` | 連載 | 単一選択 | - | - |
|
|
76
|
-
|
|
77
|
-
プリセット一覧を確認:
|
|
78
|
-
```bash
|
|
79
|
-
npx cmx-sdk list-collection-presets --type post
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
データタイプ不要(`doc`, `page` 等)の場合は空配列 `"dataTypes": []` を指定。
|
|
83
|
-
|
|
84
|
-
## 登録手順
|
|
85
|
-
|
|
86
|
-
### 1. JSON定義の生成
|
|
87
|
-
|
|
88
|
-
要件に基づいてコレクションまたはデータタイプのJSON定義を生成する。
|
|
89
|
-
|
|
90
|
-
### 2. 既存データの確認
|
|
91
|
-
|
|
92
|
-
まず既存のコレクション・データタイプを確認して、重複がないことを確認:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npx cmx-sdk list-collections
|
|
96
|
-
npx cmx-sdk list-data-types
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### 3. ユーザーへの確認
|
|
100
|
-
|
|
101
|
-
生成したJSON定義をユーザーに提示し、登録の承認を得る:
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
以下のスキーマを CMX Admin に登録します:
|
|
105
|
-
|
|
106
|
-
【コレクション】
|
|
107
|
-
- {name} ({slug}) — type: {type}
|
|
108
|
-
|
|
109
|
-
【データタイプ】
|
|
110
|
-
- {name} ({slug}) — フィールド数: {n}
|
|
111
|
-
|
|
112
|
-
重複はありません。
|
|
113
|
-
|
|
114
|
-
**このまま Admin に登録しますか?**
|
|
115
|
-
(または、JSON を出力して手動で登録することもできます)
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### 4. cmx-sdk で登録
|
|
119
|
-
|
|
120
|
-
承認されたら、`cmx-sdk` コマンドで登録する。
|
|
121
|
-
|
|
122
|
-
**コレクションの登録(プリセットデータタイプ付き):**
|
|
123
|
-
```bash
|
|
124
|
-
npx cmx-sdk create-collection --json '{"type":"post","slug":"blog","name":"ブログ","description":"ブログ記事を管理","dataTypes":["categories","tags"]}'
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
コレクション作成時にプリセットが自動適用され、`blog-categories`、`blog-tags` などのデータタイプが自動作成される。
|
|
128
|
-
|
|
129
|
-
**コレクションの付属データタイプを後から追加:**
|
|
130
|
-
```bash
|
|
131
|
-
# プリセットから追加
|
|
132
|
-
npx cmx-sdk add-collection-data-type --collection blog --preset authors
|
|
133
|
-
|
|
134
|
-
# カスタムで追加
|
|
135
|
-
npx cmx-sdk add-collection-data-type --collection blog --json '{"slug":"custom-field","name":"カスタム","referenceType":"single","fields":[{"key":"name","label":"名前","type":"text","required":true}]}'
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**グローバルデータタイプの登録:**
|
|
139
|
-
```bash
|
|
140
|
-
# カスタムフィールドで登録(推奨)
|
|
141
|
-
npx cmx-sdk create-data-type --json '{"slug":"products","name":"商品","description":"商品情報","fields":[{"key":"name","label":"名前","type":"text","required":true},{"key":"published","label":"公開","type":"boolean","required":true,"defaultValue":false}]}'
|
|
142
|
-
|
|
143
|
-
# プリセートを使用する場合(参考例)
|
|
144
|
-
npx cmx-sdk create-data-type --json '{"slug":"staff","name":"スタッフ","presetSlug":"staff"}'
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### グローバルプリセット(参考例)
|
|
148
|
-
|
|
149
|
-
参考として以下のプリセートが用意されていますが、**カスタムフィールドで自由に定義することを推奨**します。
|
|
150
|
-
|
|
151
|
-
| slug | フィールド例 | 用途例 |
|
|
152
|
-
|------|----------|------|
|
|
153
|
-
| `staff` | name, role, bio, avatar, email, published | スタッフ情報、チームメンバー紹介 |
|
|
154
|
-
| `locations` | name, address, description, latitude, longitude, image, published | 店舗・施設情報、アクセスマップ |
|
|
155
|
-
|
|
156
|
-
**公開/非公開の制御:**
|
|
157
|
-
- グローバルデータタイプは公開サイトに直接表示されるため、`published` フィールド(boolean)で公開/非公開を制御するのが一般的
|
|
158
|
-
- コレクションに紐付くデータタイプ(categories, tags, authors)はコンテンツの公開ステータスで制御されるため、`published` フィールドは不要
|
|
159
|
-
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# フィールドタイプリファレンス
|
|
2
|
-
|
|
3
|
-
## 一覧
|
|
4
|
-
|
|
5
|
-
| type | 説明 | options |
|
|
6
|
-
|------|------|---------|
|
|
7
|
-
| `text` | 単行テキスト | `maxLength`, `placeholder` |
|
|
8
|
-
| `textarea` | 複数行テキスト | `maxLength`, `placeholder` |
|
|
9
|
-
| `richtext` | リッチテキストエディタ | - |
|
|
10
|
-
| `number` | 数値 | `min`, `max`, `step` |
|
|
11
|
-
| `date` | 日付 | - |
|
|
12
|
-
| `datetime` | 日時 | - |
|
|
13
|
-
| `boolean` | 真偽値 | - |
|
|
14
|
-
| `select` | ドロップダウン選択 | `choices[]`, `multiple` |
|
|
15
|
-
| `multiselect` | 複数選択 | `choices[]` |
|
|
16
|
-
| `image` | 画像 | - |
|
|
17
|
-
| `file` | ファイル | - |
|
|
18
|
-
| `json` | JSONデータ | - |
|
|
19
|
-
| `url` | URL | - |
|
|
20
|
-
| `email` | メールアドレス | - |
|
|
21
|
-
| `relation` | 他データタイプへの参照 | `targetType` |
|
|
22
|
-
|
|
23
|
-
## options 詳細
|
|
24
|
-
|
|
25
|
-
```json
|
|
26
|
-
{
|
|
27
|
-
"maxLength": 100,
|
|
28
|
-
"placeholder": "入力してください",
|
|
29
|
-
"min": 0,
|
|
30
|
-
"max": 999,
|
|
31
|
-
"step": 1,
|
|
32
|
-
"choices": [
|
|
33
|
-
{ "value": "option1", "label": "選択肢1" },
|
|
34
|
-
{ "value": "option2", "label": "選択肢2" }
|
|
35
|
-
],
|
|
36
|
-
"multiple": false,
|
|
37
|
-
"targetType": "staff"
|
|
38
|
-
}
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
### text / textarea
|
|
42
|
-
|
|
43
|
-
- `maxLength` (number) — 最大文字数
|
|
44
|
-
- `placeholder` (string) — プレースホルダーテキスト
|
|
45
|
-
|
|
46
|
-
### number
|
|
47
|
-
|
|
48
|
-
- `min` (number) — 最小値
|
|
49
|
-
- `max` (number) — 最大値
|
|
50
|
-
- `step` (number) — ステップ値
|
|
51
|
-
|
|
52
|
-
### select
|
|
53
|
-
|
|
54
|
-
- `choices` (array) — `{ "value": "key", "label": "表示名" }` の配列
|
|
55
|
-
- `multiple` (boolean) — `true` で複数選択可
|
|
56
|
-
|
|
57
|
-
### relation
|
|
58
|
-
|
|
59
|
-
- `targetType` (string) — 参照先データタイプの slug
|
|
60
|
-
|
|
61
|
-
## defaultValue(デフォルト値)
|
|
62
|
-
|
|
63
|
-
フィールド定義に `defaultValue` を設定すると、エントリ作成時に値が未入力の場合に自動的に適用されます。
|
|
64
|
-
|
|
65
|
-
```json
|
|
66
|
-
{
|
|
67
|
-
"key": "published",
|
|
68
|
-
"label": "公開",
|
|
69
|
-
"type": "boolean",
|
|
70
|
-
"required": true,
|
|
71
|
-
"defaultValue": false
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**動作:**
|
|
76
|
-
- エントリ作成時、フィールドに値が指定されていない場合に `defaultValue` が適用される
|
|
77
|
-
- 明示的に `null` が指定された場合は `null` が保存される(デフォルト値は適用されない)
|
|
78
|
-
- 値の型は `type` と一致する必要がある(例: `boolean` フィールドに `"false"` は不可)
|
|
79
|
-
|
|
80
|
-
**使用例:**
|
|
81
|
-
- `published: false` — グローバルデータタイプで、デフォルトで非公開
|
|
82
|
-
- `status: "draft"` — ステータスフィールドで、デフォルトで下書き
|
|
83
|
-
- `order: 0` — ソート順フィールドで、デフォルトで0
|
|
84
|
-
|
|
85
|
-
## データタイプ JSON 例
|
|
86
|
-
|
|
87
|
-
### スタッフ
|
|
88
|
-
|
|
89
|
-
```json
|
|
90
|
-
{
|
|
91
|
-
"slug": "staff",
|
|
92
|
-
"name": "スタッフ",
|
|
93
|
-
"description": "チームメンバー情報",
|
|
94
|
-
"fields": [
|
|
95
|
-
{
|
|
96
|
-
"key": "name",
|
|
97
|
-
"label": "名前",
|
|
98
|
-
"type": "text",
|
|
99
|
-
"required": true,
|
|
100
|
-
"description": "フルネーム",
|
|
101
|
-
"options": { "maxLength": 100 }
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
"key": "position",
|
|
105
|
-
"label": "役職",
|
|
106
|
-
"type": "select",
|
|
107
|
-
"options": {
|
|
108
|
-
"choices": [
|
|
109
|
-
{ "value": "director", "label": "取締役" },
|
|
110
|
-
{ "value": "manager", "label": "マネージャー" },
|
|
111
|
-
{ "value": "engineer", "label": "エンジニア" }
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
{ "key": "bio", "label": "自己紹介", "type": "textarea", "options": { "maxLength": 500 } },
|
|
116
|
-
{ "key": "photo", "label": "写真", "type": "image" },
|
|
117
|
-
{ "key": "email", "label": "メールアドレス", "type": "email" }
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### FAQ
|
|
123
|
-
|
|
124
|
-
```json
|
|
125
|
-
{
|
|
126
|
-
"slug": "faq",
|
|
127
|
-
"name": "よくある質問",
|
|
128
|
-
"fields": [
|
|
129
|
-
{ "key": "question", "label": "質問", "type": "text", "required": true },
|
|
130
|
-
{ "key": "answer", "label": "回答", "type": "richtext", "required": true },
|
|
131
|
-
{
|
|
132
|
-
"key": "category",
|
|
133
|
-
"label": "カテゴリ",
|
|
134
|
-
"type": "select",
|
|
135
|
-
"options": {
|
|
136
|
-
"choices": [
|
|
137
|
-
{ "value": "general", "label": "一般" },
|
|
138
|
-
{ "value": "pricing", "label": "料金" },
|
|
139
|
-
{ "value": "technical", "label": "技術" }
|
|
140
|
-
]
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
{ "key": "is_featured", "label": "注目", "type": "boolean" }
|
|
144
|
-
]
|
|
145
|
-
}
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 実績・事例
|
|
149
|
-
|
|
150
|
-
```json
|
|
151
|
-
{
|
|
152
|
-
"slug": "case-studies",
|
|
153
|
-
"name": "実績・事例",
|
|
154
|
-
"description": "導入事例や制作実績",
|
|
155
|
-
"fields": [
|
|
156
|
-
{ "key": "title", "label": "タイトル", "type": "text", "required": true },
|
|
157
|
-
{ "key": "client", "label": "クライアント名", "type": "text" },
|
|
158
|
-
{ "key": "description", "label": "概要", "type": "textarea" },
|
|
159
|
-
{ "key": "thumbnail", "label": "サムネイル", "type": "image" },
|
|
160
|
-
{ "key": "url", "label": "サイトURL", "type": "url" },
|
|
161
|
-
{ "key": "published_date", "label": "公開日", "type": "date" }
|
|
162
|
-
]
|
|
163
|
-
}
|
|
164
|
-
```
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# 移行シナリオ別ガイド
|
|
2
|
-
|
|
3
|
-
サイト種別ごとに、よく使われるデータタイプとコレクションの構成例。
|
|
4
|
-
|
|
5
|
-
## コーポレートサイト
|
|
6
|
-
|
|
7
|
-
データタイプ:
|
|
8
|
-
- `staff` — メンバー紹介(name, position, bio, photo)
|
|
9
|
-
- `service` — サービス紹介(title, description, icon, features)
|
|
10
|
-
- `faq` — よくある質問(question, answer, category)
|
|
11
|
-
- `testimonial` — お客様の声(name, company, content, rating)
|
|
12
|
-
|
|
13
|
-
コレクション:
|
|
14
|
-
- `news` type=news: お知らせ
|
|
15
|
-
- `page` type=page: 会社概要、アクセス等
|
|
16
|
-
|
|
17
|
-
## LP(ランディングページ)
|
|
18
|
-
|
|
19
|
-
データタイプ:
|
|
20
|
-
- `feature` — 機能紹介(title, description, icon)
|
|
21
|
-
- `pricing` — 料金プラン(name, price, features, is_popular)
|
|
22
|
-
- `testimonial` — 利用者の声(name, company, content)
|
|
23
|
-
- `cta` — CTAセクション(title, description, button_text, button_url)
|
|
24
|
-
|
|
25
|
-
## ブログ・メディア
|
|
26
|
-
|
|
27
|
-
コレクション:
|
|
28
|
-
- `post` type=post: 記事(blog, column, interview等)
|
|
29
|
-
- フロントマター: title, description, category, tags, published_at
|
|
30
|
-
- **category と tags はフロントマターに直接記述(データタイプ不要)**
|
|
31
|
-
|
|
32
|
-
データタイプ:
|
|
33
|
-
- `author` — 著者情報(name, bio, avatar, social_links)
|
|
34
|
-
|
|
35
|
-
## ECサイト
|
|
36
|
-
|
|
37
|
-
データタイプ:
|
|
38
|
-
- `product` — 商品(name, price, description, image, category)
|
|
39
|
-
- `category` — カテゴリ(name, slug, parent)
|
|
40
|
-
- `review` — レビュー(rating, content, author)
|
|
41
|
-
|
|
42
|
-
コレクション:
|
|
43
|
-
- `page` type=page: 特商法表記、プライバシーポリシー等
|
|
44
|
-
- `news` type=news: セール情報、新商品情報
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cmx-seo
|
|
3
|
-
description: |
|
|
4
|
-
CMX Starter Kit の SEO・メタデータ実装スキル。generateMetadata、OGP、sitemap.ts、robots.ts、JSON-LD 構造化データのパターン。
|
|
5
|
-
トリガー: 「SEOを設定」「メタデータを追加」「OGPを設定」「サイトマップを作成」
|
|
6
|
-
「robots.txtを設定」「構造化データを追加」「JSON-LDを追加」「検索対策」
|
|
7
|
-
「Twitterカードを設定」「og:imageを設定」「canonical URLを設定」など。
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# CMX SEO・メタデータ
|
|
11
|
-
|
|
12
|
-
## 事前確認
|
|
13
|
-
|
|
14
|
-
`cmx/site-config.md` のサイト名・説明を確認し、メタデータに反映する。
|
|
15
|
-
|
|
16
|
-
## 環境変数
|
|
17
|
-
|
|
18
|
-
`NEXT_PUBLIC_SITE_URL` — サイトの本番 URL。sitemap・canonical・OGP の URL 構築に必須。`.env.local` で設定済みか確認。
|
|
19
|
-
|
|
20
|
-
## 既存ユーティリティ
|
|
21
|
-
|
|
22
|
-
`src/lib/utils/metadata.ts` に `generateCollectionMetadata` / `generateContentMetadata` ヘルパーが存在。拡張する場合はこのファイルを修正。
|
|
23
|
-
|
|
24
|
-
## パターン別ガイド
|
|
25
|
-
|
|
26
|
-
各パターンの詳細コードは [references/seo-patterns.md](references/seo-patterns.md) を参照。
|
|
27
|
-
|
|
28
|
-
### generateMetadata
|
|
29
|
-
|
|
30
|
-
- **静的ページ**: `export const metadata: Metadata` で直接定義
|
|
31
|
-
- **動的ページ**: `export async function generateMetadata()` で CMX API からデータ取得
|
|
32
|
-
- **プレビューページ**: `robots: { index: false, follow: false }` を必ず設定
|
|
33
|
-
|
|
34
|
-
### OGP / Twitter Card
|
|
35
|
-
|
|
36
|
-
`metadata.ts` の既存ヘルパーを拡張し、`openGraph` と `twitter` フィールドを追加する。デフォルト OG 画像は `/public/og-default.jpg` に配置。
|
|
37
|
-
|
|
38
|
-
### sitemap.ts
|
|
39
|
-
|
|
40
|
-
`src/app/sitemap.ts` を作成。CMX API から全コレクションの記事を取得し動的生成。
|
|
41
|
-
|
|
42
|
-
### robots.ts
|
|
43
|
-
|
|
44
|
-
`src/app/robots.ts` を作成。`/preview/` と `/api/` を disallow。
|
|
45
|
-
|
|
46
|
-
### JSON-LD 構造化データ
|
|
47
|
-
|
|
48
|
-
記事詳細ページに `<script type="application/ld+json">` を埋め込む。スキーマタイプは `BlogPosting`(ブログ)、`NewsArticle`(ニュース)、`WebPage`(その他)。
|
|
49
|
-
|
|
50
|
-
## 変更後
|
|
51
|
-
|
|
52
|
-
1. 各ページの `<head>` 出力を確認(ブラウザの DevTools)
|
|
53
|
-
2. OGP デバッガー(Facebook Sharing Debugger 等)で確認
|
|
54
|
-
3. Google Rich Results Test で JSON-LD を検証
|
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
# SEO 実装パターン
|
|
2
|
-
|
|
3
|
-
## generateMetadata — 静的ページ
|
|
4
|
-
|
|
5
|
-
```tsx
|
|
6
|
-
// src/app/about/page.tsx
|
|
7
|
-
import type { Metadata } from "next"
|
|
8
|
-
|
|
9
|
-
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"
|
|
10
|
-
|
|
11
|
-
export const metadata: Metadata = {
|
|
12
|
-
title: "会社概要 | サイト名",
|
|
13
|
-
description: "会社概要ページの説明文",
|
|
14
|
-
alternates: { canonical: `${SITE_URL}/about` },
|
|
15
|
-
openGraph: {
|
|
16
|
-
title: "会社概要 | サイト名",
|
|
17
|
-
description: "会社概要ページの説明文",
|
|
18
|
-
url: `${SITE_URL}/about`,
|
|
19
|
-
images: ["/og-default.jpg"],
|
|
20
|
-
},
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## generateMetadata — 動的ページ(記事詳細)
|
|
25
|
-
|
|
26
|
-
`src/lib/utils/metadata.ts` を拡張:
|
|
27
|
-
|
|
28
|
-
```tsx
|
|
29
|
-
import type { Metadata } from "next"
|
|
30
|
-
import { fetchPublishedContent } from "@/lib/api/public-client"
|
|
31
|
-
|
|
32
|
-
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"
|
|
33
|
-
|
|
34
|
-
export async function generateContentMetadata(
|
|
35
|
-
collectionSlug: string,
|
|
36
|
-
contentSlug: string
|
|
37
|
-
): Promise<Metadata> {
|
|
38
|
-
const data = await fetchPublishedContent(collectionSlug, contentSlug)
|
|
39
|
-
if (!data) {
|
|
40
|
-
throw new Error(`Failed to fetch content metadata: ${collectionSlug}/${contentSlug}`)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const url = `${SITE_URL}/${collectionSlug}/${contentSlug}`
|
|
44
|
-
const title = `${data.content.title} | ${data.collection.name}`
|
|
45
|
-
const description = data.content.description || undefined
|
|
46
|
-
const image = "/og-default.jpg"
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
title,
|
|
50
|
-
description,
|
|
51
|
-
alternates: { canonical: url },
|
|
52
|
-
openGraph: {
|
|
53
|
-
title: data.content.title,
|
|
54
|
-
description,
|
|
55
|
-
url,
|
|
56
|
-
type: "article",
|
|
57
|
-
images: [image],
|
|
58
|
-
},
|
|
59
|
-
twitter: {
|
|
60
|
-
card: "summary_large_image",
|
|
61
|
-
title: data.content.title,
|
|
62
|
-
description,
|
|
63
|
-
images: [image],
|
|
64
|
-
},
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## sitemap.ts
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
// src/app/sitemap.ts
|
|
73
|
-
import type { MetadataRoute } from "next"
|
|
74
|
-
import { fetchPublishedContents } from "@/lib/api/public-client"
|
|
75
|
-
import { COLLECTION_SLUGS } from "@/lib/constants/collections"
|
|
76
|
-
|
|
77
|
-
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"
|
|
78
|
-
|
|
79
|
-
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
80
|
-
const entries: MetadataRoute.Sitemap = [
|
|
81
|
-
{
|
|
82
|
-
url: SITE_URL,
|
|
83
|
-
lastModified: new Date(),
|
|
84
|
-
changeFrequency: "daily",
|
|
85
|
-
priority: 1,
|
|
86
|
-
},
|
|
87
|
-
]
|
|
88
|
-
|
|
89
|
-
// 静的ページ
|
|
90
|
-
const staticPages = ["/about", "/contact"]
|
|
91
|
-
for (const page of staticPages) {
|
|
92
|
-
entries.push({
|
|
93
|
-
url: `${SITE_URL}${page}`,
|
|
94
|
-
changeFrequency: "monthly",
|
|
95
|
-
priority: 0.5,
|
|
96
|
-
})
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// 各コレクションの記事を取得
|
|
100
|
-
for (const slug of Object.values(COLLECTION_SLUGS)) {
|
|
101
|
-
// コレクション一覧ページ
|
|
102
|
-
entries.push({
|
|
103
|
-
url: `${SITE_URL}/${slug}`,
|
|
104
|
-
changeFrequency: "daily",
|
|
105
|
-
priority: 0.8,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
// 各記事
|
|
109
|
-
try {
|
|
110
|
-
const data = await fetchPublishedContents(slug)
|
|
111
|
-
if (data?.contents) {
|
|
112
|
-
for (const item of data.contents) {
|
|
113
|
-
entries.push({
|
|
114
|
-
url: `${SITE_URL}/${slug}/${item.slug}`,
|
|
115
|
-
lastModified: item.publishedAt ? new Date(item.publishedAt) : new Date(),
|
|
116
|
-
changeFrequency: "weekly",
|
|
117
|
-
priority: 0.7,
|
|
118
|
-
})
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
} catch {
|
|
122
|
-
// コレクション取得失敗時はスキップ
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return entries
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## robots.ts
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
// src/app/robots.ts
|
|
134
|
-
import type { MetadataRoute } from "next"
|
|
135
|
-
|
|
136
|
-
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"
|
|
137
|
-
|
|
138
|
-
export default function robots(): MetadataRoute.Robots {
|
|
139
|
-
return {
|
|
140
|
-
rules: {
|
|
141
|
-
userAgent: "*",
|
|
142
|
-
allow: "/",
|
|
143
|
-
disallow: ["/preview/", "/api/"],
|
|
144
|
-
},
|
|
145
|
-
sitemap: `${SITE_URL}/sitemap.xml`,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## JSON-LD 構造化データ
|
|
151
|
-
|
|
152
|
-
### ブログ記事
|
|
153
|
-
|
|
154
|
-
```tsx
|
|
155
|
-
// src/app/blog/[slug]/page.tsx 内
|
|
156
|
-
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"
|
|
157
|
-
|
|
158
|
-
function BlogPostJsonLd({ content, collectionSlug, slug }: {
|
|
159
|
-
content: { title: string; description?: string; publishedAt?: string; updatedAt?: string; featuredImage?: string }
|
|
160
|
-
collectionSlug: string
|
|
161
|
-
slug: string
|
|
162
|
-
}) {
|
|
163
|
-
const jsonLd = {
|
|
164
|
-
"@context": "https://schema.org",
|
|
165
|
-
"@type": "BlogPosting",
|
|
166
|
-
headline: content.title,
|
|
167
|
-
description: content.description,
|
|
168
|
-
datePublished: content.publishedAt,
|
|
169
|
-
dateModified: content.updatedAt ?? content.publishedAt,
|
|
170
|
-
image: content.featuredImage,
|
|
171
|
-
url: `${SITE_URL}/${collectionSlug}/${slug}`,
|
|
172
|
-
publisher: {
|
|
173
|
-
"@type": "Organization",
|
|
174
|
-
name: "サイト名", // site-config.md のサイト名を使用
|
|
175
|
-
},
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return (
|
|
179
|
-
<script
|
|
180
|
-
type="application/ld+json"
|
|
181
|
-
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
182
|
-
/>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### 組織情報(ルートレイアウト)
|
|
188
|
-
|
|
189
|
-
```tsx
|
|
190
|
-
// src/app/layout.tsx に追加
|
|
191
|
-
function OrganizationJsonLd() {
|
|
192
|
-
const jsonLd = {
|
|
193
|
-
"@context": "https://schema.org",
|
|
194
|
-
"@type": "Organization",
|
|
195
|
-
name: "サイト名",
|
|
196
|
-
url: process.env.NEXT_PUBLIC_SITE_URL,
|
|
197
|
-
logo: `${process.env.NEXT_PUBLIC_SITE_URL}/logo.png`,
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<script
|
|
202
|
-
type="application/ld+json"
|
|
203
|
-
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
|
|
204
|
-
/>
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## コレクションタイプ別の JSON-LD スキーマタイプ
|
|
210
|
-
|
|
211
|
-
| コレクションタイプ | schema.org @type |
|
|
212
|
-
|------------------|------------------|
|
|
213
|
-
| post(ブログ) | BlogPosting |
|
|
214
|
-
| news(ニュース) | NewsArticle |
|
|
215
|
-
| page(ページ) | WebPage |
|
|
216
|
-
| doc(ドキュメント) | TechArticle |
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cmx-style
|
|
3
|
-
description: |
|
|
4
|
-
CMX Starter Kit のスタイル・デザイン変更スキル。CSS 変数、フォント、レイアウト、カラーの変更パターンと site-config.md との連動。
|
|
5
|
-
トリガー: 「スタイルを変更」「デザインを変えたい」「色を変更」「フォントを変更」
|
|
6
|
-
「レイアウトを変更」「ヘッダーを修正」「フッターを修正」「ダークモード」
|
|
7
|
-
「余白を調整」「見た目を変えたい」など。
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# CMX スタイル変更
|
|
11
|
-
|
|
12
|
-
## 事前確認
|
|
13
|
-
|
|
14
|
-
変更前に必ず `cmx/site-config.md` のデザイン方針セクションを読み、変更が方針と矛盾しないか確認する。矛盾する場合はコンフィグの更新も提案すること。
|
|
15
|
-
|
|
16
|
-
## 変更対象と対応ファイル
|
|
17
|
-
|
|
18
|
-
| 変更内容 | 対象ファイル |
|
|
19
|
-
|---------|------------|
|
|
20
|
-
| カラー(テーマ色、背景色等) | `src/app/globals.css` |
|
|
21
|
-
| フォント | `src/app/layout.tsx` |
|
|
22
|
-
| Header | `src/components/layout/Header.tsx` |
|
|
23
|
-
| Footer | `src/components/layout/Footer.tsx` |
|
|
24
|
-
| MDX 記事のスタイル | `src/app/globals.css`(`.prose` カスタマイズ) |
|
|
25
|
-
| コンポーネントスタイル | 該当 TSX ファイル |
|
|
26
|
-
| 全体的なスペーシング | `src/app/globals.css` |
|
|
27
|
-
|
|
28
|
-
## パターン別ガイド
|
|
29
|
-
|
|
30
|
-
詳細は [references/style-patterns.md](references/style-patterns.md) を参照。
|
|
31
|
-
|
|
32
|
-
### カラー変更
|
|
33
|
-
|
|
34
|
-
`src/app/globals.css` の CSS 変数を修正。shadcn/ui のテーマ変数体系に従う。
|
|
35
|
-
|
|
36
|
-
### フォント変更
|
|
37
|
-
|
|
38
|
-
`src/app/layout.tsx` の `next/font/google` インポートを変更。CSS 変数でフォントファミリーを指定。
|
|
39
|
-
|
|
40
|
-
### レイアウト変更
|
|
41
|
-
|
|
42
|
-
Header/Footer は `src/components/layout/` 配下。ナビゲーション項目、ロゴ、カラムレイアウトを修正。
|
|
43
|
-
|
|
44
|
-
## 変更後
|
|
45
|
-
|
|
46
|
-
1. 開発サーバーで全ページの表示を確認
|
|
47
|
-
2. レスポンシブ(モバイル/タブレット/デスクトップ)を確認
|
|
48
|
-
3. `cmx/site-config.md` のデザイン方針セクションを更新して同期
|