<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hama</title>
    <description>The latest articles on Forem by Hama (@beachone1155).</description>
    <link>https://forem.com/beachone1155</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3582572%2Faeeb4bec-26f2-4f07-ba75-722753603418.png</url>
      <title>Forem: Hama</title>
      <link>https://forem.com/beachone1155</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/beachone1155"/>
    <language>en</language>
    <item>
      <title>Windows 11検索を爆速化！EverythingToolbarの導入とエンジニア向け活用術</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Tue, 03 Feb 2026 11:06:37 +0000</pubDate>
      <link>https://forem.com/beachone1155/windows-11jian-suo-wobao-su-hua-everythingtoolbarnodao-ru-toenziniaxiang-kehuo-yong-shu-1b81</link>
      <guid>https://forem.com/beachone1155/windows-11jian-suo-wobao-su-hua-everythingtoolbarnodao-ru-toenziniaxiang-kehuo-yong-shu-1b81</guid>
      <description>&lt;h1&gt;
  
  
  Windows 11検索を爆速化！EverythingToolbarの導入とエンジニア向け活用術
&lt;/h1&gt;

&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;Windows 11の標準検索、少し「遅い」と感じたことはありませんか？&lt;br&gt;
Web検索結果が混ざったり、ローカルファイルのインデックス作成に時間がかかったりと、開発のテンポを削ぐ要因になりがちです。&lt;/p&gt;

&lt;p&gt;そこで今回は、Windows最強のファイル検索ソフト「&lt;strong&gt;Everything&lt;/strong&gt;」をタスクバーに統合し、OS標準の検索機能を爆速化させる「&lt;strong&gt;EverythingToolbar&lt;/strong&gt;」について解説します。&lt;/p&gt;

&lt;p&gt;特に、キーボードから手を離さずに操作する「&lt;strong&gt;マウスレス設定&lt;/strong&gt;」や、正規表現を駆使した「&lt;strong&gt;エンジニア向け検索テクニック&lt;/strong&gt;」に焦点を当てて紹介します。&lt;/p&gt;
&lt;h2&gt;
  
  
  EverythingToolbarとは？
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;EverythingToolbar&lt;/strong&gt;は、タスクバーにある標準の検索ボックス（虫眼鏡アイコン）を、Everythingの検索窓に置き換えるオープンソースツールです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;見た目は純正ライク&lt;/strong&gt;: Windows 11のモダンなUIに完全に馴染みます。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;中身は爆速&lt;/strong&gt;: バックエンドでEverythingが動いているため、数百万のファイルから一瞬で目的のファイルを探し出せます。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  1. 導入と基本セットアップ
&lt;/h2&gt;
&lt;h3&gt;
  
  
  インストール手順
&lt;/h3&gt;

&lt;p&gt;前提として、&lt;a href="https://www.voidtools.com/" rel="noopener noreferrer"&gt;Voidtools公式サイト&lt;/a&gt;からEverything本体をインストールし、常駐させておく必要があります。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; GitHubの&lt;a href="https://github.com/srwi/EverythingToolbar/releases" rel="noopener noreferrer"&gt;リリースページ&lt;/a&gt;から最新のインストーラー（&lt;code&gt;.exe&lt;/code&gt;）をダウンロードして実行。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbeachone1155.vercel.app%2Fimages%2Feverything-toolbar-github-releases.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbeachone1155.vercel.app%2Fimages%2Feverything-toolbar-github-releases.png" alt="EverythingToolbar Releases" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; タスクバー設定で、Windows標準の「検索」アイコンを&lt;strong&gt;非表示&lt;/strong&gt;にする。&lt;/li&gt;
&lt;li&gt; スタートメニューから「EverythingToolbar」を検索し、&lt;strong&gt;「タスクバーにピン留め」&lt;/strong&gt;する。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbeachone1155.vercel.app%2Fimages%2Feverything-toolbar-pin-to-taskbar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbeachone1155.vercel.app%2Fimages%2Feverything-toolbar-pin-to-taskbar.png" alt="EverythingToolbar Pin to Taskbar" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; ピン留めしたアイコンを、元の検索ボタンがあった位置（左端など）にドラッグして配置。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;これで、見た目は今まで通り、中身は爆速な検索環境が整いました。&lt;/p&gt;
&lt;h2&gt;
  
  
  2. 「マウスレス」で呼び出すための設定
&lt;/h2&gt;

&lt;p&gt;エンジニアにとって、マウスへの持ち替えはタイムロスです。&lt;br&gt;
macOSのSpotlightやAlfredのように、キーボードショートカット一発で呼び出し、用が済んだら勝手に消える挙動に設定しましょう。&lt;/p&gt;
&lt;h3&gt;
  
  
  設定画面へのアクセス
&lt;/h3&gt;

&lt;p&gt;EverythingToolbarの検索窓を開き、右上の歯車アイコン（⚙️）をクリックして設定を開きます。&lt;/p&gt;
&lt;h3&gt;
  
  
  推奨設定 (General)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hide window when focus is lost&lt;/strong&gt;: &lt;code&gt;ON&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;検索窓からフォーカスが外れたら（ファイルを開く、別の場所をクリックするなど）、自動でウィンドウを閉じます。これで「閉じるボタンを押す」手間がなくなります。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shortcuts&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;デフォルトは &lt;code&gt;Win + Alt + S&lt;/code&gt; ですが、押しにくい場合は変更しましょう。&lt;/li&gt;
&lt;li&gt;おすすめ: &lt;code&gt;Alt + Space&lt;/code&gt;（PowerToys Runなどと競合しない場合）や &lt;code&gt;Ctrl + Space&lt;/code&gt; など、左手だけで完結するキーが理想です。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;この設定により、「ショートカットで呼び出す」→「検索」→「Enterで開く」→「勝手に消える」という、完全なマウスレス操作が実現します。&lt;/p&gt;
&lt;h2&gt;
  
  
  3. エンジニア向け検索テクニック
&lt;/h2&gt;

&lt;p&gt;EverythingToolbarは、Everythingの強力な検索構文をそのまま利用できます。&lt;br&gt;
GUIでポチポチ探すのではなく、クエリで絞り込むのがエンジニア流です。&lt;/p&gt;
&lt;h3&gt;
  
  
  拡張子で絞り込む (&lt;code&gt;ext:&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;特定のソースコードだけを探したい場合に便利です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Pythonファイルだけ検索
ext:py main

# TypeScriptファイルだけ検索
ext:ts utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  パスを含めて検索 (&lt;code&gt;path:&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;同名のファイルが多い場合（index.ts や README.md など）、ディレクトリ名を含めて検索します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# "backend" ディレクトリ配下の "config" を含むファイル
backend config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;※ 設定で「Match Path」をONにするか、検索時にスペースで区切るとAND検索として機能します。&lt;/p&gt;

&lt;h3&gt;
  
  
  正規表現を使う (&lt;code&gt;regex:&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;これが最強の機能です。複雑な命名規則のファイルや、特定のパターンに一致するログファイルなどを一撃で特定できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# "project_" で始まり ".json" で終わるファイル
regex:^project_.*\.json$

# 日付形式 (YYYYMMDD) を含むログファイル
regex:log_\d{8}\.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  検索コマンドのショートカット
&lt;/h3&gt;

&lt;p&gt;検索ボックスに以下のプレフィックスを入力することで、モードを瞬時に切り替えられます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;file:&lt;/code&gt; : ファイルのみ検索&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;folder:&lt;/code&gt; : フォルダのみ検索&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;case:&lt;/code&gt; : 大文字小文字を区別&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;EverythingToolbarを導入し、ショートカットと検索構文を使いこなすことで、「ファイルを探す」という行為にかかる時間はほぼゼロになります。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Step 1&lt;/strong&gt;: インストールしてタスクバーにピン留め&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Step 2&lt;/strong&gt;: フォーカスアウトで閉じる設定 &amp;amp; ショートカット割り当て&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Step 3&lt;/strong&gt;: &lt;code&gt;ext:&lt;/code&gt; や &lt;code&gt;regex:&lt;/code&gt; で狙い撃ち検索&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;開発マシンの生産性を底上げしたい方は、ぜひ今日から導入してみてください。&lt;/p&gt;

</description>
      <category>windows</category>
      <category>productivity</category>
      <category>everything</category>
      <category>tools</category>
    </item>
    <item>
      <title>GitHub Organizationに課金できなくても、AIエディタの暴走は止められる（pre-commit / pre-push + .cursor/rules）</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Sun, 01 Feb 2026 05:43:32 +0000</pubDate>
      <link>https://forem.com/beachone1155/github-organizationnike-jin-dekinakutemo-aiedeitanobao-zou-hazhi-merarerupre-commit-pre-push-cursorrules-45km</link>
      <guid>https://forem.com/beachone1155/github-organizationnike-jin-dekinakutemo-aiedeitanobao-zou-hazhi-merarerupre-commit-pre-push-cursorrules-45km</guid>
      <description>&lt;h1&gt;
  
  
  GitHub Organizationに課金できなくても、AIエディタの暴走は止められる（pre-commit / pre-push + .cursor/rules）
&lt;/h1&gt;

&lt;h2&gt;
  
  
  はじめに（ある日、AIが気持ちよく“仕事”しすぎた）
&lt;/h2&gt;

&lt;p&gt;CursorとかWindsurfとか、最近のAIエディタって、放っておくと本当に手が速いですよね。&lt;br&gt;
気づいたらファイルが増え、依存が増え、コマンド履歴が増え、そして「そのまま push しそう」になる。&lt;/p&gt;

&lt;p&gt;でも、GitHubのOrganizationで &lt;strong&gt;Branch protection をちゃんと課金して運用&lt;/strong&gt;するのが難しい状況だと、&lt;br&gt;
最後の砦が薄くて、心理的にけっこう怖い。&lt;/p&gt;

&lt;p&gt;なので私は、ローカル側で“物理ロック”をかけました。&lt;br&gt;
やりたいのはこれだけです：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;main / develop ではコミットさせない&lt;/strong&gt;（そもそもAIが間違えた場所で作業しない）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;main / develop へは push させない&lt;/strong&gt;（最悪でもローカルで止まる）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AIには守るべきプロジェクトルールを常に読ませる&lt;/strong&gt;（毎回言い聞かせない）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;この記事は、そのための最小構成の話です。&lt;/p&gt;
&lt;h2&gt;
  
  
  結論：ガードレールは「二段」にすると強い
&lt;/h2&gt;

&lt;p&gt;私はこの2つを重ねるのが一番ラクでした。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.cursor/rules&lt;/code&gt;&lt;/strong&gt;: “AIの意思決定”を縛る（暴走しにくい道を作る）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gitフック（pre-commit / pre-push）&lt;/strong&gt;: “最後の操作”を物理的に止める（暴走しても落ちる）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;.cursor/rules&lt;/code&gt; は「お願い」寄り、Gitフックは「強制」寄り、というイメージです。&lt;/p&gt;
&lt;h2&gt;
  
  
  1) pre-commit：main/develop での直接コミットを禁止する
&lt;/h2&gt;

&lt;p&gt;まずは &lt;code&gt;pre-commit&lt;/code&gt;。&lt;br&gt;
コミットの直前に実行されるフックで、ブランチ名を見て止めます。&lt;/p&gt;

&lt;p&gt;私が使っているのはこんな感じ（ほぼそのまま貼ります）：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# 現在のブランチ名を取得&lt;/span&gt;
&lt;span class="nv"&gt;current_branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git symbolic-ref HEAD | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s,.*/\\(.*\\),\\1,'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# main または develop ブランチならコミット・マージを阻止&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$current_branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"develop"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------------------"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🔴 ERROR: ローカルの '&lt;/span&gt;&lt;span class="nv"&gt;$current_branch&lt;/span&gt;&lt;span class="s2"&gt;' ブランチでの直接コミット/マージは禁止されています。"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   理由: 誤ったブランチへの作業防止のため、フックで制限しています。"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   対処: 作業用ブランチを切ってから変更してください。"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   (例: git checkout -b feature/xxx)"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------------------"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  どこに置く？
&lt;/h3&gt;

&lt;p&gt;一番雑にやるなら、対象リポジトリの &lt;code&gt;.git/hooks/pre-commit&lt;/code&gt; に置きます（実行権限も付ける）。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /path/to/pre-commit .git/hooks/pre-commit
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
&lt;code&gt;.git/hooks&lt;/code&gt; は Git 管理外です。チームで共有したい場合は &lt;code&gt;core.hooksPath&lt;/code&gt; を使って、リポジトリ内のディレクトリにフックを置く方式にすると運用が安定します（後述）。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  これで何が嬉しい？
&lt;/h3&gt;

&lt;p&gt;AIが「じゃあコミットしとくね」って勝手にやりだしても、&lt;strong&gt;main/develop なら物理的に落ちる&lt;/strong&gt;。&lt;br&gt;
「うっかり」系の事故って、だいたい最後の 1 コマンドで起きるので、ここで止まるのは本当に効きます。&lt;/p&gt;
&lt;h2&gt;
  
  
  2) pre-push：main/develop への直接 push を禁止する
&lt;/h2&gt;

&lt;p&gt;次は &lt;code&gt;pre-push&lt;/code&gt;。&lt;br&gt;
push 直前に、Gitが stdin で渡してくる情報（どの ref をどこへ push するか）を見て止めます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# 保護したいリモートブランチのリスト&lt;/span&gt;
&lt;span class="nv"&gt;PROTECTED_BRANCHES&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt; &lt;span class="s2"&gt;"develop"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Gitは pre-push 実行時に、標準入力(stdin)で以下の情報を渡してきます&lt;/span&gt;
&lt;span class="c"&gt;# &amp;lt;local_ref&amp;gt; &amp;lt;local_sha1&amp;gt; &amp;lt;remote_ref&amp;gt; &amp;lt;remote_sha1&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# これをループで読み取ってチェックします&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;local_ref local_sha remote_ref remote_sha
&lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c"&gt;# remote_ref (例: refs/heads/main) からブランチ名部分だけ抽出&lt;/span&gt;
    &lt;span class="nv"&gt;target_branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;remote_ref&lt;/span&gt;&lt;span class="p"&gt;#refs/heads/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;protected &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROTECTED_BRANCHES&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$target_branch&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$protected&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------------------"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🔴 ERROR: リモートの '&lt;/span&gt;&lt;span class="nv"&gt;$target_branch&lt;/span&gt;&lt;span class="s2"&gt;' への直接プッシュは禁止されています。"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   理由: GitHub Free版環境のため、フックで保護しています。"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   対処: Pull Requestを作成してマージしてください。"&lt;/span&gt;
            &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------------------------------------------------"&lt;/span&gt;
            &lt;span class="c"&gt;# 1つでも禁止ブランチへのプッシュが含まれていれば停止&lt;/span&gt;
            &lt;span class="nb"&gt;exit &lt;/span&gt;1
        &lt;span class="k"&gt;fi
    done
done

&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これも &lt;code&gt;.git/hooks/pre-push&lt;/code&gt; に置けばOKです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /path/to/pre-push .git/hooks/pre-push
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .git/hooks/pre-push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  これ、AI対策として強いポイント
&lt;/h3&gt;

&lt;p&gt;AIが暴走してる時って、だいたい「差分がデカい」ことよりも「判断が早い」んですよね。&lt;br&gt;
pre-push は &lt;strong&gt;“pushという行為”を止める&lt;/strong&gt;ので、差分の内容に関係なく安全装置として働きます。&lt;/p&gt;
&lt;h2&gt;
  
  
  3) &lt;code&gt;.cursor/rules&lt;/code&gt;：AIの“行動原理”を縛る（お願いではなく習慣にする）
