開発アクティブ
東京散歩ルートマップ - Nuxt/Vue移行プロジェクト
📋 要件定義書
1. プロジェクト概要
東京の散歩ルートを地図上に表示し、各スポットの情報を可視化するインタラクティブなWebアプリケーション。

1.1 ターゲット顧客
| セグメント | 優先度 | ニーズ |
|---|---|---|
| 観光協会・自治体 | ◎ 主要 | 地域PRコンテンツとして配布・埋め込み |
| 地域メディア・Webマガジン | ◎ 主要 | 記事への埋め込み、差別化コンテンツ |
| 旅行会社・ツアー企画 | ○ 副次 | ツアールート説明資料 |
| 個人ブロガー | △ 補助 | 無料版での認知拡大(リード獲得) |
1.2 収益モデル(B2B SaaS)
| プラン | 月額 | 内容 |
|---|---|---|
| Free | 無料 | 3ルートまで、透かしあり、コミュニティサポート |
| Pro | ¥4,980 | 無制限ルート、透かしなし、CMS利用、優先サポート |
| Enterprise | 個別見積 | ホワイトラベル、専用ドメイン、SLA保証、オンボーディング |
追加収益オプション:
- ルート制作代行: ¥30,000〜/ルート
- カスタム開発: 時間単価
1.3 コスト構造・スケール方針
| 項目 | 現状(MVP) | スケール時 |
|---|---|---|
| 地図タイル | OpenStreetMap公開サーバー | CloudflareキャッシュまたはMapTiler有料 |
| ルーティング | OSRM公開サーバー(ビルド時のみ) | 自前OSRM Docker or 事前生成JSON |
| ホスティング | Cloudflare Pages(無料枠) | Workers有料プラン |
| 想定限界 | 月10万PV程度 | 100万PV以上は要検討 |
🔍 競合分析・市場調査
既存サービスとの比較
| サービス | 道沿いルート | スポット常時表示 | 編集可能 | 料金 |
|---|---|---|---|---|
| 本プロジェクト | ✅ | ✅ | ✅ | 無料 |
| NAVITIME Travel | △(電車メイン) | ❌ | ✅ | 無料〜 |
| ALKOO by NAVITIME | ✅ | ❌ | ❌ | 無料〜 |
| プラチナマップ | ❌ | ✅ | ✅ | 月7,980円〜 |
| TOKYO WALKING MAP | ❌ | ❌ | ❌ | 無料 |
| Googleマイマップ | ❌ | ❌ | ✅ | 無料 |
既存サービスの課題
- NAVITIME Travel / ALKOO
- 健康・移動効率が目的で、観光ガイドとしてのストーリー性がない
- スポットの解説が地図上に表示されない
- プラチナマップ
- 高機能だがB2B向けで月額課金
- 個人や小規模メディアには高コスト
- Googleマイマップ
- 自由にピンは立てられるが道沿いルート表示ができない
- スポット情報はクリックしないと見えない
- 従来の観光記事(テキスト+画像)
- インタラクティブ性ゼロ
- 読者が自分でGoogleマップにマッピングする手間
本プロジェクトの差別化ポイント
- OSRMによる実際の道路に沿ったルート表示
- スポット情報(名前・説明・タグ)を地図上に常時表示
- ツールチップの重なり回避アルゴリズムで視認性確保
- KMLエクスポートでGoogleマップ連携可能
- 完全無料・オープンソースでカスタマイズ自由
- ストーリー性のある観光ガイドとして活用可能
想定ユースケース
- 地域の観光協会が「おすすめ散歩ルート」を配布
- 個人ブロガーが「私の散歩ルート」を記事に埋め込み
- 旅行者が自分用にルートをカスタマイズ
- 地域メディアがインタラクティブな観光コンテンツを制作
🛠️ コア技術: OSRM (Open Source Routing Machine)
OSRMとは
OpenStreetMapのデータを使ったオープンソースのルーティングエンジン。 Googleマップのルート検索と同等の機能を、無料で利用できる。
API仕様
GET https://router.project-osrm.org/route/v1/{profile}/{coordinates}
profile: foot(徒歩), driving(車), bike(自転車)
coordinates: 経度1,緯度1;経度2,緯度2;経度3,緯度3...
リクエスト例
https://router.project-osrm.org/route/v1/foot/139.7706,35.7279;139.8107,35.7101?overview=full&geometries=geojson
レスポンス例
{
"routes": [{
"geometry": {
"type": "LineString",
"coordinates": [[139.7706, 35.7279], [139.7710, 35.7275], ...]
},
"distance": 5234.5,
"duration": 3920.2
}]
}
主要パラメータ
| パラメータ | 値 | 説明 |
|---|---|---|
overview | full | 詳細なルート座標を取得 |
geometries | geojson | GeoJSON形式で座標を取得 |
steps | true | ターンバイターン案内を取得(オプション) |
利用上の注意
- 無料で利用可能(公開デモサーバー)
- 大量リクエストの場合は自前サーバー推奨
- OpenStreetMapのデータに依存(日本は精度高い)
2. 機能要件
2.1 ルート表示機能
- 複数の散歩ルートをタブで切り替え表示
- 各ルートは異なるテーマカラーを持つ
- ルートは実際の歩行経路(OSRM API使用)で描画
- スタート地点は赤、ゴール地点は紫、経由地点はテーマカラーで表示
2.2 スポット表示機能
- 各スポットに番号付きマーカーを表示
- ツールチップ(吹き出し)でスポット情報を常時表示
- スポット名
- 説明文
- カテゴリタグ(絵文字付き)
- ツールチップは自動配置で重なりを回避
2.3 ツールチップ重なり回避アルゴリズム
- 各マーカーのピクセル座標を計算
- 4方向(右・左・上・下)を評価
- スコアリング方式で最適な方向を決定
- 他のツールチップとの重なり: -100点
- 他のマーカーとの重なり: -80点
- ルートラインとの重なり: -30点
- 画面外にはみ出す: -50点
- 右方向を優先: +5点
- 上方向を優先: +3点
- 地図のズーム・移動時に再計算
2.4 スポットリスト機能
- 画面下部にスポット一覧を表示
- スクロール可能
- タップで該当スポットにズーム
2.5 KMLエクスポート機能
- 現在表示中のルートをKML形式でダウンロード
- Googleマイマップにインポート可能な形式
KMLフォーマット仕様:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>ルート名</name>
<description>ルートの説明</description>
<!-- ルートライン用スタイル -->
<Style id="routeLine">
<LineStyle>
<color>FF00FF00</color> <!-- AABBGGRR形式(アルファ+BGR) -->
<width>4</width>
</LineStyle>
</Style>
<!-- スポットマーカー用スタイル -->
<Style id="spotMarker">
<IconStyle>
<Icon>
<href>http://maps.google.com/mapfiles/kml/paddle/red-circle.png</href>
</Icon>
</IconStyle>
</Style>
<!-- ルートライン -->
<Placemark>
<name>ルート</name>
<styleUrl>#routeLine</styleUrl>
<LineString>
<coordinates>
139.7706,35.7279,0
139.7904,35.7299,0
<!-- lng,lat,altitude 形式 -->
</coordinates>
</LineString>
</Placemark>
<!-- スポット(各ポイント) -->
<Placemark>
<name>1. 日暮里駅</name>
<description><![CDATA[スタート地点<br/>🚃 駅]]></description>
<styleUrl>#spotMarker</styleUrl>
<Point>
<coordinates>139.7706,35.7279,0</coordinates>
</Point>
</Placemark>
</Document>
</kml>
注意点:
- KMLの座標は
lng,lat,altitude順(GeoJSONと同じ) - 色は
AABBGGRR形式(HEX#RRGGBBから変換が必要) - CDATAでHTMLタグを含む説明文をエスケープ
2.6 ルート情報表示
- ルート名
- サブタイトル(ルートの特徴)
- 総距離
- 所要時間
2.7 CMS(管理画面)機能
対象ユーザー: 観光協会担当者、メディア編集者(非エンジニア)
ルート管理
- ルート一覧表示(作成日順/更新日順)
- ルート新規作成
- ルート編集(タイトル、説明、テーマカラー)
- ルート削除(確認ダイアログ付き)
- ルート複製(テンプレートとして再利用)
スポット管理
- 地図クリックでスポット追加
- ドラッグ&ドロップで順序変更
- スポット情報編集(名前、説明、タグ選択)
- スポット削除
- 住所・施設名から座標検索(Nominatim API)
プレビュー・公開
- リアルタイムプレビュー(編集中に地図反映)
- 下書き保存
- 公開/非公開切り替え
- 埋め込みコード生成(iframe)
ユーザー管理(Enterprise向け)
- チームメンバー招待
- 権限管理(閲覧のみ/編集可/管理者)
- 編集履歴・監査ログ
技術方針:
- 認証: Cloudflare Access または Auth0
- データ保存: Cloudflare D1 (SQLite)
- 画像アップロード: Cloudflare R2
3. 非機能要件
3.1 パフォーマンス
| 指標 | 目標値 | 測定条件 |
|---|---|---|
| LCP (Largest Contentful Paint) | 2.5秒以内 | 4G回線、Moto G4相当 |
| FCP (First Contentful Paint) | 1.8秒以内 | 同上 |
| ルート切り替え | 500ms以内 | 事前生成済みデータ使用時 |
| ツールチップ再計算 | 100ms以内 | 20スポット以下を前提 |
制約条件:
- 1ルートあたりの最大スポット数: 20件
- ツールチップ再計算:
debounce 150msで発火抑制 - 地図移動中は再計算スキップ(
moveendイベントで実行)
3.2 レスポンシブ対応
| ブレークポイント | ツールチップ表示 | スポットリスト |
|---|---|---|
| モバイル(〜767px) | ズームレベル15以上で表示、14以下は非表示 | ボトムシート形式 |
| タブレット(768px〜1023px) | 常時表示(最大10件)、残りは折りたたみ | サイドパネル |
| デスクトップ(1024px〜) | 常時表示(全件) | サイドパネル |
3.3 ブラウザ対応
- Chrome(最新2バージョン)
- Safari(最新2バージョン)
- Firefox(最新2バージョン)
- Edge(最新2バージョン)
3.4 SSR/CSR方針
Leafletはブラウザ専用のため、クライアントサイドのみで描画する。
<!-- 実装方針 -->
<ClientOnly>
<LMap :zoom="zoom" :center="center">
<!-- 地図コンテンツ -->
</LMap>
<template #fallback>
<div class="map-skeleton">地図を読み込み中...</div>
</template>
</ClientOnly>
<ClientOnly>でラップ- フォールバックにスケルトンUIを表示
- Leaflet関連コンポーネントは動的import不要(vue-leafletが対応済み)
3.5 OSRM利用方針
| 項目 | 方針 |
|---|---|
| 実行タイミング | ビルド時に事前生成(SSG) |
| 保存形式 | GeoJSON形式でJSONファイルに保存 |
| フォールバック | API失敗時は直線(スポット間を結ぶPolyline)で描画 |
| キャッシュ | 生成済みGeoJSONを public/routes/ に配置 |
| 更新トリガー | スポット座標変更時のみ再生成 |
// ビルドスクリプト例
// scripts/generate-routes.ts
async function generateRoute(spots: Spot[]): Promise<GeoJSON.LineString> {
const coords = spots.map(s => `${s.lng},${s.lat}`).join(';');
const url = `https://router.project-osrm.org/route/v1/foot/${coords}?overview=full&geometries=geojson`;
try {
const res = await fetch(url);
const data = await res.json();
return data.routes[0].geometry;
} catch {
// フォールバック: 直線
return {
type: 'LineString',
coordinates: spots.map(s => [s.lng, s.lat])
};
}
}
3.6 法的要件
| 項目 | 対応 |
|---|---|
| OSMクレジット | 地図右下に © OpenStreetMap contributors を常時表示(必須) |
| OSRMクレジット | フッターに Powered by OSRM を表示(推奨) |
| タイル利用規約 | OSM Tile Usage Policyに準拠(過度なアクセス禁止) |
| プライバシー | 位置情報は取得しない(ユーザー追跡なし) |
4. 技術スタック
| 項目 | 技術 |
|---|---|
| フレームワーク | Nuxt 3 |
| UIフレームワーク | Vue 3 (Composition API) |
| 地図ライブラリ | Leaflet + vue-leaflet |
| スタイリング | Tailwind CSS または CSS Modules |
| 状態管理 | Pinia(必要に応じて) |
| ルーティングAPI | OSRM (Project OSRM) |
| 地図タイル | OpenStreetMap |
5. データ構造
// ========================================
// タグ辞書(アイコン・表示名の一元管理)
// ========================================
const TAG_DICTIONARY = {
station: { icon: '🚃', label: '駅' },
shrine: { icon: '⛩️', label: '神社' },
temple: { icon: '🛕', label: '寺院' },
park: { icon: '🌳', label: '公園' },
shopping: { icon: '🛒', label: '商店街' },
cafe: { icon: '☕', label: 'カフェ' },
bridge: { icon: '🌉', label: '橋' },
landmark: { icon: '🗼', label: 'ランドマーク' },
history: { icon: '🏛️', label: '史跡' },
food: { icon: '🍛', label: 'グルメ' },
music: { icon: '🎸', label: '楽器街' },
books: { icon: '📚', label: '本の街' },
flower: { icon: '🌸', label: '花の名所' },
} as const;
type TagId = keyof typeof TAG_DICTIONARY;
// ========================================
// ルート型定義
// ========================================
interface Route {
id: string; // UUID形式推奨
title: string;
subtitle: string;
distance: number; // メートル単位(表示時に変換)
duration: number; // 秒単位(表示時に変換)
color: string; // HEX形式 #RRGGBB
spots: Spot[];
geometry?: GeoJSON.LineString; // 事前生成されたルート
createdAt: string; // ISO8601
updatedAt: string; // ISO8601
}
// ========================================
// スポット型定義
// ========================================
interface Spot {
id: string; // UUID形式推奨
order: number; // 表示順(1始まり)
name: string;
lat: number;
lng: number;
description: string;
tags: TagId[]; // 複数タグ対応(配列)
role: 'start' | 'waypoint' | 'end';
}
// ========================================
// マーカー色の優先順位
// ========================================
// role による色決定(テーマカラーより優先)
const MARKER_COLORS = {
start: '#e74c3c', // 赤(固定)
end: '#9b59b6', // 紫(固定)
waypoint: null, // ルートのテーマカラーを使用
} as const;
// ========================================
// ツールチップ位置計算用
// ========================================
interface TooltipPosition {
direction: 'top' | 'bottom' | 'left' | 'right';
offset: [number, number];
score: number;
}
interface PixelRect {
left: number;
right: number;
top: number;
bottom: number;
}
// ========================================
// 座標形式の統一ルール
// ========================================
// - データ定義: { lat, lng } オブジェクト形式
// - Leaflet: [lat, lng] 配列(LatLngTuple)
// - GeoJSON: [lng, lat] 配列(Position)
//
// 変換ユーティリティ:
// const toLeaflet = (s: Spot): [number, number] => [s.lat, s.lng];
// const toGeoJSON = (s: Spot): [number, number] => [s.lng, s.lat];
6. コンポーネント設計
src/
├── components/
│ ├── map/
│ │ ├── WalkingMap.vue # メイン地図コンポーネント
│ │ ├── RouteMarker.vue # マーカーコンポーネント
│ │ ├── RoutePolyline.vue # ルート線コンポーネント
│ │ └── SpotTooltip.vue # ツールチップコンポーネント
│ ├── ui/
│ │ ├── RouteTabs.vue # ルート切り替えタブ
│ │ ├── RouteHeader.vue # ルート情報ヘッダー
│ │ ├── SpotList.vue # スポット一覧
│ │ ├── SpotListMobile.vue # モバイル用ボトムシート
│ │ └── KmlDownloadButton.vue # KMLダウンロードボタン
│ ├── admin/ # CMS用コンポーネント
│ │ ├── RouteEditor.vue # ルート編集フォーム
│ │ ├── SpotEditor.vue # スポット編集フォーム
│ │ ├── MapEditor.vue # 地図上でのスポット編集
│ │ ├── TagSelector.vue # タグ選択UI
│ │ ├── ColorPicker.vue # テーマカラー選択
│ │ └── EmbedCodeGenerator.vue # 埋め込みコード生成
├── composables/
│ ├── useRouteData.ts # ルートデータ管理
│ ├── useTooltipPlacement.ts # ツールチップ配置計算
│ ├── useOsrmRoute.ts # OSRM API呼び出し(ビルド時)
│ ├── useKmlExport.ts # KMLエクスポート
│ ├── useGeocode.ts # 住所→座標変換(Nominatim)
│ └── useResponsiveTooltip.ts # レスポンシブ対応制御
├── stores/ # Pinia ストア
│ ├── route.ts # ルート状態管理
│ └── editor.ts # CMS編集状態管理
├── data/
│ └── routes.ts # ルートデータ定義
├── types/
│ └── index.ts # 型定義
├── pages/
│ ├── index.vue # 公開ビュー(ルート一覧)
│ ├── routes/[id].vue # ルート詳細ページ
│ └── admin/ # CMS管理画面
│ ├── index.vue # ダッシュボード
│ ├── routes/index.vue # ルート一覧
│ └── routes/[id].vue # ルート編集
└── server/
└── api/
├── routes/ # ルートCRUD API
└── geocode.ts # 住所検索プロキシ
7. 初期ルートデータ
ルート1: 日暮里→スカイツリー
| # | スポット名 | 緯度 | 経度 | 説明 | タグ |
|---|---|---|---|---|---|
| 1 | 日暮里駅 | 35.7279 | 139.7706 | スタート地点 | 🚃 出発 |
| 2 | ジョイフル三の輪 | 35.7299 | 139.7904 | 昭和レトロな商店街 | 🛒 商店街 |
| 3 | 浄閑寺 | 35.7264 | 139.7930 | 吉原遊女の歴史 | 🛕 寺社 |
| 4 | 山谷堀公園 | 35.7186 | 139.7993 | 江戸時代の船着場跡 | 🌳 公園 |
| 5 | 今戸神社 | 35.7166 | 139.8017 | 招き猫・縁結び | ⛩️ 神社 |
| 6 | 隅田公園 | 35.7117 | 139.8030 | 桜の名所 | 🌸 公園 |
| 7 | すみだリバーウォーク | 35.7105 | 139.8050 | 2020年開通の橋 | 🌉 橋 |
| 8 | 東京ミズマチ | 35.7095 | 139.8075 | 高架下カフェ | ☕ カフェ |
| 9 | 東京スカイツリー | 35.7101 | 139.8107 | 634mのランドマーク | 🗼 ゴール |
ルート2: 神田→上野
| # | スポット名 | 緯度 | 経度 | 説明 | タグ |
|---|---|---|---|---|---|
| 1 | 神田駅 | 35.6918 | 139.7709 | ビジネス街 | 🚃 出発 |
| 2 | 神田古書店街 | 35.6958 | 139.7580 | 世界最大級130店舗 | 📚 本の街 |
| 3 | 神保町 | 35.6959 | 139.7574 | カレーの聖地 | 🍛 グルメ |
| 4 | 御茶ノ水 | 35.6998 | 139.7651 | 楽器店街 | 🎸 楽器街 |
| 5 | 聖橋 | 35.7010 | 139.7680 | 美しいアーチ橋 | 🌉 橋 |
| 6 | 湯島聖堂 | 35.7020 | 139.7690 | 江戸の学問所 | 🏛️ 史跡 |
| 7 | 湯島天神 | 35.7079 | 139.7687 | 学問の神様 | ⛩️ 神社 |
| 8 | 不忍池 | 35.7120 | 139.7710 | 蓮の名所 | 🌺 公園 |
| 9 | アメ横 | 35.7074 | 139.7747 | 食べ歩き天国 | 🛒 商店街 |
| 10 | 上野駅 | 35.7141 | 139.7774 | 文化施設も多い | 🚃 ゴール |
🤖 実装プロンプト
以下をClaude等のAIに渡してください:
プロンプト
Nuxt 3 + Vue 3 (Composition API) + Leafletで、東京の散歩ルートマップアプリを作成してください。
## 要件
### 基本機能
1. 複数の散歩ルートをタブで切り替え表示
2. 各スポットに番号付きマーカーを表示
3. スポット情報(名前・説明・タグ)をツールチップで常時表示
4. スポットリストをタップで該当地点にズーム
5. KMLファイルをダウンロードできるボタン
### 重要な実装ポイント:ツールチップの重なり回避
ツールチップが他のツールチップ、マーカー、ルートラインと重ならないよう、
ピクセル座標ベースで自動配置を計算してください。
アルゴリズム:
1. 各マーカーの画面上のピクセル座標を取得
2. 4方向(右・左・上・下)それぞれにツールチップを置いた場合の矩形を計算
3. スコアリングで最適な方向を決定
- 他のツールチップとの重なり: -100点
- 他のマーカーとの重なり: -80点
- ルートラインとの重なり: -30点
- 画面外にはみ出す: -50点
- 右方向を優先: +5点
4. 地図のズーム・移動時に再計算
### 技術スタック
- Nuxt 3
- Vue 3 Composition API
- @vue-leaflet/vue-leaflet
- Tailwind CSS
- OSRM API(歩行ルート取得)
### ルートデータ
ルート1: 日暮里→スカイツリー(テーマカラー: #2ecc71)
- 日暮里駅 (35.7279, 139.7706) - スタート
- ジョイフル三の輪 (35.7299, 139.7904) - 昭和レトロな商店街
- 浄閑寺 (35.7264, 139.7930) - 吉原遊女の歴史
- 山谷堀公園 (35.7186, 139.7993) - 江戸時代の船着場跡
- 今戸神社 (35.7166, 139.8017) - 招き猫・縁結び
- 隅田公園 (35.7117, 139.8030) - 桜の名所
- すみだリバーウォーク (35.7105, 139.8050) - 2020年開通の橋
- 東京ミズマチ (35.7095, 139.8075) - 高架下カフェ
- 東京スカイツリー (35.7101, 139.8107) - ゴール
ルート2: 神田→上野(テーマカラー: #3498db)
- 神田駅 (35.6918, 139.7709) - スタート
- 神田古書店街 (35.6958, 139.7580) - 世界最大級130店舗
- 神保町 (35.6959, 139.7574) - カレーの聖地
- 御茶ノ水 (35.6998, 139.7651) - 楽器店街
- 聖橋 (35.7010, 139.7680) - 美しいアーチ橋
- 湯島聖堂 (35.7020, 139.7690) - 江戸の学問所
- 湯島天神 (35.7079, 139.7687) - 学問の神様
- 不忍池 (35.7120, 139.7710) - 蓮の名所
- アメ横 (35.7074, 139.7747) - 食べ歩き天国
- 上野駅 (35.7141, 139.7774) - ゴール
### ファイル構成
composables/でロジックを分離し、再利用可能な形にしてください。
特にuseTooltipPlacement.tsでツールチップ配置ロジックを切り出してください。
📁 参考:元のHTMLファイル
tokyo-walk-routes-v3.html を同梱しています。
このファイルの実装を参考にNuxt/Vue版を作成してください。
特に以下の関数が重要です:
updateTooltipPositions()- ツールチップ配置計算getTooltipRect()- 方向ごとの矩形計算rectsOverlap()- 矩形の重なり判定lineIntersectsRect()- ルートと矩形の交差判定generateKML()- KML生成fetchRoute()- OSRM API呼び出し
✅ 実装チェックリスト
Phase 1: プロジェクトセットアップ(1-2日)
- Nuxt 3プロジェクト作成(別リポジトリ)
- vue-leaflet インストール・ClientOnly設定
- Tailwind CSS設定
- 型定義作成(types/index.ts)
- タグ辞書定義(TAG_DICTIONARY)
- ESLint/Prettier設定
Phase 2: ビルドスクリプト(1日)
- scripts/generate-routes.ts 作成
- OSRM API呼び出し・GeoJSON保存
- フォールバック(直線描画)実装
- npm script追加(
pnpm generate:routes)
Phase 3: 地図基本機能(2-3日)
- 地図表示(OpenStreetMapタイル)
- OSMクレジット表示
- マーカー表示(role別色分け)
- ルートライン表示(事前生成GeoJSON読み込み)
- ルートデータのJSON定義
Phase 4: ツールチップ(2-3日)
- 基本表示(名前・説明・タグ)
- 重なり回避アルゴリズム(useTooltipPlacement.ts)
- debounce 150ms 実装
- ズーム・移動時の再計算(moveend イベント)
Phase 5: レスポンシブUI(2日)
- デスクトップ:サイドパネル+全ツールチップ
- タブレット:サイドパネル+10件制限
- モバイル:ボトムシート+ズーム連動表示
- タブ切り替えUI
- ルート情報ヘッダー
Phase 6: エクスポート機能(1日)
- KML生成ロジック(useKmlExport.ts)
- 色変換(HEX→AABBGGRR)
- ダウンロードボタンUI
- Googleマイマップでの動作確認
Phase 7: CMS基盤(3-4日)
- Cloudflare D1スキーマ設計
- ルートCRUD API(server/api/routes/)
- 認証設定(Cloudflare Access)
- 管理画面ルーティング(/admin/)
Phase 8: CMS編集機能(4-5日)
- ルート一覧ページ
- ルート編集フォーム(タイトル、説明、色)
- 地図クリックでスポット追加
- スポットドラッグ&ドロップ順序変更
- タグ選択UI
- 住所検索(Nominatim連携)
- リアルタイムプレビュー
Phase 9: 公開・埋め込み(2日)
- 公開/非公開切り替え
- 埋め込みコード生成(iframe)
- 埋め込み用軽量ビュー(/embed/id)
Phase 10: 仕上げ(2-3日)
- Lighthouse パフォーマンス測定
- ユニットテスト(Vitest)
- E2Eテスト(Playwright)
- ドキュメント整備
📊 工数見積もり
| Phase | 内容 | 見積もり |
|---|---|---|
| 1-6 | MVP(公開ビューのみ) | 10-12日 |
| 7-9 | CMS機能 | 9-11日 |
| 10 | 品質保証 | 2-3日 |
| 合計 | 21-26日(1人月程度) |
優先順位:
- Phase 1-6 を先行リリース(MVP)
- ユーザーフィードバック収集
- Phase 7-10 でCMS追加