Portly のしくみ

サービスを横断してシェアサイクルの「借りられる・返せる」を 1 つの地図で見える化する Web アプリ。
このページは、その裏側の考え方と技術選定を 5 分でつかむための資料です。

1. これは何か

Portly は、HELLO CYCLING・ドコモ・バイクシェアなど、別々のアプリに分かれているシェアサイクルのポート (置き場) を、1 枚の地図でまとめて見られる Web アプリです。

解決したい課題

  • サービスが分断されている — どのアプリを開けば近くにポートがあるか分からない。
  • 「返せるか」が見えない — 借りられても、目的地で返す枠が空いていないと困る。既存アプリは「空き台数」ばかり強調しがち。
  • 毎回アプリを入れる気にならない — 旅行や出張で一度しか使わない人にとってインストールは負担。

2. 3 つのこだわり

1

Web 完結

アプリ不要。URL をシェアすれば、相手は開くだけで同じ場所・同じ表示が再現されます。

2

返却枠を可視化

「貸出可能な台数」だけでなく「返却できる空き枠」も表示。安心して目的地まで走れます。

3

サービス横断

提供事業者ごとに色で塗り分け。複数サービスを 1 つの地図上で比較できます。

3. 全体のしくみ (図)

外部のオープンデータを定期的に取り込み、データベースとキャッシュに整え、ブラウザの地図で表示する、というシンプルな流れです。

ODPT (GBFS) 公共交通オープンデータ Vercel Cron 定期取り込み (静的 / 動的の 2 ジョブ) Neon Postgres ポート情報 (静的) Upstash Redis 空き台数 (動的キャッシュ) Next.js API /api/stations ブラウザ MapLibre GL 地図 点線 = 外部サービス / 実線矩形 = Portly が管理する範囲

4. 技術スタックと選んだ理由

すべて「サーバ運用を持たずに、Web 完結でリアルタイム性も出す」という方針から逆算しています。

レイヤ採用したものなぜそれか
フロントエンド Next.js 15 + React 19 Web 完結方針と相性が良く、Vercel への素直なデプロイで運用負荷を最小化できる。
地図 MapLibre GL JS OSS 由来でライセンスの縛りが緩く、ベクタータイル表示にも対応。将来の見た目調整に幅がある。
データベース Neon Postgres + Drizzle ORM サーバレス Postgres でアイドルコストがほぼゼロ。型安全な ORM でスキーマ変更も安全。
キャッシュ Upstash Redis 地図をパン/ズームするたびに走る API を吸収。DB への直撃を防ぎ、表示も速くなる。
定期実行 Vercel Cron 外部 cron サービスを足さず、デプロイと同じ場所で完結。GBFS の定期取り込みに十分。
データソース GBFS / ODPT 公的オープンデータ。HELLO CYCLING・ドコモいずれも GBFS で公開しており、横断対応がしやすい。
スタイル Tailwind CSS UI を素早く組み、デザインの一貫性を保てる。地図上の小さな UI 部品との相性も良い。
バリデーション Zod 外部 API (GBFS) のレスポンスを実行時に検証。フォーマット変化を早期に検知できる。
ホスティング Vercel Next.js / Cron / DB / Redis を 1 ベンダーで束ねられ、個人運用の負荷が最小。

5. データの流れ (2 つの経路)

「定期的にデータを溜める経路」と「ブラウザが必要な範囲だけ取りに来る経路」が分かれているのがポイントです。

定期更新 (裏側)

毎日 03:00 静的同期 — ODPT からポート情報 (場所・名前・容量) を取り込み Postgres に保存。
毎日 04:00 動的更新 — 各ポートの空き台数・空き枠を取得し、Redis に短い寿命で保存。

※ 無料プランの cron 制約に合わせて 1 日 1 回。プランを上げれば 10 分間隔等に細かくする想定。

画面表示 (表側)

パン/ズーム時 範囲だけ取得 — 地図に映っている範囲 (bbox) を API に渡し、必要なポートだけ返す。
ピン押下時 詳細フェッチ — 最初は最小情報だけ返し、ピンを押したときに詳細を別 API で読みに行く。

※ 一覧 API のレスポンスを軽くして、地図移動時の通信を 60% 以上削減しています。

6. 収益とサステナビリティ

長期的に運用を続けるため、複数の小さな収益源を組み合わせる方針です。

有料プランや課金で締め出さず、Web で誰でも開けるオープンさを維持することを優先しています。

7. これからやりたいこと