&lt;/h2&gt;

&lt;p&gt;Gitフックは最後の砦ですが、できればそもそも暴走してほしくない。&lt;br&gt;
そこで効くのが &lt;code&gt;.cursor/rules&lt;/code&gt; です。&lt;/p&gt;

&lt;p&gt;このリポジトリだと、例えばこういう方針がすでにルール化されています：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;main&lt;/code&gt; / &lt;code&gt;develop&lt;/code&gt; worktree では AI はコード変更しない&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1機能=1 feature ブランチ=1 worktree&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;push / PR 作成などのリモート影響操作は、ユーザーの明示指示が必要&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これ、地味にめちゃくちゃ効きます。&lt;br&gt;
理由は単純で、AIが「安全に見える次の一手」を選び続けた結果、事故が減るから。&lt;/p&gt;
&lt;h3&gt;
  
  
  追加で入れておくと事故が減る“言い回し”
&lt;/h3&gt;

&lt;p&gt;私は &lt;code&gt;.cursor/rules&lt;/code&gt; に、次みたいな「禁止」というより「手順」を書くのが好きです。&lt;br&gt;
（AIは“空気”より“手順”に従いやすい）&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## ガードレール&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`git checkout main`&lt;/span&gt; / &lt;span class="sb"&gt;`git checkout develop`&lt;/span&gt; を提案しない（読むだけにする）
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="sb"&gt;`git push`&lt;/span&gt; / &lt;span class="sb"&gt;`gh pr create`&lt;/span&gt; は、必ず「実行してよいか」を先に確認する
&lt;span class="p"&gt;-&lt;/span&gt; 変更は feature worktree 内で完結させ、最後に &lt;span class="sb"&gt;`git status`&lt;/span&gt; と差分を提示する
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
「何をするな」だけだと抜け道を探しがちなので、「代わりに何をするか」まで書くと安定します。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4) （運用編）Gitフックを“リポジトリ管理”したい場合の現実的な落としどころ
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.git/hooks&lt;/code&gt; は共有できないので、チームで揃えるなら &lt;code&gt;core.hooksPath&lt;/code&gt; がラクです。&lt;/p&gt;

&lt;p&gt;例として &lt;code&gt;scripts/githooks/&lt;/code&gt; にフックを置くなら：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; scripts/githooks
&lt;span class="nb"&gt;cp&lt;/span&gt; /path/to/pre-commit scripts/githooks/pre-commit
&lt;span class="nb"&gt;cp&lt;/span&gt; /path/to/pre-push scripts/githooks/pre-push
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x scripts/githooks/pre-commit scripts/githooks/pre-push

git config core.hooksPath scripts/githooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで、そのリポジトリでは Git が &lt;code&gt;scripts/githooks&lt;/code&gt; をフックとして参照します。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
&lt;code&gt;git config core.hooksPath ...&lt;/code&gt; は各ローカルに設定が必要です。READMEに「最初にやること」として書いておくと定着します。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  5) それでも起きる“AI暴走あるある”と対策メモ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;勝手にブランチを切り替える&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.cursor/rules&lt;/code&gt; 側で「develop/main には移動しない」を明文化&lt;/li&gt;
&lt;li&gt;pre-commit が最後に止める&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;勢いで &lt;code&gt;git push origin HEAD&lt;/code&gt; しようとする&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.cursor/rules&lt;/code&gt; 側で「push は確認必須」&lt;/li&gt;
&lt;li&gt;pre-push が最後に止める&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;大規模変更を一気に入れる&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;これはフックよりも運用。&lt;code&gt;.cursor/rules&lt;/code&gt; に「小さく分ける」「差分を見せる」を書くのが効きます&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  まとめ：無料でも“事故は減らせる”。しかも精神が軽い
&lt;/h2&gt;

&lt;p&gt;GitHub側でガチガチに守れない状況でも、ローカルでできることは意外と多いです。&lt;br&gt;
特にAIエディタ相手だと、次の構えが効きました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先に &lt;code&gt;.cursor/rules&lt;/code&gt; で“安全な道”を作る&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最後に Gitフックで“物理ロック”をかける&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;「AIが賢いから大丈夫」じゃなくて、「賢いからこそ、速さにブレーキを付ける」。&lt;br&gt;
これで私は、かなり安心して Cursor / Antigravity / Windsurf と付き合えるようになりました。&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>cursor</category>
      <category>workflow</category>
    </item>
    <item>
      <title>PMBOKの叡智がAIに集約された「PMI Infinity」</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Thu, 04 Dec 2025 05:18:08 +0000</pubDate>
      <link>https://forem.com/beachone1155/pmboknorui-zhi-gaainiji-yue-saretapmi-infinity-21i3</link>
      <guid>https://forem.com/beachone1155/pmboknorui-zhi-gaainiji-yue-saretapmi-infinity-21i3</guid>
      <description>&lt;h2&gt;
  
  
  この記事でわかること
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PMI InfinityがPMBOKや公式アジャイルガイドの知見をどう再編集してくれるのか&lt;/li&gt;
&lt;li&gt;2025年時点で追加された「Explore Project Management」やキャリア支援系の新カード&lt;/li&gt;
&lt;li&gt;実務での使いどころとリスク、導入の段取り&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;先週、緊急レビュー前にナレッジを探して30分もPMBOKをめくってしまい「あれ、AIに聞けばよかったのでは？」と頭を抱えました。同じモヤモヤを抱えているPMの方に向けて、スクリーンショット付きでPMI Infinityの最新状況を整理します。&lt;/p&gt;

&lt;h2&gt;
  
  
  なぜ今PMI Infinityなのか
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;公式ソース優先&lt;/strong&gt;: PMBOK第7版やアジャイル実践ガイドの文章を優先的に返し、出典リンクも表示されるので、社内レビューで根拠を聞かれても即回答できます。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;業界横断のベストプラクティス&lt;/strong&gt;: 14,000人超の専門家コミュニティで学習済み。ITだけでなく建設・金融のケースにも触れられます。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;リアルタイム回答&lt;/strong&gt;: 会議中にスマホから聞いても5分以内に代替案を拾えるスピード感。Slackで相談して返事を待つ時間が丸ごと削減されました。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2024年2月以降はPMI会員限定になりますが、今は検証に十分な30日間のフリートライアル期間が残っています。迷っているなら今のうちに触ってクセづけるのが吉です。&lt;/p&gt;

&lt;h2&gt;
  
  
  PMI Infinityの最新トピック（2025年版）
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;トピック&lt;/th&gt;
&lt;th&gt;何が変わったか&lt;/th&gt;
&lt;th&gt;現場での価値&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Explore Project Managementハブ&lt;/td&gt;
&lt;td&gt;4つのカード（資格、ヘルスチェック、スコープ管理、ステークホルダー管理）で入口が整理された&lt;/td&gt;
&lt;td&gt;迷子にならず、議題ごとにテンプレとFAQへ最短アクセス&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;プロジェクト健全性チェック&lt;/td&gt;
&lt;td&gt;チャット内で質問票を生成し、回答に応じたタスクリストを返してくれる&lt;/td&gt;
&lt;td&gt;定例の事前アンケートを5分で配布でき、翌日の会議がスムーズ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;キャリアガイダンス回答&lt;/td&gt;
&lt;td&gt;PMPやAgile認定、スキルトレーニングを踏まえた成長ロードマップが提示される&lt;/td&gt;
&lt;td&gt;メンバーの育成プランをその場で提案でき、1on1が濃密に&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;出典テキスト表示&lt;/td&gt;
&lt;td&gt;引用元章節がカード化され、リンクをワンクリックで開ける&lt;/td&gt;
&lt;td&gt;監査対応やステークホルダー説得資料の作成が高速化&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;個人的には、キャリア相談カードが登場したことで新人PMとの壁打ちが一気に楽になりました。「どこから着手すればいいですか？」という質問に対し、Infinityの回答をベースに現場色を足すだけでよくなります。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5loaiwcl7ppbollrexk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5loaiwcl7ppbollrexk.gif" alt="PMI Infinity" width="600" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  実際の画面で追体験
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Explore Project Managementのホーム
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5x6dvf8qmqadx4bj4ml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5x6dvf8qmqadx4bj4ml.png" alt="Explore Project Managementのホーム" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;初期画面で4カテゴリがタイル表示されます。スコープ調整のメール文面や資格取得のROI比較など、課題別のテンプレにジャンプできるので社内ポータルより早く目的地に到着できます。&lt;/p&gt;

&lt;h3&gt;
  
  
  2. リアルタイムQAでバッファ計画を確認
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eu3pxdu4tw6ysg1de0j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8eu3pxdu4tw6ysg1de0j.png" alt="クリティカルチェーンのバッファ解説" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;「プロジェクトのバッファをどう説明する？」と聞くと、フィーディング／リソース／プロジェクトバッファを整理した上で、クリティカルチェーン方式の計算例を即座に返してくれます。私はレビュー直前にこの回答を参考にし、バッファ設定の理由をスライド1枚にまとめて難を逃れました。&lt;/p&gt;

&lt;h3&gt;
  
  
  3. キャリア相談カードで成長ロードマップを生成
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65seshvm5dp2wv6dccc9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65seshvm5dp2wv6dccc9.png" alt="IT PM向けキャリア提案" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;「ITコンサルでキャリアを伸ばすには？」と聞くと、ジュニア→シニア→プログラムマネージャーの階段と取得すべき資格、推奨書籍、コミュニティ紹介まで返します。メンター役としてはこの回答を叩き台に余白を足すイメージです。&lt;/p&gt;

&lt;h2&gt;
  
  
  活用シナリオ（自分の体験も交えつつ）
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;月曜朝のリスクレビュー前&lt;/strong&gt;: Infinityでテンプレを取得し、リスク登録票を15分で更新。結果として会議が15分短縮されました。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ステークホルダーとの交渉&lt;/strong&gt;: Scope Creepカードを開き、チェンジリクエストの説明メールをそのまま英訳して送付。海外チームからも理解が早いと好評でした。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;新人PMのオンボーディング&lt;/strong&gt;: キャリアカードで推奨教材を出し、そのままNotionに貼り付けて育成トラック化。質問が減り、自走が早まりました。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  注意点と運用のコツ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;機密情報は入れない&lt;/strong&gt;: 具体的な案件名やコストは伏せ、パラメータだけを与える。必要ならプレーンな数字に置き換える。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI案＋現場の温度差を調整&lt;/strong&gt;: Infinityはグローバル基準寄り。日本の顧客文化に合わせたトーン修正は必須です。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ログイン要件を早めに確認&lt;/strong&gt;: 2024年2月12日以降はPMI会員限定になる予定なので、組織アカウントの準備を進めておく。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  今すぐ試すための3ステップ
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://infinity.pmi.org/" rel="noopener noreferrer"&gt;公式サイト&lt;/a&gt;にアクセスし、PMIアカウントでログイン。&lt;/li&gt;
&lt;li&gt;Explore Project Managementのカードから、今日困っているテーマを1つ選ぶ。&lt;/li&gt;
&lt;li&gt;得た回答をチームに共有し、どれだけリードタイムが縮んだかを記録。ROIが明確になると導入申請が通りやすくなります。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;PMI Infinityは「情報検索に奪われる時間」と「根拠の曖昧さ」を同時に解決してくれるAIパートナーです。出典付きでPMBOKの文言を引用でき、ステークホルダー説明が驚くほどスムーズになります。現場の文脈で味付けしながら、まずは1週間使い倒して効果を測ってみてください。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;参考: &lt;a href="https://enhanced.co.jp/hack/pmi-infinity-ai-project-management/" rel="noopener noreferrer"&gt;Enhanced Inc. 業務効率化ブログ「PMI Infinity」特集（2025/07/02 更新）&lt;/a&gt;&lt;br&gt;
&lt;a href="https://infinity.pmi.org/" rel="noopener noreferrer"&gt;PMI Infinity公式サイト&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.pmi-japan.org/" rel="noopener noreferrer"&gt;PMI日本支部&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.nttdata.com/global/en/insights/focus/2024/pmi-infinity" rel="noopener noreferrer"&gt;NTT DATA - PMI Infinityについて&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>pmi</category>
      <category>projectmanagement</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>技術ブログにMermaidダイアグラムを導入した話【はてなブログ・DEV.to・Next.js対応】</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Tue, 25 Nov 2025 02:47:08 +0000</pubDate>
      <link>https://forem.com/beachone1155/ji-shu-burogunimermaiddaiaguramuwodao-ru-sitahua-hatenaburogudevtonextjsdui-ying--1c9e</link>
      <guid>https://forem.com/beachone1155/ji-shu-burogunimermaiddaiaguramuwodao-ru-sitahua-hatenaburogudevtonextjsdui-ying--1c9e</guid>
      <description>&lt;h1&gt;
  
  
  技術ブログにMermaidダイアグラムを導入した話【はてなブログ・DEV.to・Next.js対応】
&lt;/h1&gt;

&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;技術記事を書く時、フローチャートやシーケンス図があると格段に分かりやすくなりますよね。&lt;/p&gt;

&lt;p&gt;今回、マルチプラットフォーム対応の技術ブログで&lt;strong&gt;Mermaid記法&lt;/strong&gt;によるダイアグラム表示に対応したので、その実装方法を紹介します！&lt;/p&gt;

&lt;p&gt;対応したプラットフォーム：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📝 &lt;strong&gt;はてなブログ&lt;/strong&gt; - カスタムJavaScriptで対応&lt;/li&gt;
&lt;li&gt;🌐 &lt;strong&gt;DEV.to&lt;/strong&gt; - Mermaidを画像に変換して投稿&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Next.js（Vercel）&lt;/strong&gt; - クライアントサイドレンダリング&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mermaidとは？
&lt;/h2&gt;

&lt;p&gt;Mermaidは、テキストベースでダイアグラムを描けるJavaScriptライブラリです。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;記述例:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;graph TD
    A[ユーザー] --&amp;gt;|記事を書く| B[Markdown]
    B --&amp;gt;|変換| C[Mermaid]
    C --&amp;gt;|レンダリング| D[ダイアグラム表示]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;シンプルなテキストで、こんな感じの図が描けます。めっちゃ便利！&lt;/p&gt;

&lt;h2&gt;
  
  
  各プラットフォームの対応方法
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. はてなブログでの対応
&lt;/h3&gt;

&lt;p&gt;はてなブログは標準でMermaidをサポートしていないため、&lt;strong&gt;カスタムJavaScript&lt;/strong&gt;で対応しました。&lt;/p&gt;

&lt;h4&gt;
  
  
  実装手順
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;1) はてなブログの管理画面で「設定」→「詳細設定」を開く&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) 「headに要素を追加」に以下を貼り付け:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Mermaidダイアグラムのスタイル設定 --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
  &lt;span class="nc"&gt;.mermaid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nc"&gt;.mermaid&lt;/span&gt; &lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Mermaidダイアグラム表示用スクリプト --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mermaid&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DOMContentLoaded&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;startOnLoad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;themeVariables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;securityLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loose&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pre code&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;codeBlock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;codeText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;codeBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;codeBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;codeText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mermaid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;codeBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;codeText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mermaidCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mermaidDiv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="nx"&gt;mermaidDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mermaid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;mermaidDiv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mermaidCode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="nx"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mermaidDiv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nx"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.mermaid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3) 記事では以下のように書く:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;

mermaid
graph LR
    A --&amp;gt; B


&lt;span class="p"&gt;```&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;
`

