ポートフォリオ
VAN-CONVERSION-ESTIMATOR PUBLIC / 2026

一部の情報は非公開です。アクセスキーをお持ちの方は完全版をご覧いただけます。

商用バン専門店

商用バン専門店のサイト刷新と見積もりシミュレーター

Wix の旧サイトを SolidStart の訪問者向けサイトと Nuxt の CMS へ作り直し、114 の価格ポイントを扱う見積もりシミュレーターを追加。初期表示は 3 秒以上から 1 秒以内になりました。

概要

トヨタ ハイエース/日産 キャラバンの架装を扱う販売店のサイトを作り直しました。旧サイトは Wix 上にあり、初期表示に 3 秒以上かかるうえ、オンラインで見積もりを出す手段がまったくありませんでした。新サイトはデザインから公開まで一人で担当し、初期表示は 1 秒以内に収めています。

技術選定は訪問者側と運用側で分けています。訪問者側は SolidStart によるサーバーサイドレンダリング、運用側は Nuxt の CMS。どちらも Cloudflare Workers 上で動き、同じ D1 を共有します。全体は Turborepo のモノレポで、見積もりのロジックは独立したパッケージに切り出し、両側で使い回しています。

コード規模 6 万行以上の TypeScript
自動テスト 1,000 件以上のテストケース
初期表示 3 秒以上 → 1 秒以内

主要機能:新車架装の見積もりシミュレーター

シミュレーターはメーカーの車体仕様 13 種類と架装プラン 23 種類を扱い、その掛け合わせで見積もり可能な価格ポイントが 114 通りできます。価格ポイントごとに、そこで選べるオプションが紐づきます。オプションは 49 種類を 8 グループに整理してあり、単一選択のグループと複数選択のグループでは意味が異なります。組み合わせは全体でおよそ 8.6×10¹⁰ 通りです。

設定はすべて D1 にあり、CMS のフォームから運用担当者が編集します。車種・価格・オプション・適用範囲は自由に変えられますが、JSON が見えることはなく、ルールの式を書く必要もありません。新しい関係の種類が要るようになったときは、私がエンジンに 1 つ追加し、業務の言葉で書かれたフォームを合わせて用意します。

見積もり可能な価格ポイント 110 通り以上
オプションの適用関係 2,800 件以上
LIVE 操作できます
見積もりシミュレーターのデモ:車体仕様を選び、駆動方式と燃料を選び、架装プランを選び、オプションにチェックを入れると合計金額がその場で更新される。条件を満たさないオプションは表示したまま操作不可にし、日本語で理由を添える。

アーキテクチャ

3 つの Worker が同じ D1 を共有します。訪問者向けサイトは基本的に読み取りのみで、書き込みは問い合わせの送信と「気になる」のカウントの 2 経路だけです。CMS は読み書きの全部を担当し、YouTube と Instagram を取得する cron Worker が 6 時間ごとに動きます。

外部 API に触れるのは cron Worker だけで、サードパーティの認証情報もその Worker にしか置いていません。訪問者向けサイトと CMS からは手が届かない構成です。取得結果は D1 のスナップショットとして書き、失敗したときはエラーを記録するだけで既存のデータを上書きしません。トップページには前回成功したときの内容がそのまま残ります。

3 つの Worker が同じ D1 を共有するデプロイ構成とデータの流れ
3 つの Worker が同じ D1 を共有するデプロイ構成とデータの流れ

技術的な難所

連鎖的な無効化と、変化が止まるまでの反復

オプションを 1 つ外すと、それに依存していたオプションが無効になり、その無効化がさらに次の層を巻き込むことがあります。ルールエンジンは選択済みの集合を while-changed のループで走査し、1 周しても何も変わらなくなるまで繰り返します。1 回走査するだけでは、宙に浮いた選択が残ってしまうためです。

エンジンは実行時依存ゼロの純粋関数パッケージで、DOM もフレームワークもデータベースも知りません。だからこそ訪問者側と CMS のプレビューが同じ判定を共有でき、そのままユニットテストにかけられます。金額に関わるロジックはすべてこの形で切り出しています。

転送データの辞書化

素直に入れ子で持つと、オプションの一覧が価格ポイントごとに丸ごと複製されます。114 の価格ポイントで適用関係は合計 2,837 件あり、同じ定義が何度もシリアライズされていました。サーバーからはトップレベルの辞書と id の参照だけを送り、クライアント側で一度だけ組み立て直す形にしています。エンジンにも画面にも手を入れていません。

保存時に依存の循環を止める

運用担当者が「A には B が要る、B には A が要る」と設定してしまうと、その 2 つは訪問者側で永久に選べなくなります。CMS は書き込みの前に三色マークの深さ優先探索をかけ、循環を見つけたら保存を拒否します。そのとき id の経路はオプション名に翻訳して伝えます。

同じ検査で、あと 2 種類の詰んだ設定も止めています。依存先が同じ単一選択グループの中にある場合と、依存先がその価格ポイントの適用オプションに含まれていない場合です。どちらも、訪問者側では表示されているのに永久に押せないオプションになります。

その他の判断

  • 見積もり結果はデータベースに保存しない。共有リンクが持つのは価格ポイントの id とオプションの id だけで、サーバーが D1 から計算し直し、ルールエンジンをもう一度通す。URL を書き換えて不正な組み合わせを作り、金額を吊り上げることはできない
  • 条件を満たさないオプションは表示したまま操作不可にし、「要◯◯」「◯◯と併用不可」と添える。車種ごとの違いがお客様の目に見えるようにするため
  • 見積書の PDF はブラウザ側で生成する。jsPDF はクリック時に動的読み込みするので、SSR の成果物にもメインバンドルにも入らない
  • 見積もりの設定は読み取りが圧倒的に多いので、サーバーの結果はエッジキャッシュに乗せ、TTL は 5 分
  • D1 が絡むテストは実際の workerd 上で走らせ、本物のマイグレーションからデータベースを作る。データベースはモックしない
訪問者側の見積もりシミュレーター:段階的に絞り込む選択の流れと、その場で更新される金額パネル
訪問者側の見積もりシミュレーター:段階的に絞り込む選択の流れと、その場で更新される金額パネル
CMS のオプション設定画面:依存関係は業務の言葉で表示され、運用担当者が JSON やルールの式に触れることはない
CMS のオプション設定画面:依存関係は業務の言葉で表示され、運用担当者が JSON やルールの式に触れることはない
公開サイト