クライアントは会社ではなく、営業マネージャー個人です — 自分だけのリード獲得チャネルを必要としていた長期レンタルの担当者です。プロダクト全体を規定するドメインルールは「価格は計算しない。参照する」。車両ごとに12セルのマトリクス — 契約期間3種 × 走行距離プラン2種 × 保証金2種 — に確定月額が入っており、訪問者が目にする見積もりは概算ではなく、実際の契約書に載るその数字です。訪問者が条件を選ぶと、それに合う確定価格が表示されます。静的な価格表サイトとは、この点が異なります。
アプリケーションは Next.js 16 App Router と React 19、TypeScript で書き、Tailwind でスタイリングしています。DB は Drizzle ORM 経由の Neon Postgres、デプロイは Vercel、車両画像は非公開の Blob ストレージに置いています。独立したバックエンドはありません — アプリの API route 8本がそのままバックエンドで、JWT 認証の管理者ゲートの内側にあります。
管理画面では次の機能を提供します。画像アップロードと12セル価格グリッドを備えた車両 CRUD、相談リード管理、そしてクライアントが実際に読める計測 — 見積もり閲覧→相談開始のコンバージョンファネル、車種別の転換率、見積もりイベントの異常検知、12セルに欠けのある車両を検出するヘルスチェック。デプロイはユニットテストからデプロイ後スモークまで、7つのゲートを通る単一スクリプトでのみ行います。
検索で見つけてもらうために、次を用意しました — 構造化データ、sitemap、llms.txt、共有カード、Safari が実際に読める ICO エンコーディングにバイト互換で作り直したファビコン、そして同じ夜のうちに3つの検索コンソールへの登録とインデックス要請まで。ブランドの本社から「公式サイトと誤認されかねない」という懸念が上がったとき、私は運営主体を区別する仕組みを設けました — 担当者先行の命名、誤認防止の告知、公式ドメインと構造的に切り分けるアンカー。その三日後、クライアントともう一度向き合った末に、それをすべて取り壊してブランド先行に戻しました。競合サイトもブランドを前面に出しており、同じ方針を採ることにしました。
サーバーレス関数とデータベースは当初はユーザーから遠い地域にあり、両方をユーザー側へ移すとコールドスタートは実測で81%短縮されました。DB 移行では接続プールの障害にも遭いました — pg_dump の残した痕跡がコネクションプーラーの再利用セッションに空の search_path を焼き付け、確かに存在するテーブルが見つからない状態になり、汚染されたバックエンドを強制終了して初めて解消しました。
開発には3週間かかり、ソースファイル94個とコミット100余りが残りました。独自ドメインで公開し、検索インデックスと計測を整えたうえで、クライアントが自分で使える運用マニュアルとともに納品しました。