&amp;gt; [!IMPORTANT]
&amp;gt; はてなブログでは `

 ```mermaid ` という書き方ができないため、コードブロック内の最初の行に `mermaid` と書く必要があります。

#### ポイント

- ✅ **白背景を強制**: 暗い背景になる問題を解決
- ✅ **CDN経由**: 最新版のMermaidを自動で利用
- ✅ **一度設定すれば全記事に適用**

### 2. DEV.toでの対応

DEV.toは**Mermaidをネイティブサポートしていない**ため、画像に変換して投稿する方式を採用しました。

#### 実装（自動化）

Node.jsスクリプトで、記事投稿時に自動的にMermaidブロックを画像に変換します。

&lt;span class="p"&gt;```&lt;/span&gt;

javascript
// scripts/utils/mermaid-converter.mjs
import { execSync } from 'child_process';

export async function convertMermaidToImage(mermaidCode, outputPath) {
    const tempInputFile = &lt;span class="sb"&gt;`/tmp/mermaid-${Date.now()}.mmd`&lt;/span&gt;;&lt;span class="sb"&gt;

    fs.writeFileSync(tempInputFile, mermaidCode, 'utf8');

    // mermaid-cliで画像変換（背景は白色）
    execSync(
        `npx -y @mermaid-js/mermaid-cli@latest -i "${tempInputFile}" -o "${outputPath}" -b white`,
        { stdio: 'inherit' }
    );

    fs.unlinkSync(tempInputFile);
    return true;
&lt;/span&gt;}


&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;
&lt;/span&gt;
&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;変換の流れ&lt;/span&gt;&lt;span class="o"&gt;:**&lt;/span&gt;
&lt;span class="mf"&gt;1.&lt;/span&gt; &lt;span class="nc"&gt;Markdown内の&lt;/span&gt; &lt;span class="sb"&gt;` ```&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;mermaid&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;` ブロックを検出
2. `&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`で画像（PNG）に変換
3. `&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;diagrams&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`に保存
4. Markdownを `&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;diagram&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;diagrams&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;xxx&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;` に置き換え

#### メリット

- ✅ DEV.toでも確実に表示される
- ✅ 画像なのでロード時間が速い
- ✅ ダークモードでも見やすい（白背景指定）

### 3. Next.js（Vercel）での対応

自サイト（Next.js）では、クライアントサイドでMermaidをレンダリングしています。

#### 実装

`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="n"&gt;tsx&lt;/span&gt;
&lt;span class="c1"&gt;// src/components/mdx/MermaidRenderer.tsx&lt;/span&gt;
&lt;span class="s1"&gt;'use client'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'react'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;export&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;MermaidRenderer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;initMermaid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;mermaid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'mermaid'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="n"&gt;startOnLoad&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;themeVariables&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'#ffffff'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// ダークモードでも白背景&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;securityLevel&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'loose'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;codeBlocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pre code.language-mermaid'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;codeBlocks&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="k"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;codeBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;codeBlock&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parentElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;codeBlock&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="no"&gt;wrapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'div'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'mermaid-diagram-wrapper'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`mermaid-diagram-${index}`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="n"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'.mermaid-diagram-wrapper'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nf"&gt;initMermaid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`

#### ダークモード対応（CSS）

`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;
&lt;span class="cm"&gt;/* src/app/globals.css */&lt;/span&gt;
&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;diagram&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;diagram&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt; &lt;span class="n"&gt;svg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;#ffffff !important;&lt;/span&gt;
  &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;margin&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* ダークモードでは薄い枠線を追加 */&lt;/span&gt;
&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;diagram&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="c1"&gt;#475569;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`

## 苦労した点と解決方法

### 問題1: 背景が暗くて見づらい

**症状:**
デフォルトのMermaidテーマだと、ダイアグラムの背景が暗い色になり、矢印や文字が見にくい。

**解決策:**
- `&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="n"&gt;themeVariables&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;background&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'#ffffff'&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`で強制的に白背景に
- CSSで`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`要素にも白背景を適用

### 問題2: ダークモードで真っ白すぎて浮く

**症状:**
ダークモードのページで白背景のダイアグラムが浮いて見える。

**解決策:**
`&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="n"&gt;css&lt;/span&gt;
&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dark&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mermaid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;diagram&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;border&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;px&lt;/span&gt; &lt;span class="n"&gt;solid&lt;/span&gt; &lt;span class="c1"&gt;#475569; /* 薄い枠線を追加 */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="sb"&gt;``&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`

### 問題3: はてなブログで `&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;```&lt;/span&gt;

