【第1回】路線バス乗り換え PWA を Firebase x React で作った — 概要・機能・技術スタック
目次
はじめに
「よく使うバスルートなのに、毎回 Google マップで同じ検索をしている」——そんな小さなストレスを解消するため、React + TypeScript + Firebase でプライベートな路線バス乗り換え PWA を自作しました。
認証・データベース・サーバー関数・ホスティングをすべて Firebase で統一したフルスタック構成です。個人〜家族向けの小規模アプリで Firebase を選ぶメリットが凝縮されているので、同じ構成を検討している方の参考になれば幸いです。
今回はアプリの概要・主要機能・技術スタックを紹介します。
アプリの主要機能と画面構成
アプリは3つの画面で構成されています。
バス検索モード(メイン画面)
1,000件以上のバス停組み合わせから、発地・着地の入力欄でルートを絞り込んで表示します。
- 市営バスのルートをタップ → リアルタイム時刻表・接近情報ページを直接開く
- 民営バスはあらかじめ登録した URL を開く
- バス停名・読み仮名のどちらでも絞り込み可能
- 目的のルートが見つかったら★でお気に入り登録

お気に入りタブ
よく使うルートをピン留めしておき、タップ一発でリアルタイム時刻表・接近情報を開けます。
- ドラッグ&ドロップで並び順を変更 → Firestore にリアルタイム反映
- 家族のスマホでも同じ並び順が共有される

便利なリンク集
乗り換えに役立つリンクをまとめたタブです。路線図 PDF など定期的に URL が変わるリンクは、Cloud Functions でスクレイピングして常に最新の URL を動的取得しています。「リンクが切れて開けない」という問題を自動で解消しています。
※ スクレイピングを実装する際は、対象サービスの利用規約を事前にご確認ください。本アプリは個人・プライベート利用を前提とした実装です。

技術スタック
| カテゴリ | 技術 | 選んだ理由 |
|---|---|---|
| フロントエンド | React 18.3 + TypeScript 4.9 | 型安全で大規模なバス停データを扱いやすい |
| UI フレームワーク | Material-UI 6.1 | テーマカラー設定だけで統一感のある UI が作れる |
| 認証 | Firebase Auth | 数行の実装でメール認証・セッション管理が完結 |
| データベース | Firestore | リアルタイム同期でお気に入りの変更が即座に反映 |
| サーバー関数 | Firebase Cloud Functions(Node.js 20) | スクレイピング処理をサーバーレスで運用 |
| ドラッグ&ドロップ | @dnd-kit(core, sortable) | タッチデバイスに対応した軽量ライブラリ |
| ルーティング | React Router DOM 6 | SPA のタブ遷移をシンプルに管理 |
| ホスティング | Firebase Hosting | CDN 配信・HTTPS が無料かつ設定ゼロで完結 |
フルスタック Firebase 構成で統一しており、インフラ管理をほぼゼロにできています。個人〜家族向けのプライベートアプリには Firebase がとても相性の良い選択肢です。
React + Firebase で実現した3つの技術ポイント
1. バス時刻表 URL をリアルタイムで動的生成する
課題:バス事業者のナビシステムは URL に「バス停 ID + 日時」を含める必要があり、時刻が固定では意味がない。
解決策:タップした瞬間の現在時刻を動的に URL に埋め込む。
バス停 ID の管理がポイントです。BUS_STOP_MAP(1,252件のバス停名→ID マッピング)をアプリバンドルに含めることで、Firestore を参照せずオフラインでも URL 生成できます。Firestore の読み取りコスト削減にもなります。
BUS_STOP_MAP のデータは、市営バスナビが提供しているバス停検索 API を呼び出して全バス停を一括取得し、TypeScript の定数ファイルとして書き出す自動生成スクリプトを別途用意しています。「バス停名」をキーに「バス停 ID とふりがな」を引けるオブジェクト構造になっており、辞書を引く感覚でバス停 ID をすぐ参照できます。1,252件のデータはアプリのビルド時にバンドルへ含まれるため、実行時に外部へ問い合わせる必要がありません。路線改編でバス停が増減した際はスクリプトを再実行して定数ファイルを更新するだけでよく、Firestore にバス停マスタを持たせる必要がないのも利点です。
以下は、バス停 ID と現在時刻から乗換検索 URL を生成するコードです。
// バス停IDと現在時刻から乗換検索URLを生成する例
const now = new Date();
const dateStr = formatDate(now); // YYYYMMDD
const timeStr = formatTime(now); // HHMM
const url = `https://YOUR_BUS_NAVI_HOST/BusTransfer`
+ `?startBusStopId=${startId}&endBusStopId=${endId}&date=${dateStr}&time=${timeStr}`;
2. 1,252件のバス停を発着地フィルターで高速絞り込み
課題:1,252件という大量のバス停組み合わせから、目的のルートを素早く見つける必要がある。
解決策:Firestore を参照せず、ブラウザのみで完結するクライアントサイドフィルタリングを実装。
発地・着地それぞれを独立した TextField で絞り込める UI を作りました。バス停名・読み仮名のどちらでも検索でき、30件ずつページングして「もっと見る」で追加表示します。Firestore を参照しないため、検索レスポンスは即時です。
3. ドラッグ&ドロップの並び順を Firestore にリアルタイム同期
課題:お気に入りの並び順をデバイスをまたいで共有したい。
解決策:@dnd-kit のドロップイベントで Firestore の users/{uid}/favoriteRoutes を即時更新。
ドロップ完了時に Firestore を更新するため、アプリを再起動しても並び順が保持されます。Firestore のリアルタイム同期により、他のデバイスにも変更が即座に反映されます。
まとめ:個人開発 PWA に Firebase が最適な理由
今回の記事で紹介したポイントをまとめます。
- Firebase Auth・Firestore・Cloud Functions・Hosting をフル活用し、インフラ運用コストをほぼゼロに
- 大量データ(バス停 1,252件)はバンドルに持たせることで Firestore コスト削減とオフライン対応を両立
- @dnd-kit + Firestore のリアルタイム同期でデバイスをまたいだ並び順共有を実現
- タップした瞬間の時刻で URL を動的生成し、常に最新の時刻表を起点にした検索が可能
次回は、このアプリの Firestore データ設計と Firebase Auth の実装を詳しく解説します。users/{uid}/favoriteRoutes コレクションのスキーマ設計や、メール認証フローの実装ポイントなど、Firebase を使う上で参考になる設計を紹介します。






ディスカッション
コメント一覧
まだ、コメントがありません