はじめに — 「AIとの対話」を記事でどう見せるか
ZERONOVA LAB は「AIネイティブ実験スタジオ」を掲げています。Journal の記事には、PMである Zeronova と AI である Claude Code の対話セクションを必ず含めるというルールがあります。
しかし、プレーンテキストの対話セクションは読みにくい。「Zeronova: ○○」「Claude Code: △△」という文字列が並んでいるだけでは、会話の臨場感が伝わりません。
Journal の PM-AI 対話セクションをチャット風UIで自動描画。react-markdown のカスタムコンポーネントで Zeronova: / Claude Code: パターンを検出し、Slack風のチャットメッセージに変換
2026年2月13日、react-markdown のカスタムコンポーネントを活用して、Markdown の対話セクションをSlack風のチャットUIに自動変換する仕組みをClaude Codeで実装しました。
設計の要件
まず、対話セクションがどのような Markdown で書かれるかを確認します。
**Zeronova**: RLS first の設計方針でいきたい。
**Claude Code**: 各テーブルに POLICY を定義する
アプローチが安全です。
**Zeronova**: それで進めよう。
要件は以下の通りです。
**Zeronova**:で始まる段落を「人間側のメッセージ」として描画**Claude Code**:で始まる段落を「AI側のメッセージ」として描画- 後方互換として
**PM**:も Zeronova として扱う - Markdown の記法変更は不要(既存の記事がそのまま動く)
- 対話以外の段落は通常のテキストとして描画
スピーカー検出の実装
react-markdown では、Markdown の各要素(段落、見出し、リスト等)をカスタムコンポーネントで差し替えることができます。<p> タグの描画をカスタマイズすることで、対話セクションを検出します。
detectSpeaker() 関数が、段落の先頭にあるスピーカーラベルを検出します。
function detectSpeaker(children: React.ReactNode): {
speaker: "zeronova" | "claude" | null;
content: React.ReactNode;
} {
// children の最初の要素が <strong> で
// テキストが "Zeronova" / "Claude Code" / "PM" なら検出
// ...
}
react-markdown は **Zeronova** を <strong>Zeronova</strong> に変換します。カスタム <p> コンポーネントの children から最初の <strong> 要素のテキストを取得し、Zeronova、Claude Code、PM のいずれかにマッチするかを判定します。
PM → Zeronova へのスピーカーラベル変更
チャット風UI の実装と同時に、スピーカーラベルの表記も見直しました。
それまでの Journal 記事では **PM**: というスピーカーラベルを使っていました。しかし、チャットUIで表示名として「PM」を見たときに、誰の発言なのかが分かりにくい。
**Zeronova**: に変更することを提案します。AuthorCard・About ページ・JSON-LD の著者名と一致させることで、サイト全体で名前の一貫性が保てます。既存6記事の **PM**: も **Zeronova**: に一括変更し、後方互換として **PM**: の検出も残します。author.name と alternateName で担保されているので、UI は「Zeronova」のみが最適です。この判断の結果、チャットUIのスピーカーラベル(Zeronova)、AuthorCard(Zeronova(ゼロノバ))、JSON-LD(name + alternateName)の3層で、UIの自然さ・ブランディング・SEO を同時に満たす設計になりました。
Claude Codeで既存6記事のスピーカーラベルを一括変更し、合計17箇所の **PM**: を **Zeronova**: に置換しました。
チャット風UIの描画
スピーカーが検出された段落は、チャットメッセージとして描画されます。
Zeronova のメッセージ
- アバター画像(
/avatar.png、28x28px、next/imageで最適化) - 白テキストの名前ラベル「Zeronova」
- グレーの左ボーダー(
border-gray-400) - 薄い白背景(
bg-white/5)
Claude Code のメッセージ
- TerminalIcon(neon green の丸いアイコン)
- primary カラーの名前ラベル「Claude Code」
- neon green の左ボーダー(
border-primary) - 薄い green 背景(
bg-primary/5)
この差異によって、人間と AI の発言が視覚的に即座に区別でき、対話の流れが直感的に把握できます。
実装上のポイント
next/image でのアバター最適化
Zeronova のアバター画像は next/image の Image コンポーネントで描画しています。Markdown レンダラーはクライアントコンポーネントのため、next/image をそのまま使用できます。
後方互換の維持
**PM**: という旧形式のスピーカーラベルも Zeronova として描画します。Journal の初期記事では **PM**: を使っていたため、後方互換を維持しないと既存記事の表示が崩れます。
新規記事では **Zeronova**: を使うことをガイドラインで規定しています。
順序付きリストのスタイル修正
対話UI の実装中に、別のレンダリング問題も発見しました。
順序付きリスト <ol><li> がカスタムの緑ドット(番号なし)で表示されていた。react-markdown の li コンポーネントに ordered: boolean が渡されるため、順序付き/なしでスタイルを出し分け
react-markdown の <li> カスタムコンポーネントに ordered プロパティが渡されることを利用して、順序付きリスト(<ol>)では数字、順序なしリスト(<ul>)ではカスタムのneon greenドットを表示するように修正しました。
内部リンクの target 属性
もう一つの修正として、内部リンクの target 属性の問題がありました。
内部リンクがすべて target="_blank" になっていた。href?.startsWith("/") でサイト内リンクを判定し、内部リンクは新しいタブで開かないように修正
全てのリンクに target="_blank" を設定していたため、/journal/another-article のような内部リンクも新しいタブで開かれていました。href が / で始まるかどうかで内部/外部を判定し、内部リンクは target なし(同一タブでのページ遷移)にしました。
著者情報の統一(E-E-A-T対応)
対話UIの実装と同じ日に、サイト全体の著者情報の統一もClaude Codeで行いました。
AuthorCard role を全バリアント統一: "Product Manager / AI-Native Builder"。PM → PdM 表記統一。全76ツールの JSON-LD に jobTitle: "Product Manager" を追加
AuthorCard コンポーネントの role 表記が Journal / Focus Blog / Tools でバラバラだったのを、すべて「Product Manager / AI-Native Builder」に統一。PM と PdM の表記揺れも PdM(Product Manager の日本語略称としてより正確)に統一しました。日本のIT業界では PM がプロジェクトマネージャーを指すことが多いため、PdM(プロダクトマネージャー)と明記することで職種の誤解を防ぐ効果もあります。
合わせて、ツールページの JSON-LD(getToolAuthorJsonLd())に jobTitle: "Product Manager" を追加し、全76ツールの構造化データに反映しました。Google は jobTitle を使って著者の専門性を判定するため、E-E-A-T の Expertise シグナル強化に寄与します。
著者ペルソナの定義を docs/author-persona.md に Single Source of Truth として集約し、各ガイドライン(journal-spec.md、x-posting-guidelines.md、note-checklist.md、kdp-publishing-guide.md)からの参照に切り替えた点も、コンテンツの一貫性を保つ上で重要な改善でした。
author-persona.md を Single Source of Truth として新規作成。6ファイルに分散していたペルソナ定義を統合し、約160行の重複を削除
まとめ — ブランドコンセプトをUIで体現する
PM-AI 対話のチャット風UIの実装を通じて得た学びをまとめます。
- react-markdown のカスタムコンポーネントは強力: Markdown の記法を変えずに、レンダリング時に特定パターンを検出して UI を差し替えられる。プリプロセス方式より保守性が高い
- 視覚的な差異が対話の読みやすさを左右する: アバター、ボーダーカラー、背景色の組み合わせで、人間とAIの発言を即座に区別できる
- 後方互換は初期から考慮する:
**PM**:→**Zeronova**:の移行で、後方互換を維持したことで既存記事への影響をゼロにできた - ブランドコンセプトはUIで体現する: 「AIネイティブ実験スタジオ」を掲げる以上、AIとの対話が視覚的に「対等な議論」として表現される必要がある。プレーンテキストの
**Claude Code**:ではその意図が伝わらない - Single Source of Truth は運用コストを下げる: 著者ペルソナ情報が6ファイルに分散していた状態を、1ファイルに統合したことで更新漏れのリスクが減った
「AIとの対話をどう見せるか」は単なるUI実装の話ではなく、ブランドの信頼性に直結する問題です。チャット風UIによって「Zeronova と Claude Code が対等に議論して意思決定している」というリアリティが視覚的に伝わるようになりました。
関連記事:
- 開発記事でAIとの協業をちゃんと書くことにした理由 — AI協業の可視化ポリシー
- E-E-A-T × SEO改善を個人開発サイトに実装した全記録 — 著者情報統一の詳細
- Next.js で Markdown ブログを構築する方法 — react-markdown の基本
Zeronova(ゼロノバ)
Product Manager / AI-Native Builder
Web/IT業界19年以上・20以上のWebサービスを担当したPdM。東証プライム上場企業の子会社代表として事業経営を経験。現在はAIを駆使して企画から実装まで完結させる個人開発を実践中。