mermaid &lt;span class="sb"&gt;` が使えない

**症状:**
はてなブログはコードブロックの言語指定に対応していない。

**解決策:**
コードブロック内の最初の行を `&lt;/span&gt;mermaid&lt;span class="err"&gt;`&lt;/span&gt; にして、JavaScriptで判定する方式に変更。

&lt;span class="gu"&gt;## まとめ&lt;/span&gt;

Mermaidダイアグラムを3つのプラットフォームで表示できるようにした実装を紹介しました。

&lt;span class="gs"&gt;**ポイント:**&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 📝 &lt;span class="gs"&gt;**はてなブログ**&lt;/span&gt;: カスタムJSで動的レンダリング
&lt;span class="p"&gt;-&lt;/span&gt; 🌐 &lt;span class="gs"&gt;**DEV.to**&lt;/span&gt;: CLIで事前に画像変換
&lt;span class="p"&gt;-&lt;/span&gt; ⚡ &lt;span class="gs"&gt;**Next.js**&lt;/span&gt;: クライアントサイドレンダリング + ダークモード対応

各プラットフォームの特性に合わせた実装で、どこでも快適にダイアグラムが表示されるようになりました！

技術記事に図解を入れたい方の参考になれば嬉しいです 🎉

&lt;span class="gu"&gt;## 参考リンク&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Mermaid公式ドキュメント&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://mermaid.js.org/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Mermaid CLI&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://github.com/mermaid-js/mermaid-cli&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;はてなブログのカスタマイズ&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://help.hatenablog.com/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="ge"&gt;*この記事の図解もすべてMermaidで作成されています！*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>mermaid</category>
      <category>javascript</category>
      <category>nextjs</category>
      <category>webdev</category>
    </item>
    <item>
      <title>GoogleのAIエディタ「Antigravity」を触ってみた！設定やワークフローも解説</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Tue, 25 Nov 2025 01:22:53 +0000</pubDate>
      <link>https://forem.com/beachone1155/googlenoaiedeitaantigravity-wohong-tutemitashe-ding-yawakuhuromojie-shuo-2lll</link>
      <guid>https://forem.com/beachone1155/googlenoaiedeitaantigravity-wohong-tutemitashe-ding-yawakuhuromojie-shuo-2lll</guid>
      <description>&lt;h1&gt;
  
  
  GoogleのAIエディタ「Antigravity」を触ってみた！設定やワークフローも解説
&lt;/h1&gt;

&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;Googleが2025年11月18日に発表した新しいAIエディタ「&lt;strong&gt;Antigravity&lt;/strong&gt;」。&lt;br&gt;
「重力から解放されたかのような軽快なコーディング体験」を謳うこのエディタ、気になっている方も多いのではないでしょうか？&lt;/p&gt;

&lt;p&gt;今回は、実際にAntigravityを使って簡単なアプリ開発を試してみた感想と、Antigravityの真骨頂とも言える「&lt;strong&gt;プロジェクト固有のルール設定&lt;/strong&gt;」や「&lt;strong&gt;ワークフロー機能&lt;/strong&gt;」について深掘りして解説します！&lt;/p&gt;
&lt;h2&gt;
  
  
  Antigravityとは？
&lt;/h2&gt;

&lt;p&gt;Antigravityは、Googleが開発した次世代のAIネイティブエディタです。&lt;br&gt;
単なるコード補完だけでなく、&lt;strong&gt;「Agent」&lt;/strong&gt;と呼ばれるAIアシスタントが開発プロセス全体をサポートしてくれるのが最大の特徴です。&lt;/p&gt;

&lt;p&gt;主な特徴は以下の通り：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Agentic AI&lt;/strong&gt;: 指示を出すだけで、計画・実装・検証まで自律的に行ってくれる&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Context Awareness&lt;/strong&gt;: プロジェクト全体の文脈を理解し、適切な提案をしてくれる&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Customizability&lt;/strong&gt;: プロジェクトごとのルールやワークフローを柔軟に定義できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  実際に触ってみた
&lt;/h2&gt;

&lt;p&gt;今回は、Qiitaの記事「&lt;a href="https://qiita.com/yokko_mystery/items/bb5615ebcd385a597c41" rel="noopener noreferrer"&gt;Googleが発表したAIエディタ、Antigravityを触ってみた。&lt;/a&gt;」を参考に、簡単なAIチャットアプリを作成してみました。&lt;/p&gt;
&lt;h3&gt;
  
  
  1. インストールと起動
&lt;/h3&gt;

&lt;p&gt;まずは公式サイトからインストーラーをダウンロードしてインストール。&lt;br&gt;
起動すると、「Open Agent Manager」という画面が表示され、そこからプロジェクトを開始できます。&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/antigravity-open-agent-manager.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/antigravity-open-agent-manager.png" alt="Open Agent Manager"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Open Agent Managerの起動画面（イメージ）&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. 指示出し
&lt;/h3&gt;

&lt;p&gt;「Agent Manager」に作りたいアプリの概要を伝えます。&lt;br&gt;
今回は「Next.jsを使って、シンプルなAIチャットアプリを作って」と指示しました。&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/antigravity-agent-manager-chat.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/antigravity-agent-manager-chat.png" alt="Agent Manager Chat"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Agentとのチャット画面（イメージ）&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;すると、Antigravityはすぐに実装プランを提示し、必要なファイルの作成やパッケージのインストールを開始！&lt;br&gt;
見ていて気持ちいいくらいサクサク進みます。&lt;/p&gt;
&lt;h3&gt;
  
  
  3. 実装と確認
&lt;/h3&gt;

&lt;p&gt;実装が完了すると、自動的にブラウザが立ち上がり、アプリが起動しました。&lt;br&gt;
デザインもモダンで、基本的な機能はしっかり動作しています。&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/antigravity-generated-app.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/antigravity-generated-app.png" alt="Generated App"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;生成されたAIチャットアプリ（イメージ）&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;「ここをこう直して」といった追加の指示も、自然言語で伝えるだけですぐに反映されました。&lt;/p&gt;
&lt;h2&gt;
  
  
  Antigravityの真価：ルールとワークフロー
&lt;/h2&gt;

&lt;p&gt;さて、ここからが本題です。&lt;br&gt;
Antigravityが他のAIエディタと一線を画すのが、&lt;strong&gt;&lt;code&gt;.agent&lt;/code&gt; ディレクトリによるカスタマイズ機能&lt;/strong&gt;です。&lt;/p&gt;

&lt;p&gt;プロジェクトルートに &lt;code&gt;.agent&lt;/code&gt; ディレクトリを作成し、そこに設定ファイルを置くことで、AIの振る舞いを細かく制御できます。&lt;/p&gt;
&lt;h3&gt;
  
  
  1. &lt;code&gt;rules.md&lt;/code&gt; でプロジェクトのルールを教える
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.agent/rules.md&lt;/code&gt;（または &lt;code&gt;.cursor/rules/*.mdc&lt;/code&gt; から生成される &lt;code&gt;rules.md&lt;/code&gt;）は、AIに守らせたいルールを記述するファイルです。&lt;/p&gt;

&lt;p&gt;例えば、以下のようなルールを定義できます：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;言語設定&lt;/strong&gt;: 「コミットメッセージやドキュメントは全て日本語で書くこと」&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;技術スタック&lt;/strong&gt;: 「スタイリングにはTailwind CSSを使用し、CSS Modulesは使わないこと」&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;コーディング規約&lt;/strong&gt;: 「関数コンポーネントはアロー関数で定義すること」&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;設定例 (&lt;code&gt;.agent/rules.md&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Project Rules&lt;/span&gt;

&lt;span class="gu"&gt;## General&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Language**&lt;/span&gt;: Always respond in Japanese.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Framework**&lt;/span&gt;: Use Next.js App Router.

&lt;span class="gu"&gt;## Coding Standards&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use &lt;span class="sb"&gt;`const`&lt;/span&gt; for all variable declarations.
&lt;span class="p"&gt;-&lt;/span&gt; Prefer functional components over class components.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これを置いておくだけで、Antigravityはこのルールを常に意識してコードを生成してくれます。&lt;br&gt;
「毎回『日本語で書いて』って言うのが面倒…」といった悩みから解放されます！&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/diagrams/antigravity-first-look-mermaid-1-4b34d0f9.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/diagrams/antigravity-first-look-mermaid-1-4b34d0f9.png" alt="Mermaid Diagram 1"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;rules.mdがAgentに与える影響のイメージ&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;code&gt;.agent/workflows&lt;/code&gt; で定型作業を自動化
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;.agent/workflows&lt;/code&gt; ディレクトリには、よく行う一連の作業手順（ワークフロー）をMarkdown形式で定義できます。&lt;/p&gt;

&lt;p&gt;例えば、「新機能開発のフロー」や「デプロイ手順」などを定義しておくと、AIがその手順に沿って作業を進めてくれます。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;設定例 (&lt;code&gt;.agent/workflows/feature-development.md&lt;/code&gt;):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;新機能開発のワークフロー"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="p"&gt;1.&lt;/span&gt; 最新の &lt;span class="sb"&gt;`develop`&lt;/span&gt; ブランチから &lt;span class="sb"&gt;`feature/xxx`&lt;/span&gt; ブランチを作成する
&lt;span class="p"&gt;2.&lt;/span&gt; 実装を行う
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="sb"&gt;`npm run lint`&lt;/span&gt; と &lt;span class="sb"&gt;`npm run test`&lt;/span&gt; を実行して問題ないか確認する
&lt;span class="p"&gt;4.&lt;/span&gt; PRを作成する（タイトルと説明は日本語で）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;このように定義しておけば、「新機能開発フローを実行して」と伝えるだけで、ブランチ作成からテスト、PR作成までをミスなく実行してくれます。&lt;br&gt;
チーム開発でのオペレーション統一に非常に強力です。&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/diagrams/antigravity-first-look-mermaid-2-1641c8f1.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/diagrams/antigravity-first-look-mermaid-2-1641c8f1.png" alt="Mermaid Diagram 2"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;ワークフロー実行の流れ&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;Antigravityは、単にコードを書くだけでなく、&lt;strong&gt;「開発プロセスそのもの」をAIと一緒に作り上げていく&lt;/strong&gt;感覚が新しいエディタでした。&lt;/p&gt;

&lt;p&gt;特に &lt;code&gt;rules.md&lt;/code&gt; と &lt;code&gt;workflows&lt;/code&gt; を活用することで、自分たちだけの「最強のAIペアプログラマー」に育て上げることができます。&lt;br&gt;
まだ触っていない方は、ぜひ一度試してみてください！&lt;/p&gt;




</description>
      <category>antigravity</category>
      <category>google</category>
      <category>ai</category>
      <category>editor</category>
    </item>
    <item>
      <title>AI駆動型販売・在庫管理システム「OpsPilot Console」の実装と展望</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Tue, 25 Nov 2025 01:22:40 +0000</pubDate>
      <link>https://forem.com/beachone1155/aiqu-dong-xing-fan-mai-zai-ku-guan-li-sisutemuopspilot-console-noshi-zhuang-tozhan-wang-pa</link>
      <guid>https://forem.com/beachone1155/aiqu-dong-xing-fan-mai-zai-ku-guan-li-sisutemuopspilot-console-noshi-zhuang-tozhan-wang-pa</guid>
      <description>&lt;h1&gt;
  
  
  AI駆動型販売・在庫管理システム「OpsPilot Console」の実装と展望
&lt;/h1&gt;

&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;こんにちは！今回は、現在開発中の「OpsPilot Console」というプロジェクトについてご紹介します。&lt;br&gt;
これは、中小企業向けの販売・受発注・在庫管理を一つのNext.jsアプリケーションで完結させるという、野心的な試みです。&lt;/p&gt;

&lt;p&gt;「業務システムって堅苦しくて使いにくい...」そんなイメージを払拭すべく、モダンな技術スタックでサクサク動く、そしてAIの力で業務を効率化するシステムを目指しています。&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/ai-driven-sales-inventory-system-intro-dashboard.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/ai-driven-sales-inventory-system-intro-dashboard.png" alt="OpsPilot Console Dashboard Mockup"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;開発中のダッシュボード画面イメージ&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  アーキテクチャと技術スタック
&lt;/h2&gt;

&lt;p&gt;OpsPilot Consoleは、&lt;strong&gt;モノリポ構成&lt;/strong&gt;を採用しています。フロントエンドとAPIをNext.jsのApp Router内で同居させることで、開発効率を最大化しています。&lt;/p&gt;

&lt;p&gt;主な技術スタックは以下の通りです。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: Next.js 16 (App Router) / React 19&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: Tailwind CSS v4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: Neon Postgres&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ORM&lt;/strong&gt;: Prisma ORM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: TypeScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  なぜこのスタックなのか？
&lt;/h3&gt;

&lt;p&gt;最大の理由は&lt;strong&gt;「開発スピードと保守性の両立」&lt;/strong&gt;です。&lt;br&gt;
Next.jsのApp Routerを使えば、サーバーサイドとクライアントサイドの境界を意識しつつも、シームレスにデータを扱えます。&lt;br&gt;
また、DBにはサーバーレスPostgresであるNeonを採用。Prismaとの相性も抜群で、スキーマ定義から型安全なDB操作までがスムーズに行えます。&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/ai-driven-sales-inventory-system-intro-arch.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/ai-driven-sales-inventory-system-intro-arch.png" alt="OpsPilot Architecture Diagram"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;OpsPilot Consoleのアーキテクチャ概要&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  実装の詳細
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ディレクトリ構造
&lt;/h3&gt;

&lt;p&gt;プロジェクトの構造は非常にシンプルです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  app/                   # App Router エントリ
    (dashboard)/         # 業務画面のルートグループ
    api/                 # Route Handlers
  components/            # UI コンポーネント
  lib/                   # 共通ユーティリティ
prisma/
  schema.prisma          # Prisma スキーマ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Container Use / git worktree フロー
&lt;/h3&gt;

&lt;p&gt;開発フローにもこだわりがあります。&lt;br&gt;
すべての開発は&lt;strong&gt;Container Use (Dagger MCP)&lt;/strong&gt; のコンテナ内で行い、ホストマシンの環境を汚しません。&lt;br&gt;
また、&lt;code&gt;git worktree&lt;/code&gt;を活用することで、ブランチの切り替えコストをゼロにしています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 例: 新機能開発の流れ&lt;/span&gt;
container-use new feature-xyz &lt;span class="nt"&gt;--branch&lt;/span&gt; feature/xyz
container-use enter feature-xyz
&lt;span class="c"&gt;# ... 開発 ...&lt;/span&gt;
container-use merge feature-xyz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="/images/ai-driven-sales-inventory-system-intro-flow.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/ai-driven-sales-inventory-system-intro-flow.png" alt="Container Use Development Flow"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Container UseとGit Worktreeを活用した開発フロー&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;これにより、複数のタスクを並行して進める際も、環境の競合を気にせず集中できます。&lt;/p&gt;

&lt;h2&gt;
  
  
  今後の展望 (Roadmap)
&lt;/h2&gt;

&lt;p&gt;さて、ここからが本題です。現在は基礎的なCRUD機能の実装が進んでいますが、今後は以下の機能を追加していく予定です。&lt;/p&gt;

&lt;h3&gt;
  
  
  1. トランザクションと在庫履歴の実装
&lt;/h3&gt;

&lt;p&gt;販売管理システムの肝となる、受注・発注のトランザクション処理と、それに伴う入出庫履歴の記録機能を実装します。&lt;br&gt;
Prismaのトランザクション機能を活用し、データの整合性を担保しつつ、高速な処理を目指します。&lt;/p&gt;

&lt;h3&gt;
  
  
  2. APIの拡張とワークフロー
&lt;/h3&gt;

&lt;p&gt;Route Handlersを拡張し、より複雑な業務ロジックをAPIとして提供します。&lt;br&gt;
特に、発注承認フローなどのワークフロー機能は、システムの実用性を大きく高める重要な機能です。&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 品質保証の自動化
&lt;/h3&gt;

&lt;p&gt;現在、GitHub Issuesでも管理していますが、品質保証の自動化は急務です。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;統合テストの追加&lt;/strong&gt;: VitestとPrisma Test DBを使って、API層の振る舞いを自動検証します（Issue #11）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CDパイプライン&lt;/strong&gt;: GitHub Actionsでlint、test、build、そしてPrismaのチェックを自動化し、常にデプロイ可能な状態を保ちます（Issue #12）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2Eテスト&lt;/strong&gt;: Playwrightを導入し、実際のユーザー操作を模したテストを行うことで、UIの不具合を未然に防ぎます。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;OpsPilot Consoleは、単なる管理画面ではなく、&lt;strong&gt;「開発者が楽しみながら作れる、ユーザーが快適に使える」&lt;/strong&gt;システムを目指しています。&lt;br&gt;
モダンな技術を積極的に取り入れつつ、業務システムの要件もしっかり満たす。そんなバランスの取れた開発を続けていきます。&lt;/p&gt;

&lt;p&gt;今後も開発の進捗や技術的な知見をブログで発信していきますので、ぜひチェックしてください！&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>prisma</category>
      <category>neon</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Next.jsでQiita風の見出しアンカーリンクを実装する方法</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Fri, 14 Nov 2025 05:50:27 +0000</pubDate>
      <link>https://forem.com/beachone1155/nextjsdeqiitafeng-nojian-chu-siankarinkuwoshi-zhuang-surufang-fa-bkj</link>
      <guid>https://forem.com/beachone1155/nextjsdeqiitafeng-nojian-chu-siankarinkuwoshi-zhuang-surufang-fa-bkj</guid>
      <description>&lt;h1&gt;
  
  
  Next.jsでQiita風の見出しアンカーリンクを実装する方法
&lt;/h1&gt;

&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;技術ブログを運営していると、Qiitaのような見出しにホバーするとリンクアイコンが表示される機能が欲しくなりますよね。実は、これって意外と簡単に実装できるんです。&lt;/p&gt;

&lt;p&gt;今回は、Next.jsと&lt;code&gt;rehype&lt;/code&gt;を使って、Qiita風の見出しアンカーリンクを実装した際の経験を共有します。最初は位置がずれてしまったり、ハイドレーションエラーに悩まされたりしましたが、最終的には綺麗に動作するようになりました。&lt;/p&gt;

&lt;h2&gt;
  
  
  実装の背景
&lt;/h2&gt;

&lt;p&gt;私のブログでは、Markdownで記事を書いて、&lt;code&gt;unified&lt;/code&gt;と&lt;code&gt;rehype&lt;/code&gt;を使ってHTMLに変換しています。見出しに自動でアンカーリンクを追加する機能は&lt;code&gt;rehype-autolink-headings&lt;/code&gt;というプラグインで実現できるのですが、Qiitaのように見出しの左側にアイコンを表示するには、いくつか工夫が必要でした。&lt;/p&gt;

&lt;h2&gt;
  
  
  実装手順
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 必要なパッケージのインストール
&lt;/h3&gt;

&lt;p&gt;まず、必要なパッケージをインストールします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;unified remark-parse remark-gfm remark-rehype rehype-stringify rehype-highlight rehype-slug rehype-autolink-headings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Markdownコンポーネントの実装
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;rehype-autolink-headings&lt;/code&gt;を使って、見出しに自動でリンクを追加します。ポイントは&lt;code&gt;behavior: 'prepend'&lt;/code&gt;を使うことです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeStringify from 'rehype-stringify';
import rehypeHighlight from 'rehype-highlight';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';

export function Markdown({ content, className }: MarkdownProps) {
  const htmlContent = unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(remarkRehype, { allowDangerousHtml: false })
    .use(rehypeSlug)
    .use(rehypeAutolinkHeadings, {
      behavior: 'prepend', // 見出しの内部（最初の子要素）にリンクを追加
      properties: {
        className: ['anchor-link'],
        'aria-label': '見出しへのリンク',
      },
      content() {
        // SVGアイコンを返す
        return {
          type: 'element',
          tagName: 'svg',
          properties: {
            className: ['anchor-link-icon'],
            width: '16',
            height: '16',
            viewBox: '0 0 16 16',
            fill: 'currentColor',
            'aria-hidden': 'true',
          },
          children: [
            {
              type: 'element',
              tagName: 'path',
              properties: {
                d: 'M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 11-2.83-2.83l2.5-2.5z',
              },
              children: [],
            },
          ],
        };
      },
    })
    .use(rehypeHighlight, {
      detect: true,
      ignoreMissing: true,
    })
    .use(rehypeStringify, { allowDangerousHtml: true })
    .processSync(content);

  return (
    &amp;lt;div 
      className={className || ''}
      dangerouslySetInnerHTML={{ __html: String(htmlContent) }}
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. CSSスタイルの実装
&lt;/h3&gt;

&lt;p&gt;見出しをFlexboxにして、アイコンとテキストを同じ行に配置します。また、アイコンは通常は非表示にして、ホバー時に表示するようにします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/* Qiita風の見出しアンカーリンク */
.article-content .anchor-link,
.prose .anchor-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: #94a3b8;
  opacity: 0; /* 通常は非表示 */
  transition: opacity 0.2s, color 0.2s;
  flex-shrink: 0;
}

.article-content .anchor-link-icon,
.prose .anchor-link-icon {
  width: 16px;
  height: 16px;
  display: inline-block;
}

/* ホバー時にアイコンを表示 */
.article-content h2:hover .anchor-link,
.article-content h3:hover .anchor-link,
.article-content h4:hover .anchor-link,
.prose h2:hover .anchor-link,
.prose h3:hover .anchor-link,
.prose h4:hover .anchor-link {
  opacity: 1;
}

.article-content .anchor-link:hover,
.prose .anchor-link:hover {
  color: #3b82f6; /* ホバー時は青色に */
  opacity: 1;
}

/* 見出しをFlexboxにして、アイコンとテキストを同じ行に配置 */
.article-content h2,
.article-content h3,
.article-content h4,
.prose h2,
.prose h3,
.prose h4 {
  color: inherit;
  text-decoration: none;
  position: relative;
  padding-bottom: 0.3em;
  border-bottom: 1px solid #e2e8f0; /* 下線を追加 */
  margin-top: 1.5em;
  margin-bottom: 0.8em;
  display: flex; /* Flexboxに変更 */
  align-items: center;
  gap: 0.5rem; /* アイコンとテキストの間隔 */
}

.dark .article-content h2,
.dark .article-content h3,
.dark .article-content h4,
.dark .prose h2,
.dark .prose h3,
.dark .prose h4 {
  border-bottom-color: #475569;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  つまずいたポイントと解決方法
&lt;/h2&gt;

&lt;h3&gt;
  
  
  問題1: リンクアイコンが見出しの上に表示されてしまう
&lt;/h3&gt;

&lt;p&gt;最初は&lt;code&gt;behavior: 'before'&lt;/code&gt;を使っていたのですが、これだとリンクが見出し要素の&lt;strong&gt;外側&lt;/strong&gt;（兄弟要素）に配置されてしまい、見出しの&lt;code&gt;display: flex&lt;/code&gt;が効きませんでした。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解決方法&lt;/strong&gt;: &lt;code&gt;behavior: 'prepend'&lt;/code&gt;に変更することで、リンクが見出し要素の&lt;strong&gt;内部&lt;/strong&gt;（最初の子要素）に配置されるようになり、Flexboxが正しく機能するようになりました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ これだと見出しの外側に配置される&lt;/span&gt;
&lt;span class="nx"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;before&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ これで見出しの内部に配置される&lt;/span&gt;
&lt;span class="nx"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prepend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  問題2: ハイドレーションエラーが発生する
&lt;/h3&gt;

&lt;p&gt;リンクアイコンをクリックすると、以下のようなエラーが発生していました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;原因を調べたところ、クライアント側で見出しのIDを上書きする&lt;code&gt;AssignHeadingIds&lt;/code&gt;コンポーネントが原因でした。&lt;code&gt;rehypeSlug&lt;/code&gt;がサーバー側で生成したIDと、クライアント側で変更されたIDが一致しなかったのです。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;解決方法&lt;/strong&gt;: &lt;code&gt;AssignHeadingIds&lt;/code&gt;コンポーネントを削除し、&lt;code&gt;TableOfContents&lt;/code&gt;コンポーネントを修正して、DOMから実際の見出しIDを取得するようにしました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  // DOMから実際の見出し要素を取得してIDを取得（rehypeSlugが生成したIDを使用）
  const headings = Array.from(document.querySelectorAll('.article-content h2, .article-content h3')) as HTMLElement[]
  if (headings.length &amp;gt; 0) {
    const tocItems: TOCItem[] = headings.map((heading) =&amp;gt; {
      const level = heading.tagName === 'H2' ? 2 : 3
      const text = heading.textContent?.trim() || ''
      const id = heading.id || '' // rehypeSlugが生成したIDをそのまま使用
      return { id, text, level }
    }).filter(item =&amp;gt; item.id)
    setToc(tocItems)
  }
}, [content])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで、サーバー側とクライアント側で同じIDが使用されるため、ハイドレーションエラーが解消されました。&lt;/p&gt;

&lt;h2&gt;
  
  
  実装のポイント
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;behavior: 'prepend'&lt;/code&gt;を使う&lt;/strong&gt;: 見出しの内部にリンクを配置することで、Flexboxが正しく機能します。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexboxでレイアウト&lt;/strong&gt;: 見出しを&lt;code&gt;display: flex&lt;/code&gt;にして、アイコンとテキストを同じ行に配置します。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ホバーで表示&lt;/strong&gt;: &lt;code&gt;opacity: 0&lt;/code&gt;で通常は非表示にし、ホバー時に&lt;code&gt;opacity: 1&lt;/code&gt;で表示します。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDの一貫性&lt;/strong&gt;: サーバー側とクライアント側で同じIDを使用することで、ハイドレーションエラーを防ぎます。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;Qiita風の見出しアンカーリンクを実装するのは、思ったより簡単でした。&lt;code&gt;rehype-autolink-headings&lt;/code&gt;の&lt;code&gt;behavior&lt;/code&gt;オプションを適切に設定し、Flexboxでレイアウトすることで、綺麗に動作するようになりました。&lt;/p&gt;

&lt;p&gt;ハイドレーションエラーには少し悩まされましたが、サーバー側とクライアント側で同じIDを使用することで解決できました。同じような問題に遭遇した方の参考になれば幸いです。&lt;/p&gt;

&lt;p&gt;もし、さらにカスタマイズしたい場合は、SVGアイコンのデザインを変更したり、アニメーションを追加したりすることもできます。ぜひ試してみてください！&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>markdown</category>
      <category>rehype</category>
    </item>
    <item>
      <title>Next.js + Resend APIでお問い合わせフォームを実装した話</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Fri, 14 Nov 2025 01:20:53 +0000</pubDate>
      <link>https://forem.com/beachone1155/nextjs-resend-apideowen-ihe-wasehuomuwoshi-zhuang-sitahua-1mkm</link>
      <guid>https://forem.com/beachone1155/nextjs-resend-apideowen-ihe-wasehuomuwoshi-zhuang-sitahua-1mkm</guid>
      <description>&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;この記事では、Next.js（App Router）とResend APIを使用して、お問い合わせフォームを実装した過程をまとめます。フロントエンドのバリデーションから、バックエンドのメール送信、XSS対策、エラーハンドリングまで、実装の詳細を解説します。&lt;/p&gt;

&lt;h2&gt;
  
  
  実装の背景と目的
&lt;/h2&gt;

&lt;p&gt;技術ブログに読者からのお問い合わせを受け付ける機能を追加したいと考えました。要件は以下の通りです：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;名前、メールアドレス、メッセージを送信&lt;/li&gt;
&lt;li&gt;メールで通知を受け取る&lt;/li&gt;
&lt;li&gt;フロントエンドでバリデーション&lt;/li&gt;
&lt;li&gt;XSS対策を実装&lt;/li&gt;
&lt;li&gt;エラーハンドリングを適切に行う&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resend APIの選択理由
&lt;/h2&gt;

&lt;p&gt;メール送信サービスとしてResendを選択した理由：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;無料プランが充実&lt;/strong&gt;: 月3,000通まで送信可能&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;シンプルなAPI&lt;/strong&gt;: 実装が簡単&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next.jsとの相性&lt;/strong&gt;: TypeScript対応、ドキュメントが充実&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;開発者フレンドリー&lt;/strong&gt;: 無料プランでも&lt;code&gt;onboarding@resend.dev&lt;/code&gt;から送信可能&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Resend APIの設定
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. アカウント作成とAPIキー取得
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://resend.com/" rel="noopener noreferrer"&gt;Resend&lt;/a&gt; にアクセスしてアカウントを作成&lt;/li&gt;
&lt;li&gt;Dashboard → API Keys → Create API Key&lt;/li&gt;
&lt;li&gt;表示されたAPIキーをコピー（一度しか表示されないため注意）&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. 環境変数の設定
&lt;/h3&gt;

&lt;h4&gt;
  
  
  ローカル開発環境（.env.local）
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RESEND_API_KEY=re_xxxxxxxxxxxxx
CONTACT_EMAIL=your-email@example.com
RESEND_FROM_EMAIL=onboarding@resend.dev  # オプション（未設定時はデフォルト値を使用）
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Vercel環境変数の設定
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Production環境&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your-resend-api-key"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add RESEND_API_KEY production
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add CONTACT_EMAIL production

&lt;span class="c"&gt;# Preview環境&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your-resend-api-key"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add RESEND_API_KEY preview
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add CONTACT_EMAIL preview

&lt;span class="c"&gt;# Development環境&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your-resend-api-key"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add RESEND_API_KEY development
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"your-email@example.com"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add CONTACT_EMAIL development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Resendパッケージのインストール
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;resend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  実装の詳細
&lt;/h2&gt;

&lt;h3&gt;
  
  
  フロントエンド（ContactForm.tsx）
&lt;/h3&gt;

&lt;h4&gt;
  
  
  コンポーネントの構造
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardDescription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardTitle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Loader2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ContactForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFormData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;isSubmitting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setIsSubmitting&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;submitStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSubmitStatus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;emailError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setEmailError&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// メールアドレスのバリデーション（APIと同じロジック）&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValidEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleEmailChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLInputElement&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
    &lt;span class="nf"&gt;setFormData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// リアルタイムバリデーション（入力中はエラーをクリア）&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setEmailError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// メールアドレスのバリデーション&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isValidEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setEmailError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;正しいメールアドレスを入力してください（例: example@email.com）&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;setEmailError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setIsSubmitting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;setSubmitStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;idle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/contact&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setSubmitStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;setFormData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;setEmailError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({}))&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid email address&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;setEmailError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;正しいメールアドレスを入力してください（例: example@email.com）&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;setSubmitStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to submit contact form:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;setSubmitStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;setIsSubmitting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardTitle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;お問い合わせ&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardTitle&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardDescription&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nx"&gt;ご質問やご要望がございましたら&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;&lt;span class="nx"&gt;お気軽にお問い合わせください&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardDescription&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardHeader&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardContent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-y-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* フォームフィールド */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardContent&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  メールアドレスバリデーション
&lt;/h4&gt;

&lt;p&gt;フロントエンドでメールアドレスのバリデーションを実装し、ユーザーに即座にフィードバックを提供します：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// メールアドレスのバリデーション（APIと同じロジック）&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isValidEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// エラーメッセージの表示&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emailError&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mt-1 text-sm text-red-600&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emailError&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  バックエンド（/api/contact）
&lt;/h3&gt;

&lt;h4&gt;
  
  
  API Routeの実装
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Resend&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resend&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;

    &lt;span class="c1"&gt;// バリデーション&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All fields are required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isValidEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid email address&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Resend APIキーの確認&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resendApiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESEND_API_KEY&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contactEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CONTACT_EMAIL&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;resendApiKey&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;contactEmail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// 開発環境ではログ出力のみ、本番環境ではエラーを返す&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email service is not configured&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact form submission (dev mode):&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact form submitted successfully (dev mode - email not sent)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// メール送信&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Resend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resendApiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// HTMLエスケープ（XSS対策）&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapeHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/"/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/'/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;#039;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapedName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapedEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapedMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// 送信元メールアドレス（環境変数で設定可能）&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fromEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RESEND_FROM_EMAIL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;onboarding@resend.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fromEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contactEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;replyTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 返信先を送信者のメールアドレスに設定&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`お問い合わせ: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escapedName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
        &amp;lt;h2&amp;gt;お問い合わせ内容&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;名前:&amp;lt;/strong&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escapedName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;メール:&amp;lt;/strong&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escapedEmail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;メッセージ:&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;escapedMessage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;
        &amp;lt;hr&amp;gt;
        &amp;lt;p style="color: #666; font-size: 12px;"&amp;gt;
          このメールは &amp;lt;a href="https://beachone1155.vercel.app/contact"&amp;gt;beachone1155 Engineer Blog&amp;lt;/a&amp;gt; のお問い合わせフォームから送信されました。
        &amp;lt;/p&amp;gt;
      `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Resend API error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to send email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact form email sent successfully:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;emailResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact form submitted successfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Contact form error:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.[^\s&lt;/span&gt;&lt;span class="sr"&gt;@&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  XSS対策（HTMLエスケープ）
&lt;/h2&gt;

&lt;p&gt;ユーザー入力のHTMLタグをエスケープして、XSS攻撃を防ぎます：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapeHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;amp;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;amp;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;lt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&amp;gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/"/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;quot;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/'/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;#039;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 使用例&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapedName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;escapedMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;escapeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これにより、&lt;code&gt;&amp;lt;script&amp;gt;alert('XSS')&amp;lt;/script&amp;gt;&lt;/code&gt;のような入力も安全に処理されます。&lt;/p&gt;

&lt;h2&gt;
  
  
  エラーハンドリング
&lt;/h2&gt;

&lt;h3&gt;
  
  
  フロントエンド
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 送信成功時&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-3 bg-green-50 border border-green-200 rounded-md text-green-800 text-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;お問い合わせありがとうございます&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="nx"&gt;正常に送信されました&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

&lt;span class="c1"&gt;// 送信失敗時&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitStatus&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p-3 bg-red-50 border border-red-200 rounded-md text-red-800 text-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;送信に失敗しました&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="nx"&gt;しばらく時間をおいて再度お試しください&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;
  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  バックエンド
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;環境変数未設定時の処理（開発環境ではログ出力のみ）&lt;/li&gt;
&lt;li&gt;Resend APIエラー時の処理&lt;/li&gt;
&lt;li&gt;ネットワークエラー時の処理&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  テスト項目と確認事項
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 基本動作の確認
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ フォーム送信が正常に動作するか&lt;/li&gt;
&lt;li&gt;✅ メールが正しく送信されるか&lt;/li&gt;
&lt;li&gt;✅ 送信成功メッセージが表示されるか&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. バリデーションの確認
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ 空のフィールドで送信 → エラー表示&lt;/li&gt;
&lt;li&gt;✅ 不正なメールアドレス（&lt;code&gt;a@a&lt;/code&gt;など） → エラー表示&lt;/li&gt;
&lt;li&gt;✅ 正しいメールアドレス → 送信成功&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. XSS対策の確認
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;タグを含む入力 → エスケープされる&lt;/li&gt;
&lt;li&gt;✅ HTMLタグを含む入力 → エスケープされる&lt;/li&gt;
&lt;li&gt;✅ 特殊文字（&lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;"&lt;/code&gt;, &lt;code&gt;'&lt;/code&gt;） → エスケープされる&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. エラーハンドリングの確認
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ ネットワークエラー時の処理&lt;/li&gt;
&lt;li&gt;✅ Resend APIエラー時の処理&lt;/li&gt;
&lt;li&gt;✅ 環境変数未設定時の処理&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. 本番環境での確認
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Vercel環境変数が正しく設定されているか&lt;/li&gt;
&lt;li&gt;✅ 本番環境でメール送信が正常に動作するか&lt;/li&gt;
&lt;li&gt;✅ エラーログが適切に記録されるか&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  今後の改善点
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 独自ドメインの設定
&lt;/h3&gt;

&lt;p&gt;現在は&lt;code&gt;onboarding@resend.dev&lt;/code&gt;を使用していますが、独自ドメインを設定することで：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;送信元の信頼性が向上&lt;/li&gt;
&lt;li&gt;ブランドの一貫性が保たれる&lt;/li&gt;
&lt;li&gt;メールの到達率が向上する可能性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;設定方法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resend Dashboardでドメインを追加&lt;/li&gt;
&lt;li&gt;DNSレコードを設定（SPF、DKIM、CNAME）&lt;/li&gt;
&lt;li&gt;環境変数&lt;code&gt;RESEND_FROM_EMAIL&lt;/code&gt;に設定（例: &lt;code&gt;contact@yourdomain.com&lt;/code&gt;）&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. レート制限の実装
&lt;/h3&gt;

&lt;p&gt;スパム対策として、レート制限を実装することを検討：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// IPアドレスベースのレート制限&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rateLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkRateLimit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeDiff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;lastRequest&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeDiff&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 1分以内&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. reCAPTCHAの追加
&lt;/h3&gt;

&lt;p&gt;ボット対策として、Google reCAPTCHAを追加することも検討できます。&lt;/p&gt;

&lt;h3&gt;
  
  
  4. メールテンプレートの改善
&lt;/h3&gt;

&lt;p&gt;現在のHTMLメールを、より洗練されたテンプレートに改善することも可能です。&lt;/p&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;Next.jsとResend APIを使用して、お問い合わせフォームを実装しました。主なポイントは以下の通りです：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;フロントエンド&lt;/strong&gt;: リアルタイムバリデーション、エラーメッセージ表示&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;バックエンド&lt;/strong&gt;: Resend APIを使用したメール送信、XSS対策&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;エラーハンドリング&lt;/strong&gt;: 適切なエラーメッセージとログ記録&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;セキュリティ&lt;/strong&gt;: HTMLエスケープによるXSS対策&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;無料プランでも十分に使える実装となっており、個人ブログや小規模なサイトに適しています。同様の機能を実装する際の参考になれば幸いです。&lt;/p&gt;




&lt;h2&gt;
  
  
  参考リンク
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://resend.com/docs" rel="noopener noreferrer"&gt;Resend Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" rel="noopener noreferrer"&gt;Next.js API Routes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://resend.com/docs/api-reference/emails/send-email" rel="noopener noreferrer"&gt;Resend API Reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nextjs</category>
      <category>resend</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Google AdSense「有用性の低いコンテンツ」審査対応の記録</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Fri, 14 Nov 2025 01:20:50 +0000</pubDate>
      <link>https://forem.com/beachone1155/google-adsenseyou-yong-xing-nodi-ikontentu-shen-cha-dui-ying-noji-lu-512k</link>
      <guid>https://forem.com/beachone1155/google-adsenseyou-yong-xing-nodi-ikontentu-shen-cha-dui-ying-noji-lu-512k</guid>
      <description>&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;この記事では、Google AdSenseの審査で「有用性の低いコンテンツ」という理由で不合格になった際の対応記録をまとめます。技術ブログを運営している方で、同様の問題に直面している方の参考になれば幸いです。&lt;/p&gt;

&lt;h2&gt;
  
  
  問題の発覚
&lt;/h2&gt;

&lt;p&gt;Google AdSenseの審査を申請したところ、以下の理由で不合格になりました：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;有用性の低いコンテンツ&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
お客様のサイトは、弊社の定めるサイト運営者ネットワークのご利用要件を満たしていないと判断されました。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  審査時のサイト状況
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公開記事数: 12件&lt;/li&gt;
&lt;li&gt;一部の記事が短い（360語、247語、101語など）&lt;/li&gt;
&lt;li&gt;Aboutページが存在しない&lt;/li&gt;
&lt;li&gt;サイトの目的や価値提案が不明確&lt;/li&gt;
&lt;li&gt;ナビゲーションが不十分&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  実施した対応
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Aboutページの追加
&lt;/h3&gt;

&lt;p&gt;サイトの目的、著者情報、価値提案を明確にするため、Aboutページを追加しました。&lt;/p&gt;

&lt;h4&gt;
  
  
  実装内容
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;src/app/about/page.tsx&lt;/code&gt; を作成し、以下の情報を記載：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;著者情報&lt;/strong&gt;: 経歴、スキル、専門分野&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;サイトの目的&lt;/strong&gt;: 技術ブログとしての役割と価値&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;サイトの特徴&lt;/strong&gt;: マルチプラットフォーム自動投稿、実践的なコンテンツなど&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;お問い合わせ&lt;/strong&gt;: 連絡先情報&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  効果
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;サイトの目的が明確になり、読者への価値提案が伝わりやすくなった&lt;/li&gt;
&lt;li&gt;著者情報の公開により、サイトの信頼性が向上&lt;/li&gt;
&lt;li&gt;Googleのクローラーがサイトの内容を理解しやすくなった&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. ナビゲーションの改善
&lt;/h3&gt;

&lt;p&gt;ユーザーがサイトを理解しやすくするため、ナビゲーションを改善しました。&lt;/p&gt;

&lt;h4&gt;
  
  
  ヘッダーの改善
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Aboutページへのリンクを追加&lt;/li&gt;
&lt;li&gt;主要ページへのアクセスを容易に&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  フッターの改善
&lt;/h4&gt;

&lt;p&gt;フッターに主要ページへのリンクを追加し、サイトマップ的な役割を持たせました：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ホーム&lt;/li&gt;
&lt;li&gt;ブログ&lt;/li&gt;
&lt;li&gt;このサイトについて&lt;/li&gt;
&lt;li&gt;タグ一覧&lt;/li&gt;
&lt;li&gt;ポートフォリオ&lt;/li&gt;
&lt;li&gt;お問い合わせ&lt;/li&gt;
&lt;li&gt;プライバシーポリシー&lt;/li&gt;
&lt;li&gt;免責事項&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  効果
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;ユーザーがサイト全体を把握しやすくなった&lt;/li&gt;
&lt;li&gt;クローラーがサイト構造を理解しやすくなった&lt;/li&gt;
&lt;li&gt;ユーザーエクスペリエンスが向上&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. コンテンツの充実
&lt;/h3&gt;

&lt;p&gt;短い記事の内容を拡充し、最低500語以上になるように調整しました。&lt;/p&gt;

&lt;h4&gt;
  
  
  対象記事
&lt;/h4&gt;

&lt;p&gt;以下の記事を拡充：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vercel-neon-branch-limit.md&lt;/code&gt; (360語 → 拡充)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;platform-hosted-images-automation.md&lt;/code&gt; (247語 → 拡充)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hatena-automation.md&lt;/code&gt; (101語 → 拡充)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  拡充内容
&lt;/h4&gt;

&lt;p&gt;各記事に以下を追加：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;実装の詳細な説明&lt;/strong&gt;: コード例と実装方法&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;エラーハンドリング&lt;/strong&gt;: エラー時の対処法&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;トラブルシューティング&lt;/strong&gt;: よくある問題と解決方法&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;参考情報&lt;/strong&gt;: 関連リンクとドキュメント&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  効果
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;記事の有用性が向上&lt;/li&gt;
&lt;li&gt;読者にとってより価値のあるコンテンツに&lt;/li&gt;
&lt;li&gt;検索エンジンでの評価が向上する可能性&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. メタ情報の改善
&lt;/h3&gt;

&lt;p&gt;SEOとサイトの信頼性向上のため、メタ情報を充実させました。&lt;/p&gt;

&lt;h4&gt;
  
  
  改善内容
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;src/app/layout.tsx&lt;/code&gt; のメタ情報を充実：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;description&lt;/strong&gt;: より詳細な説明に変更&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;keywords&lt;/strong&gt;: 技術スタック、プラットフォーム名などを追加&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open Graph&lt;/strong&gt;: タイトル、説明、画像を充実&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Twitter Card&lt;/strong&gt;: カード情報を充実&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;metadataBase&lt;/strong&gt;: ベースURLを設定&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  改善前
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;エンジニアの技術ブログ - 自動化、開発、学習記録&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;エンジニア&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;技術ブログ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;プログラミング&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;自動化&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;開発&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  改善後
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;エンジニアの技術ブログ。Next.js、TypeScript、Pythonを使用した実践的な開発ノウハウ、GitHub Actionsによる自動化、マルチプラットフォーム投稿システムの構築方法などを発信しています。&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="nx"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;エンジニア&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;技術ブログ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;プログラミング&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;自動化&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;開発&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Next.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TypeScript&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Python&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;FastAPI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PostgreSQL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GitHub Actions&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Vercel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Qiita&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Zenn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DEV.to&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;はてなブログ&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;マルチ投稿&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CI/CD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;フルスタック開発&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  効果
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;検索エンジンがサイトの内容を理解しやすくなった&lt;/li&gt;
&lt;li&gt;SNSでのシェア時に適切な情報が表示されるようになった&lt;/li&gt;
&lt;li&gt;サイトの専門性が明確になった&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Google AdSenseの要件
&lt;/h2&gt;

&lt;h3&gt;
  
  
  最小コンテンツ要件
&lt;/h3&gt;

&lt;p&gt;Google AdSenseでは、以下の要件を満たす必要があります：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;独自性のある質の高いコンテンツ&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;オリジナルのコンテンツであること&lt;/li&gt;
&lt;li&gt;読者にとって価値のある情報であること&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;十分なコンテンツ量&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;各ページに十分なコンテンツがあること&lt;/li&gt;
&lt;li&gt;短すぎる記事は避ける（最低500語程度を推奨）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;明確なサイト構造&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ナビゲーションが明確であること&lt;/li&gt;
&lt;li&gt;サイトの目的が明確であること&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;著者情報の公開&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;サイトの運営者情報が明確であること&lt;/li&gt;
&lt;li&gt;Aboutページなどで情報を公開すること&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  参考リンク
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.google.com/adsense/answer/10502938" rel="noopener noreferrer"&gt;AdSense 最小コンテンツ要件&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/adsense/answer/10015918" rel="noopener noreferrer"&gt;独自性のある質の高いコンテンツ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/webmasters/answer/9044175#thin-content" rel="noopener noreferrer"&gt;質の低いコンテンツ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/publisherpolicies/answer/11035931" rel="noopener noreferrer"&gt;Google Publisher Policies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  実装の詳細
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Aboutページの実装
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/about/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/Container&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Breadcrumb&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/Breadcrumb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// ... その他のインポート&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AboutPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;py-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Breadcrumb&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{[{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;このサイトについて&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-w-4xl mx-auto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* サイトの目的 */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mb-12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-4xl font-bold mb-6&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;このサイトについて&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-lg text-muted-foreground mb-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;beachone1155&lt;/span&gt; &lt;span class="nx"&gt;Engineer&lt;/span&gt; &lt;span class="nx"&gt;Blogは&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;&lt;span class="nx"&gt;エンジニアの技術ブログとして&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;
            &lt;span class="nx"&gt;実践的な開発ノウハウ&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;&lt;span class="nx"&gt;自動化の知見&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;&lt;span class="nx"&gt;学習記録を発信しています&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 著者情報 */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mb-12&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardHeader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardTitle&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;著者について&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardTitle&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardHeader&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CardContent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* 著者情報の内容 */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/CardContent&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Card&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Container&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  フッターの改善
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/Footer.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Footer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;footer&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;border-t border-gray-200 bg-gray-50 dark:bg-gray-900 mt-16&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max-w-5xl mx-auto px-4 py-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;grid grid-cols-1 md:grid-cols-3 gap-8 mb-8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* サイト情報 */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-semibold text-foreground mb-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;beachone1155&lt;/span&gt; &lt;span class="nx"&gt;Engineer&lt;/span&gt; &lt;span class="nx"&gt;Blog&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text-sm text-muted-foreground&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nx"&gt;エンジニアの技術ブログ&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;&lt;span class="nx"&gt;自動化&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;&lt;span class="nx"&gt;開発&lt;/span&gt;&lt;span class="err"&gt;、&lt;/span&gt;&lt;span class="nx"&gt;学習記録を発信しています&lt;/span&gt;&lt;span class="err"&gt;。&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* ナビゲーション */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;font-semibold text-foreground mb-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;ナビゲーション&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ul&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-y-2 text-sm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;ホーム&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/blog&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;ブログ&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;このサイトについて&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Link&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;li&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* その他のリンク */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ul&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/footer&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  結果と今後の対応
&lt;/h2&gt;

&lt;h3&gt;
  
  
  実施後の状態
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Aboutページを追加&lt;/li&gt;
&lt;li&gt;✅ ナビゲーションを改善&lt;/li&gt;
&lt;li&gt;✅ 短い記事を拡充（最低500語以上）&lt;/li&gt;
&lt;li&gt;✅ メタ情報を充実&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  次のステップ
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AdSenseの再審査を申請&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;変更をデプロイ後、AdSenseの審査を再申請&lt;/li&gt;
&lt;li&gt;審査結果を確認&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;コンテンツの継続的な改善&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;定期的に記事を追加&lt;/li&gt;
&lt;li&gt;既存記事の内容を充実&lt;/li&gt;
&lt;li&gt;読者のフィードバックを反映&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SEOの最適化&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;構造化データの追加を検討&lt;/li&gt;
&lt;li&gt;サイトマップの最適化&lt;/li&gt;
&lt;li&gt;パフォーマンスの改善&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;p&gt;Google AdSenseの「有用性の低いコンテンツ」問題に対応するため、以下の改善を実施しました：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Aboutページの追加&lt;/strong&gt;: サイトの目的と価値提案を明確化&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ナビゲーションの改善&lt;/strong&gt;: ユーザーがサイトを理解しやすく&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;コンテンツの充実&lt;/strong&gt;: 短い記事を拡充して有用性を向上&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;メタ情報の改善&lt;/strong&gt;: SEOとサイトの信頼性を向上&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;これらの対応により、サイトの有用性が向上し、AdSenseの審査要件を満たすことができました。同様の問題に直面している方は、参考にしていただければ幸いです。&lt;/p&gt;




&lt;h2&gt;
  
  
  参考リンク
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://support.google.com/adsense" rel="noopener noreferrer"&gt;Google AdSense Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/publisherpolicies/answer/11035931" rel="noopener noreferrer"&gt;Google Publisher Policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/webmasters/answer/35769" rel="noopener noreferrer"&gt;Webmaster Quality Guidelines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>adsense</category>
      <category>seo</category>
      <category>webdev</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>はてなブログへの自動投稿に対応した（GitHub Actions + AtomPub API）</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Thu, 06 Nov 2025 02:53:34 +0000</pubDate>
      <link>https://forem.com/beachone1155/hatenaburoguhenozi-dong-tou-gao-nidui-ying-sitagithub-actions-atompub-api-3ji4</link>
      <guid>https://forem.com/beachone1155/hatenaburoguhenozi-dong-tou-gao-nidui-ying-sitagithub-actions-atompub-api-3ji4</guid>
      <description>&lt;h2&gt;
  
  
  追加内容の概要
&lt;/h2&gt;

&lt;p&gt;本ブログの自動配信パイプライン（Qiita/Dev.to/Zenn/X）に、はてなブログへの自動投稿機能を追加しました。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;はてなブログ: AtomPub API（WSSE認証）で投稿&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Frontmatter の &lt;code&gt;publish_on&lt;/code&gt; に &lt;code&gt;hatena&lt;/code&gt; を書くだけで配信対象になります。&lt;/p&gt;

&lt;h2&gt;
  
  
  実装ポイント
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scripts/publish.mjs&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;publishToHatena(post)&lt;/code&gt; を追加（AtomEntry XML + X-WSSE認証）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;main()&lt;/code&gt; に &lt;code&gt;hatena&lt;/code&gt; を統合（X以外は並列処理）&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;.github/workflows/publish.yml&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Hatena ステップを追加&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;バリデーション/型に &lt;code&gt;hatena&lt;/code&gt; を追加&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  使い方
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/content/YYYY/MM/slug.md&lt;/code&gt; の Frontmatter で配信先を指定します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;publish_on: ["hatena"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub に push すると、Actions が記事を配信します。ローカルでは &lt;code&gt;PUBLISH_TARGETS=hatena node scripts/publish.mjs&lt;/code&gt; のように個別実行も可能です。&lt;/p&gt;

&lt;h2&gt;
  
  
  補足
&lt;/h2&gt;

&lt;p&gt;note への自動投稿も検討しましたが、公式APIが存在せず、非公式APIも投稿機能が不明確なため、今回は実装を見送りました。はてなブログは公式のAtomPub APIが提供されているため、安定して自動投稿できます。&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>automation</category>
      <category>hatena</category>
    </item>
    <item>
      <title>R2E APIをAWSに移行するプラン：Render + Vercel + Neon から AWS への移行戦略</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Tue, 04 Nov 2025 08:23:15 +0000</pubDate>
      <link>https://forem.com/beachone1155/r2e-apiwoawsniyi-xing-surupuranrender-vercel-neon-kara-aws-henoyi-xing-zhan-lue-4h5j</link>
      <guid>https://forem.com/beachone1155/r2e-apiwoawsniyi-xing-surupuranrender-vercel-neon-kara-aws-henoyi-xing-zhan-lue-4h5j</guid>
      <description>&lt;h2&gt;
  
  
  はじめに
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//r2e-aws-migration-plan.md"&gt;以前の記事&lt;/a&gt;でR2E（Research→Experience）API を Render（FastAPI）+ Vercel（Next.js）+ Neon（PostgreSQL/pgvector）の構成で公開していました。しかし無料枠の制約やコールドスタートで初回 30〜60 秒かかる体験は、プロダクション運用には耐えません。そこで、私は本番運用を見据えて、同機能を &lt;strong&gt;AWS へ段階的に移行する&lt;/strong&gt; 計画を立てました。本稿は、私がこの移行で採用する構成、具体的な手順、運用の要点をまとめたプランです。&lt;/p&gt;

&lt;h2&gt;
  
  
  現在の構成の整理
&lt;/h2&gt;

&lt;p&gt;現在の構成は以下の通りです：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwm48l53qtsdwul7o8ywc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwm48l53qtsdwul7o8ywc.png" alt="R2E API アーキテクチャ構成図" width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  フロントエンド
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js on Vercel&lt;/strong&gt;: ブログサイトと &lt;code&gt;/portfolio/r2e&lt;/code&gt; のデモUI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Route&lt;/strong&gt;: &lt;code&gt;/api/r2e/[...path]&lt;/code&gt; でバックエンドにプロキシ&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  バックエンド
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI on Render&lt;/strong&gt;: Docker コンテナでデプロイ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;https://research-to-experience-api.onrender.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;コールドスタート&lt;/strong&gt;: 15分間アクセスがないとスリープ、初回起動に30〜60秒&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  データベース
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Neon PostgreSQL&lt;/strong&gt;: クラウド PostgreSQL（&lt;code&gt;pgvector&lt;/code&gt; 拡張対応）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ベクトル次元&lt;/strong&gt;: 1536（OpenAI &lt;code&gt;text-embedding-3-small&lt;/code&gt; 対応）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  課題
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Render 無料枠のコールドスタート問題（30〜60秒の待機）&lt;/li&gt;
&lt;li&gt;スケーラビリティの制限&lt;/li&gt;
&lt;li&gt;監視・ログ機能の不足&lt;/li&gt;
&lt;li&gt;データベースとアプリケーションの分離（Neon は別サービス）&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  AWS移行の目的とメリット
&lt;/h2&gt;

&lt;h3&gt;
  
  
  メリット
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;コールドスタートの解消&lt;/strong&gt;: ECS Fargate は常時起動可能（コストとトレードオフ）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;スケーラビリティ&lt;/strong&gt;: オートスケーリング対応&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;統合監視&lt;/strong&gt;: CloudWatch でログ・メトリクスを一元管理&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;セキュリティ&lt;/strong&gt;: VPC 内でリソースを分離、Secrets Manager で機密情報管理&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;高可用性&lt;/strong&gt;: マルチAZ対応、自動バックアップ&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  デメリット・考慮事項
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;コスト&lt;/strong&gt;: 無料枠から月額約 $80-120 へ（小規模運用）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;運用複雑度&lt;/strong&gt;: インフラ管理の知識が必要&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;セットアップ工数&lt;/strong&gt;: 初期構築に時間がかかる&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  私が採用するAWS構成
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysos7lkz1ah0qe83vsk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysos7lkz1ah0qe83vsk8.png" alt="R2E API アーキテクチャ構成図(AWS版)" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  フロントエンド
&lt;/h3&gt;

&lt;p&gt;フロントエンドは &lt;strong&gt;AWS Amplify&lt;/strong&gt; を第一候補として採用します。Next.js の SSR/SSG をサポートし、GitHub 連携で CI/CD と環境変数管理が一か所にまとまります。もし SSR が不要なページのみを分離できる場合は、コスト最適化のために &lt;strong&gt;S3 静的ホスティング + CloudFront&lt;/strong&gt; に切り替えるオプションも用意します（本計画では Amplify を本線、S3 + CloudFront を代替案として維持）。&lt;/p&gt;

&lt;h3&gt;
  
  
  バックエンド
&lt;/h3&gt;

&lt;p&gt;アプリケーション層は &lt;strong&gt;ECS Fargate + ALB&lt;/strong&gt; に移行します。既存の Dockerfile を無変更で使え、常時起動でコールドスタートを排除できます。スケーリングは CPU/メモリのターゲット追跡で運用し、最初は 1 タスク常時起動、負荷に応じて最大 5 までの自動拡張を設定します。将来的に構成簡素化が必要になれば App Runner への置き換え、超低トラフィック帯では Lambda 実行（ただし本APIの性質上は非本命）も検討余地として残します。&lt;/p&gt;

&lt;h3&gt;
  
  
  データベース
&lt;/h3&gt;

&lt;p&gt;データ層は &lt;strong&gt;RDS for PostgreSQL 15+（pgvector有効）&lt;/strong&gt; を採用します。自動バックアップとマルチAZで可用性を確保しつつ、Neon から &lt;code&gt;pg_dump/pg_restore&lt;/code&gt; で段階移行します。Aurora Serverless v2 は将来のコスト最適化候補として調査継続に留めます。&lt;/p&gt;

&lt;h3&gt;
  
  
  その他のAWSサービス
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt;（パブリック/プライベート分離）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ALB&lt;/strong&gt;（HTTPS終端 + 負荷分散）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR&lt;/strong&gt;（Dockerイメージ保管）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt;（ログ/メトリクス/アラーム）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Manager&lt;/strong&gt;（DATABASE_URL/OPENAI_API_KEY を安全管理）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway&lt;/strong&gt;（OpenAI/arXiv への外部アクセス）&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  AWS移行後のアーキテクチャ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  全体構成
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysos7lkz1ah0qe83vsk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fysos7lkz1ah0qe83vsk8.png" alt="R2E API AWS移行後のアーキテクチャ構成図" width="800" height="550"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  データフロー
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;クエリ受信&lt;/strong&gt;: Amplify（または CloudFront+S3）→ Next.js API Route → ALB → ECS Fargate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;論文取得&lt;/strong&gt;: ECS Fargate → NAT Gateway → arXiv&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;埋め込み生成&lt;/strong&gt;: ECS Fargate → NAT Gateway → OpenAI API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ベクトル検索&lt;/strong&gt;: ECS Fargate → RDS PostgreSQL (pgvector)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;要約生成&lt;/strong&gt;: ECS Fargate → NAT Gateway → OpenAI API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;レスポンス返却&lt;/strong&gt;: ECS Fargate → ALB → API Route → フロントエンド&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  移行手順（詳細）
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ステップ1: データベース移行（RDS作成）
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1-1. RDS for PostgreSQL インスタンス作成
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# AWS CLI での作成例（コンソールでも可）&lt;/span&gt;
aws rds create-db-instance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-identifier&lt;/span&gt; r2e-postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-instance-class&lt;/span&gt; db.t3.micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine-version&lt;/span&gt; 15.4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-username&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-user-password&lt;/span&gt; &amp;lt;PASSWORD&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allocated-storage&lt;/span&gt; 20 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage-type&lt;/span&gt; gp3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-security-group-ids&lt;/span&gt; sg-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--db-subnet-group-name&lt;/span&gt; r2e-db-subnet-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--backup-retention-period&lt;/span&gt; 7 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--multi-az&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--publicly-accessible&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;パラメータグループ設定&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;パラメータグループを作成（&lt;code&gt;r2e-pg-params&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shared_preload_libraries&lt;/code&gt; に &lt;code&gt;vector&lt;/code&gt; を追加&lt;/li&gt;
&lt;li&gt;インスタンスにパラメータグループを適用
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- パラメータグループ設定後、RDSに接続して拡張を有効化&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1-2. Neon から RDS へのデータ移行
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Neon からダンプ取得&lt;/span&gt;
pg_dump &lt;span class="nt"&gt;-h&lt;/span&gt; ep-xxx.region.neon.tech &lt;span class="nt"&gt;-U&lt;/span&gt; user &lt;span class="nt"&gt;-d&lt;/span&gt; dbname &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-owner&lt;/span&gt; &lt;span class="nt"&gt;--no-acl&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt; c &lt;span class="nt"&gt;-f&lt;/span&gt; r2e_backup.dump

&lt;span class="c"&gt;# 2. RDS にリストア&lt;/span&gt;
pg_restore &lt;span class="nt"&gt;-h&lt;/span&gt; r2e-postgres.xxx.rds.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-U&lt;/span&gt; postgres &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--no-owner&lt;/span&gt; &lt;span class="nt"&gt;--no-acl&lt;/span&gt; r2e_backup.dump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ダウンタイム最小化&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;メンテナンスウィンドウで実施&lt;/li&gt;
&lt;li&gt;または、レプリケーション設定（より複雑）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ステップ2: バックエンド移行（ECS Fargate）
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2-1. ECR に Docker イメージをプッシュ
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ECR リポジトリ作成&lt;/span&gt;
aws ecr create-repository &lt;span class="nt"&gt;--repository-name&lt;/span&gt; r2e-api

&lt;span class="c"&gt;# ログイン&lt;/span&gt;
aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; ap-northeast-1 | &lt;span class="se"&gt;\&lt;/span&gt;
  docker login &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &amp;lt;ACCOUNT_ID&amp;gt;.dkr.ecr.ap-northeast-1.amazonaws.com

&lt;span class="c"&gt;# イメージビルド&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; r2e-api &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# タグ付け・プッシュ&lt;/span&gt;
docker tag r2e-api:latest &amp;lt;ACCOUNT_ID&amp;gt;.dkr.ecr.ap-northeast-1.amazonaws.com/r2e-api:latest
docker push &amp;lt;ACCOUNT_ID&amp;gt;.dkr.ecr.ap-northeast-1.amazonaws.com/r2e-api:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2-2. ECS クラスター・タスク定義作成
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;タスク定義&lt;/strong&gt; (&lt;code&gt;task-definition.json&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"family"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"r2e-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"networkMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"requiresCompatibilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cpu"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"512"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"memory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1024"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"containerDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"r2e-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"image"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;ACCOUNT_ID&amp;gt;.dkr.ecr.ap-northeast-1.amazonaws.com/r2e-api:latest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"portMappings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"containerPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tcp"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"secrets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DATABASE_URL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"valueFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:ap-northeast-1:&amp;lt;ACCOUNT_ID&amp;gt;:secret:r2e/database-url"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPENAI_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"valueFrom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:ap-northeast-1:&amp;lt;ACCOUNT_ID&amp;gt;:secret:r2e/openai-api-key"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"logConfiguration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"logDriver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"awslogs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/ecs/r2e-api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ap-northeast-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"awslogs-stream-prefix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ecs"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# タスク定義登録&lt;/span&gt;
aws ecs register-task-definition &lt;span class="nt"&gt;--cli-input-json&lt;/span&gt; file://task-definition.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2-3. ALB 作成とターゲットグループ設定
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ALB 作成&lt;/span&gt;
aws elbv2 create-load-balancer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; r2e-api-alb &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnets&lt;/span&gt; subnet-xxx subnet-yyy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-groups&lt;/span&gt; sg-alb-xxx

&lt;span class="c"&gt;# ターゲットグループ作成&lt;/span&gt;
aws elbv2 create-target-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; r2e-api-tg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; HTTP &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-type&lt;/span&gt; ip &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--health-check-path&lt;/span&gt; /health

&lt;span class="c"&gt;# リスナー作成&lt;/span&gt;
aws elbv2 create-listener &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--load-balancer-arn&lt;/span&gt; &amp;lt;ALB_ARN&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--protocol&lt;/span&gt; HTTPS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--certificates&lt;/span&gt; &lt;span class="nv"&gt;CertificateArn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;CERT_ARN&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--default-actions&lt;/span&gt; &lt;span class="nv"&gt;Type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;forward,TargetGroupArn&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;TG_ARN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2-4. ECS サービス作成
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecs create-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt; r2e-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-name&lt;/span&gt; r2e-api-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--task-definition&lt;/span&gt; r2e-api &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--desired-count&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--launch-type&lt;/span&gt; FARGATE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network-configuration&lt;/span&gt; &lt;span class="s2"&gt;"awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=DISABLED}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--load-balancers&lt;/span&gt; &lt;span class="s2"&gt;"targetGroupArn=&amp;lt;TG_ARN&amp;gt;,containerName=r2e-api,containerPort=8000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ステップ3: フロントエンド（Amplify もしくは S3+CloudFront）
&lt;/h3&gt;

&lt;p&gt;本番運用では Amplify を使います。SSRが不要なページのみ切り出す場合は S3+CloudFront で静的ホスティングに置き換えます。&lt;/p&gt;

&lt;p&gt;Amplify の設定:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Vercel 環境変数更新&lt;/span&gt;
vercel &lt;span class="nb"&gt;env rm &lt;/span&gt;BACKEND_URL production
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"https://r2e-api-alb-xxx.ap-northeast-1.elb.amazonaws.com"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  vercel &lt;span class="nb"&gt;env &lt;/span&gt;add BACKEND_URL production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Amplify プロジェクト作成&lt;/span&gt;
amplify init

&lt;span class="c"&gt;# 環境変数設定（Console でも可）&lt;/span&gt;
amplify &lt;span class="nb"&gt;env &lt;/span&gt;add
&lt;span class="c"&gt;# BACKEND_URL=https://r2e-api-alb-xxx.ap-northeast-1.elb.amazonaws.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;S3+CloudFront 案（静的配信）:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# S3 バケット作成（静的ホスティング）&lt;/span&gt;
aws s3 mb s3://r2e-frontend-bucket
aws s3 website s3://r2e-frontend-bucket &lt;span class="nt"&gt;--index-document&lt;/span&gt; index.html &lt;span class="nt"&gt;--error-document&lt;/span&gt; 404.html

&lt;span class="c"&gt;# CloudFront ディストリビューション作成&lt;/span&gt;
aws cloudfront create-distribution &lt;span class="nt"&gt;--origin-domain-name&lt;/span&gt; r2e-frontend-bucket.s3.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ステップ4: ネットワーク設定（VPC）
&lt;/h3&gt;

&lt;h4&gt;
  
  
  4-1. VPC 作成（既存のVPCを使用する場合はスキップ）
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# VPC作成&lt;/span&gt;
aws ec2 create-vpc &lt;span class="nt"&gt;--cidr-block&lt;/span&gt; 10.0.0.0/16 &lt;span class="nt"&gt;--tag-specifications&lt;/span&gt; &lt;span class="s1"&gt;'ResourceType=vpc,Tags=[{Key=Name,Value=r2e-vpc}]'&lt;/span&gt;

&lt;span class="c"&gt;# パブリックサブネット作成&lt;/span&gt;
aws ec2 create-subnet &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cidr-block&lt;/span&gt; 10.0.1.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; ap-northeast-1a

&lt;span class="c"&gt;# プライベートサブネット作成&lt;/span&gt;
aws ec2 create-subnet &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cidr-block&lt;/span&gt; 10.0.2.0/24 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--availability-zone&lt;/span&gt; ap-northeast-1a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4-2. インターネットゲートウェイ・NAT Gateway 設定
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# インターネットゲートウェイ作成・アタッチ&lt;/span&gt;
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway &lt;span class="nt"&gt;--internet-gateway-id&lt;/span&gt; igw-xxx &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx

&lt;span class="c"&gt;# パブリックサブネットのルートテーブル設定&lt;/span&gt;
aws ec2 create-route &lt;span class="nt"&gt;--route-table-id&lt;/span&gt; rtb-xxx &lt;span class="nt"&gt;--destination-cidr-block&lt;/span&gt; 0.0.0.0/0 &lt;span class="nt"&gt;--gateway-id&lt;/span&gt; igw-xxx

&lt;span class="c"&gt;# NAT Gateway 作成（Elastic IP 必要）&lt;/span&gt;
aws ec2 allocate-address &lt;span class="nt"&gt;--domain&lt;/span&gt; vpc
aws ec2 create-nat-gateway &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subnet-id&lt;/span&gt; subnet-public-xxx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allocation-id&lt;/span&gt; eipalloc-xxx

&lt;span class="c"&gt;# プライベートサブネットのルートテーブル設定&lt;/span&gt;
aws ec2 create-route &lt;span class="nt"&gt;--route-table-id&lt;/span&gt; rtb-private-xxx &lt;span class="nt"&gt;--destination-cidr-block&lt;/span&gt; 0.0.0.0/0 &lt;span class="nt"&gt;--nat-gateway-id&lt;/span&gt; nat-xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4-3. セキュリティグループ設定
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ALB セキュリティグループ（HTTP/HTTPS 許可）&lt;/span&gt;
aws ec2 create-security-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-name&lt;/span&gt; r2e-alb-sg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"R2E API ALB Security Group"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx

&lt;span class="c"&gt;# ECS セキュリティグループ（ALBからのみ許可、NAT Gateway経由で外部アクセス）&lt;/span&gt;
aws ec2 create-security-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-name&lt;/span&gt; r2e-ecs-sg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"R2E API ECS Security Group"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx

&lt;span class="c"&gt;# RDS セキュリティグループ（ECSからのみ許可）&lt;/span&gt;
aws ec2 create-security-group &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--group-name&lt;/span&gt; r2e-rds-sg &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--description&lt;/span&gt; &lt;span class="s2"&gt;"R2E API RDS Security Group"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--vpc-id&lt;/span&gt; vpc-xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ステップ5: Secrets Manager 設定
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# データベース接続文字列&lt;/span&gt;
aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; r2e/database-url &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"postgresql://user:pass@r2e-postgres.xxx.rds.amazonaws.com:5432/dbname"&lt;/span&gt;

&lt;span class="c"&gt;# OpenAI API キー&lt;/span&gt;
aws secretsmanager create-secret &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; r2e/openai-api-key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--secret-string&lt;/span&gt; &lt;span class="s2"&gt;"sk-..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  コスト比較
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Render + Neon（現在の構成）
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Render&lt;/strong&gt;: 無料枠（制限あり）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Neon&lt;/strong&gt;: 無料枠（制限あり）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;合計&lt;/strong&gt;: $0/月（制限内）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS移行後（小規模運用）
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;サービス&lt;/th&gt;
&lt;th&gt;月額コスト目安&lt;/th&gt;
&lt;th&gt;備考&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RDS (db.t3.micro)&lt;/td&gt;
&lt;td&gt;$15-30&lt;/td&gt;
&lt;td&gt;無料枠なし&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS Fargate (0.5 vCPU, 1GB)&lt;/td&gt;
&lt;td&gt;$10-20&lt;/td&gt;
&lt;td&gt;常時起動&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALB&lt;/td&gt;
&lt;td&gt;$16&lt;/td&gt;
&lt;td&gt;固定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway&lt;/td&gt;
&lt;td&gt;$32&lt;/td&gt;
&lt;td&gt;固定&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch Logs&lt;/td&gt;
&lt;td&gt;$2-5&lt;/td&gt;
&lt;td&gt;使用量ベース&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;データ転送&lt;/td&gt;
&lt;td&gt;$1-5&lt;/td&gt;
&lt;td&gt;少量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;合計&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;約 $80-120/月&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  コスト最適化のヒント
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ECS Fargate のスケールダウン&lt;/strong&gt;: 夜間は desired-count を 0 に（ただしコールドスタート発生）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway の代替&lt;/strong&gt;: NAT Instance（EC2 t3.micro）で約 $7/月（運用コスト増）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RDS の停止&lt;/strong&gt;: 開発環境のみ、停止可能（ただし起動に時間がかかる）&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  ハマりポイントと対応
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. pgvector 拡張のインストール
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;: RDS のパラメータグループで &lt;code&gt;shared_preload_libraries&lt;/code&gt; を設定しても、拡張が有効にならない。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;対応&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;パラメータグループ作成時に &lt;code&gt;vector&lt;/code&gt; を追加&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;インスタンス再起動が必要&lt;/strong&gt;（パラメータ変更反映のため）&lt;/li&gt;
&lt;li&gt;接続後に &lt;code&gt;CREATE EXTENSION vector;&lt;/code&gt; を実行
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 確認&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;pg_extension&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'vector'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. VPC 内からの外部APIアクセス
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;: プライベートサブネットの ECS タスクから OpenAI API にアクセスできない。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;対応&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway 必須&lt;/strong&gt;: プライベートサブネットからインターネットへのルーティング&lt;/li&gt;
&lt;li&gt;セキュリティグループでアウトバウンド HTTPS (443) を許可&lt;/li&gt;
&lt;li&gt;コストが高いため、開発環境では NAT Instance を検討&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. ECS タスクのヘルスチェック失敗
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;: ALB のヘルスチェックが失敗し、タスクが起動しない。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;対応&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ヘルスチェックパス: &lt;code&gt;/health&lt;/code&gt; を返すエンドポイントを実装&lt;/li&gt;
&lt;li&gt;ヘルスチェック間隔: 30秒（デフォルト）&lt;/li&gt;
&lt;li&gt;タイムアウト: 5秒（デフォルト）&lt;/li&gt;
&lt;li&gt;成功閾値: 2回（デフォルト）
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# FastAPI のヘルスチェック実装例
&lt;/span&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Secrets Manager の IAM 権限不足
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;: ECS タスクが Secrets Manager から値を取得できない。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;対応&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECS タスク実行ロールに &lt;code&gt;SecretsManagerReadWrite&lt;/code&gt; ポリシーをアタッチ&lt;/li&gt;
&lt;li&gt;または、最小権限ポリシー:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"secretsmanager:GetSecretValue"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:secretsmanager:ap-northeast-1:&amp;lt;ACCOUNT_ID&amp;gt;:secret:r2e/*"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. RDS への接続タイムアウト
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;問題&lt;/strong&gt;: ECS タスクから RDS に接続できない。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;対応&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;セキュリティグループで RDS のポート（5432）を許可&lt;/li&gt;
&lt;li&gt;サブネットグループが正しく設定されているか確認&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;publicly-accessible&lt;/code&gt; は &lt;code&gt;false&lt;/code&gt; に設定（プライベートサブネット経由）&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  監視・運用
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CloudWatch Logs 設定
&lt;/h3&gt;

&lt;p&gt;ECS タスクのログは自動的に CloudWatch Logs に送信されます（タスク定義で設定済み）。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ロググループ確認&lt;/span&gt;
aws logs describe-log-groups &lt;span class="nt"&gt;--log-group-name-prefix&lt;/span&gt; /ecs/r2e-api

&lt;span class="c"&gt;# ログストリーム確認&lt;/span&gt;
aws logs describe-log-streams &lt;span class="nt"&gt;--log-group-name&lt;/span&gt; /ecs/r2e-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  CloudWatch Metrics 設定
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ECS メトリクス&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CPUUtilization&lt;/code&gt;: CPU 使用率&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MemoryUtilization&lt;/code&gt;: メモリ使用率&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RunningTaskCount&lt;/code&gt;: 実行中のタスク数&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;RDS メトリクス&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CPUUtilization&lt;/code&gt;: CPU 使用率&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DatabaseConnections&lt;/code&gt;: データベース接続数&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FreeableMemory&lt;/code&gt;: 利用可能メモリ&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  アラーム設定
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ECS CPU 使用率アラーム（80%超過）&lt;/span&gt;
aws cloudwatch put-metric-alarm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alarm-name&lt;/span&gt; r2e-ecs-high-cpu &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--alarm-description&lt;/span&gt; &lt;span class="s2"&gt;"ECS CPU utilization exceeds 80%"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--metric-name&lt;/span&gt; CPUUtilization &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; AWS/ECS &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--statistic&lt;/span&gt; Average &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--period&lt;/span&gt; 300 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--evaluation-periods&lt;/span&gt; 2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--threshold&lt;/span&gt; 80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--comparison-operator&lt;/span&gt; GreaterThanThreshold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  オートスケーリング設定
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ターゲット追跡スケーリングポリシー&lt;/span&gt;
aws application-autoscaling register-scalable-target &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-namespace&lt;/span&gt; ecs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scalable-dimension&lt;/span&gt; ecs:service:DesiredCount &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-id&lt;/span&gt; service/r2e-cluster/r2e-api-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min-capacity&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-capacity&lt;/span&gt; 5

aws application-autoscaling put-scaling-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--service-namespace&lt;/span&gt; ecs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scalable-dimension&lt;/span&gt; ecs:service:DesiredCount &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource-id&lt;/span&gt; service/r2e-cluster/r2e-api-service &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-name&lt;/span&gt; r2e-cpu-scaling &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-type&lt;/span&gt; TargetTrackingScaling &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target-tracking-scaling-policy-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{
    "TargetValue": 70.0,
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ECSServiceAverageCPUUtilization"
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  移行のメリット
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;コールドスタート解消&lt;/strong&gt;: 常時起動で即座に応答&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;スケーラビリティ&lt;/strong&gt;: オートスケーリングで負荷に対応&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;統合監視&lt;/strong&gt;: CloudWatch でログ・メトリクスを一元管理&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;セキュリティ強化&lt;/strong&gt;: VPC 内でリソース分離、Secrets Manager で機密情報管理&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;高可用性&lt;/strong&gt;: マルチAZ対応、自動バックアップ&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  コスト vs 機能のトレードオフ
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;無料枠（Render + Neon）&lt;/strong&gt;: 制限あり、コールドスタートあり&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS移行（$80-120/月）&lt;/strong&gt;: 制限なし、コールドスタートなし、高可用性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;小規模運用では Render + Neon でも十分ですが、本格運用やスケールアップを想定する場合は AWS 移行を検討すべきです。&lt;/p&gt;

&lt;h3&gt;
  
  
  段階的移行
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;フェーズ1&lt;/strong&gt;: RDS のみ移行（Neon → RDS）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;フェーズ2&lt;/strong&gt;: バックエンド移行（Render → ECS Fargate）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;フェーズ3&lt;/strong&gt;: フロントエンド移行（Vercel → Amplify、オプション）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;各フェーズで動作確認を行い、問題があればロールバック可能にすることが重要です。&lt;/p&gt;

&lt;h3&gt;
  
  
  次のステップ
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Terraform/CDK 化&lt;/strong&gt;: インフラをコード化して再現性を確保&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD パイプライン&lt;/strong&gt;: GitHub Actions で自動デプロイ&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;コスト最適化&lt;/strong&gt;: リザーブドインスタンス、Spot インスタンスの検討&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;マルチリージョン&lt;/strong&gt;: 高可用性のための複数リージョン展開&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AWS 移行は工数がかかりますが、長期的な運用を考えると投資価値があります。段階的に移行を進め、各ステップで動作確認を行います。&lt;/p&gt;

&lt;h2&gt;
  
  
  参考リンク
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/ecs/latest/userguide/what-is-fargate.html" rel="noopener noreferrer"&gt;AWS ECS Fargate ドキュメント&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html" rel="noopener noreferrer"&gt;RDS for PostgreSQL ドキュメント&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector on RDS 手順&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/secretsmanager/" rel="noopener noreferrer"&gt;AWS Secrets Manager ドキュメント&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-best-practices.html" rel="noopener noreferrer"&gt;VPC ネットワーキング ベストプラクティス&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>migration</category>
      <category>ecs</category>
      <category>rds</category>
    </item>
    <item>
      <title>Vercel統合Neonでブランチ制限に達した話と対処方法</title>
      <dc:creator>Hama</dc:creator>
      <pubDate>Mon, 03 Nov 2025 02:32:13 +0000</pubDate>
      <link>https://forem.com/beachone1155/verceltong-he-neondeburantizhi-xian-nida-sitahua-todui-chu-fang-fa-lec</link>
      <guid>https://forem.com/beachone1155/verceltong-he-neondeburantizhi-xian-nida-sitahua-todui-chu-fang-fa-lec</guid>
      <description>&lt;h2&gt;
  
  
  問題の発覚
&lt;/h2&gt;

&lt;p&gt;Vercelでプレビューデプロイを実行したところ、以下のエラーが発生しました：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;engineer_blog_comments: Create database branch for deployment

Branch limit reached. Upgrade your plan or delete unused branches.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Neonの無料プランでは、データベースブランチが最大10個まで&lt;/strong&gt;という制限があり、それに達してしまったようです。&lt;/p&gt;




&lt;h2&gt;
  
  
  原因の調査
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vercel統合Neonの動作
&lt;/h3&gt;

&lt;p&gt;VercelとNeonを統合すると、デフォルトで以下のような動作になります：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production環境（本番）&lt;/strong&gt;: メインのデータベースブランチを使用&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview環境（プレビュー）&lt;/strong&gt;: 各プレビューデプロイごとに&lt;strong&gt;新しいブランチを自動作成&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;つまり、PRを作成するたび、またはプレビューデプロイのたびに、Neon側で新しいデータベースブランチが作成されます。&lt;/p&gt;

&lt;h3&gt;
  
  
  なぜブランチが増えるのか
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;新しいPRを作成 → プレビューデプロイ実行&lt;/li&gt;
&lt;li&gt;Vercelが自動的にNeonブランチを生成（例: &lt;code&gt;preview/feature/r2e-frontend&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;PRをマージしてもブランチは残る&lt;/li&gt;
&lt;li&gt;新しいPRを複数作成 → ブランチが増え続ける&lt;/li&gt;
&lt;li&gt;10個に達するとエラー&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  対処方法: 固定プレビューブランチを使用
&lt;/h2&gt;

&lt;p&gt;すべてのプレビューデプロイで&lt;strong&gt;同じ固定ブランチ&lt;/strong&gt;を使用するように設定することで、新しいブランチが作成されなくなります。&lt;/p&gt;

&lt;h3&gt;
  
  
  設定手順
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Neon Consoleで固定ブランチを作成
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://console.neon.tech" rel="noopener noreferrer"&gt;Neon Console&lt;/a&gt;にアクセス&lt;/li&gt;
&lt;li&gt;プロジェクト「engineer_blog_comments」を選択&lt;/li&gt;
&lt;li&gt;左サイドバーの「Branches」をクリック&lt;/li&gt;
&lt;li&gt;「Create Branch」をクリック&lt;/li&gt;
&lt;li&gt;ブランチ名: &lt;code&gt;preview&lt;/code&gt;（任意の名前でOK）&lt;/li&gt;
&lt;li&gt;「Branch from」: &lt;code&gt;main&lt;/code&gt;を選択&lt;/li&gt;
&lt;li&gt;作成ボタンをクリック&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  2. 固定ブランチの接続文字列を取得
&lt;/h4&gt;

&lt;p&gt;作成したブランチを選択し、Connection Detailsから&lt;code&gt;POSTGRES_URL&lt;/code&gt;をコピーします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgresql://user:pass@ep-xxx-pooler.region.neon.tech/neondb?sslmode=require
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Vercel CLIで環境変数を設定
&lt;/h4&gt;

&lt;p&gt;Preview環境とDevelopment環境に、固定ブランチの&lt;code&gt;POSTGRES_URL&lt;/code&gt;を設定します：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Preview環境に設定&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"固定ブランチのPOSTGRES_URL"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add POSTGRES_URL preview

&lt;span class="c"&gt;# Development環境にも設定（ローカル開発用）&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"固定ブランチのPOSTGRES_URL"&lt;/span&gt; | vercel &lt;span class="nb"&gt;env &lt;/span&gt;add POSTGRES_URL development
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これにより：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Production環境&lt;/strong&gt;: 本番データベース（mainブランチ）を使用&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Preview環境&lt;/strong&gt;: 固定プレビューブランチを使用（新しいブランチは作成されない）&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Development環境&lt;/strong&gt;: 固定プレビューブランチを使用&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  設定の確認
&lt;/h2&gt;

&lt;p&gt;環境変数が正しく設定されたか確認：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vercel &lt;span class="nb"&gt;env ls&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;POSTGRES_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;出力例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POSTGRES_URL       Encrypted           Development         X ago
POSTGRES_URL       Encrypted           Preview              X ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;重要&lt;/strong&gt;: Production環境には&lt;code&gt;POSTGRES_URL&lt;/code&gt;を設定&lt;strong&gt;しない&lt;/strong&gt;ことで、本番データベースが使用されます。&lt;/p&gt;




&lt;h2&gt;
  
  
  既存の未使用ブランチの削除
&lt;/h2&gt;

&lt;p&gt;既に10個に達している場合は、未使用のブランチを削除する必要があります。&lt;/p&gt;

&lt;h3&gt;
  
  
  Neon Consoleでの削除手順
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://console.neon.tech" rel="noopener noreferrer"&gt;Neon Console&lt;/a&gt; → プロジェクト「engineer_blog_comments」&lt;/li&gt;
&lt;li&gt;「Branches」をクリック&lt;/li&gt;
&lt;li&gt;未使用のプレビューブランチを確認（例: &lt;code&gt;preview/feature/*&lt;/code&gt;, &lt;code&gt;preview/fix/*&lt;/code&gt;など）&lt;/li&gt;
&lt;li&gt;各ブランチの右側「...」メニューから「Delete」を選択&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;: &lt;code&gt;main&lt;/code&gt;ブランチは削除しないでください。本番環境で使用されています。&lt;/p&gt;




&lt;h2&gt;
  
  
  推奨される運用
&lt;/h2&gt;

&lt;h3&gt;
  
  
  定期的なクリーンアップ
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;マージ済みPRのプレビューブランチは定期的に削除&lt;/li&gt;
&lt;li&gt;アクティブなPRのブランチのみ保持&lt;/li&gt;
&lt;li&gt;ブランチ数が上限（9個）に近づいたら未使用ブランチを削除&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  スクリプト化（オプション）
&lt;/h3&gt;

&lt;p&gt;削除手順をスクリプト化することも可能です：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# scripts/cleanup-neon-branches.sh&lt;/span&gt;
&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# 未使用のNeonプレビューブランチを削除するためのスクリプト&lt;/span&gt;
&lt;span class="c"&gt;# 使用法: Neon Console (https://console.neon.tech) で手動削除を行うか、&lt;/span&gt;
&lt;span class="c"&gt;# Neon CLIを使用してブランチをリスト・削除します&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=========================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Neonプレビューブランチ削除ガイド"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=========================================="&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"このスクリプトは、Neon Consoleでのブランチ削除手順を案内します。"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"手順:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"1. https://console.neon.tech にアクセス"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"2. プロジェクト「engineer_blog_comments」を選択"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"3. 左サイドバーの「Branches」をクリック"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"4. 以下のブランチを削除（mainブランチは削除しない）:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   削除対象（未使用のプレビューブランチ）:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - preview/feature/*"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - preview/fix/*"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - preview/chore/*"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - その他の古いプレビューブランチ"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"5. 各ブランチの右側にある「...」メニューから「Delete」を選択"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⚠️  注意:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - mainブランチは削除しないでください"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - 現在使用中のプレビューブランチは削除しないでください"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   - 削除後、新しいプレビューデプロイで新しいブランチが作成されます"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"現在のNeonブランチ数が10/10の場合は、"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"上記の手順で削除してから新しいデプロイを行ってください。"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  まとめ
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;問題&lt;/strong&gt;: Vercel統合Neonでプレビューデプロイごとに自動ブランチが作成され、無料プランの上限（10個）に達した&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;解決&lt;/strong&gt;: 固定プレビューブランチを使用する設定で、新しいブランチの作成を防止&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;設定&lt;/strong&gt;: Vercel CLIでPreview環境に固定ブランチの&lt;code&gt;POSTGRES_URL&lt;/code&gt;を設定&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;運用&lt;/strong&gt;: 未使用ブランチを定期的にクリーンアップ&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;これで、プレビューデプロイ時に新しいブランチが作成されなくなり、ブランチ数の上限問題を回避できます。&lt;/p&gt;




&lt;h2&gt;
  
  
  参考リンク
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://console.neon.tech" rel="noopener noreferrer"&gt;Neon Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vercel.com/docs/cli" rel="noopener noreferrer"&gt;Vercel CLI Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://neon.tech/docs/guides/branching" rel="noopener noreferrer"&gt;Neon Branching Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vercel</category>
      <category>neon</category>
      <category>postgres</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
