※この本文はGeminiに書かせています。記載内容はおおむね事実と思われますが、誤りが含まれている可能性が大きいです。
前回までのあらすじ
これまでに、Nuxt 4 と Drizzle ORM を用いたプロジェクトの土台を作成し、ブログ情報の基本的な CRUD 操作を実装した。また、Gemini API との連携を見据えた DB スキーマの定義や、Nuxt UI v4 を用いた一覧画面のプロトタイプ構築を行ってきた。
GitHub連携によるコンテキストの共有
今回は、開発環境の GitHub リポジトリを Gemini に連携させた。これにより、既存のディレクトリ構造や型定義を前提としたコード生成が可能になった。具体的には、shared/types に定義した Zod スキーマを介して、サーバー(Nitro)とフロントエンド(Vue)で型安全なデータ通信を行うためのエンドポイントとインターフェースを整備した。
ブログ詳細画面の実装
ブログ一覧の各カードから、特定のブログの詳細を確認できる「ダッシュボード画面」への遷移を実装した。
pages/blogs/[id].vueの作成:タブ切り替えにより、記事一覧、ネタ帳、AI設定を管理できる画面を構成した。- データ取得ロジックの構築:
server/api/blogs/[id].get.tsにおいて、Drizzle で必要なカラムのみを SELECT し、Zod スキーマでパースして返却する堅牢なロジックを実装した。 - 遷移機能の付与:
BlogCardコンポーネントにおいて、UCardをクリックすることで詳細画面へ遷移する動線を作成した。
発生した問題と解決
実装後の動作確認において、カード内に配置した「外部リンク」ボタンや「削除」ボタンが正常に機能しない問題が発生した。カード全体をリンクにしたことで、子要素であるボタンのクリックイベントが親要素にバブリング(伝播)し、ボタン固有の処理よりも先にページ遷移が発火してしまうことが原因である。
この問題に対し、ボタン要素に @click.stop 修飾子を付与することでイベントの伝播を阻止した。また、HTMLの仕様として <a> タグの入れ子(リンクの中にリンクを置くこと)を避けるため、CSSの stretched link パターンを適用した。具体的には、カード内に絶対位置指定した「全体リンク」を配置し、ボタン要素はそれよりも高い z-index を持つレイヤーに配置することで、カード全体のクリック判定とボタンの独立した操作性を両立させた。
実装コードのイメージ:
<ULink :to="`/blogs/${id}`" class="after:absolute after:inset-0" /> <UButton @click.stop="onDelete" class="relative z-10" />
AIは効率的なコードを提案したが、こうした UI 上の基本的なイベント制御や、アクセシビリティに関わる構造上の注意点については、開発者が実際に操作して指摘するまで考慮されないという課題が浮き彫りになった。
