Google Maps API とNuxtで社内向けの情報集約マップを作る連載の最終回(第12回)です。前回(第11回)では、既存の台帳をCSVで一括取り込みできるようにしました。最終回は、ここまで作ったアプリを社内に公開する前に確認すべきことを、APIキーの制限・費用の上限・地図の設定・性能・本番データベース・データの保存期間の順に点検し、連載全体を締めくくります。
「動くものはできた。でも、公開した途端に想定外の請求が来たり、キーを悪用されたりしないか心配…」
結論から言うと、Google Maps Platform を使うシステムの運用費用とリスクは、「キーの制限」「APIごとの利用上限(クォータ)」「予算アラート」の3点セットでかなりの部分を仕組みで抑えられます。ただし、予算アラートは通知だけで利用は止まりません。上限を本当に止めるのはクォータです。この3点が見積もりや設計書に入っていない場合は、公開前に必ず確認してください。
この回で点検すること
| 分類 | 点検すること | サンプルでの対応 |
|---|---|---|
| APIキー | ブラウザ用・サーバー用のキーの分離と制限、Referer の送り方 | nuxt.config.ts に Referrer-Policy を追加 |
| 費用の上限 | APIごとのクォータ、予算アラート | Cloud コンソールで設定(手順を README に記載) |
| 地図の設定 | Map ID を開発用から本番用へ | 環境変数で差し替え |
| 性能 | 実際の端末・想定件数での計測 | 手順を README に記載 |
| 本番DB | SQLite から PostgreSQL へ | ビルド時の切り替えを実装し、PostgreSQL で動作確認 |
| データの保存期間 | Google 由来の座標の30日制限 | 期限切れ・期限間近の一覧を出すスクリプトを追加 |
サンプルの code/README.md に、この回の内容をまとめた「公開前チェックリスト」を置きました。以下では、その背景と実装を説明します。
1. APIキーの制限を仕上げる
第1回から、キーは2本に分けてきました。公開前に、それぞれの制限を本番用に仕上げます。
| キー | アプリケーションの制限 | APIの制限 |
|---|---|---|
ブラウザ用(NUXT_PUBLIC_GOOGLE_MAPS_API_KEY) | 「ウェブサイト」=本番のオリジン(例: https://map.example.co.jp)だけを登録 | Maps JavaScript API、Places API (New) |
サーバー用(NUXT_GOOGLE_MAPS_SERVER_KEY) | 「IPアドレス」=サーバーの送信元IP(固定できる場合) | Geocoding API |
Google の公式ガイドでは、Maps JavaScript API を使うウェブサイトには「ウェブサイト」の制限、信頼できるサーバーから呼ぶ Web サービスには「IPアドレス」の制限を使うこと、キーは使うAPIだけに絞ること、アプリや用途ごとに別のキーを作ることが推奨されています。開発用の localhost を許可したキーは、本番用とは別にしておくと、開発用キーが漏れても本番に影響しません。
📰 出典:Google Maps Platform「API security best practices」
Referer ヘッダーを送らない設定にしない
ブラウザ用キーの「ウェブサイト」制限は、ブラウザが送る Referer ヘッダー(=どのページからのリクエストかを示す情報)で判定されます。公式ガイドには、最近のブラウザはプライバシー保護のため別サイトへのリクエストでは Referer をオリジン(https://ドメイン の部分)だけに削ること、Referer が完全に削られる環境ではウェブサイト制限付きのキーでリクエストが失敗しうることが書かれています。
そこで、登録するのはパス付きの URL ではなくオリジンにし、サイト側でも Referer を送らない設定にしないよう、ブラウザの既定と同じ値を明示しました。
nuxt.config.ts(追加部分)
// 第12回: 本番公開前の設定。Referer ヘッダーを送らない設定(no-referrer 等)にすると、
// ブラウザ用キーの「ウェブサイト制限」で地図の読み込みが拒否されうるため、ブラウザ既定と同じ値を明示する
routeRules: {
'/**': {
headers: {
'Referrer-Policy': 'strict-origin-when-cross-origin',
'X-Content-Type-Options': 'nosniff',
},
},
},
社内のセキュリティ方針で「Referer は一切送らない」と決まっている場合は、地図のキーの制限方法と衝突します。インフラ担当と事前にすり合わせておきましょう。
2. 費用の上限を「仕組みで」決める
クォータ(利用上限)で止める
Cloud コンソールの「割り当て(Quotas)」ページでは、APIごとに1日・1分あたりのリクエスト数の上限を下げられます。公式ドキュメントでは、クォータの上限は「プロジェクトが送れるリクエスト数に上限を設けることで、想定外の請求を防ぐのに役立つ」と説明されています。一方で、低くしすぎると利用者が地図を使えなくなるとも注意されているので、想定の数倍程度から始めて、実際の利用量を見ながら調整します。
目安は、地図の表示なら「利用者数×1日に開く回数」、住所変換なら「通常の登録件数+CSV取り込みの予定件数」に余裕を持たせた値です。CSV の一括取り込み(第11回)の日だけ上限を一時的に上げる運用も考えられます。
予算アラートは「通知だけ」
請求先アカウントには、予算とアラート(例: 予算の50%・90%・100%で通知)を設定します。ただし公式ドキュメントには、予算を設定しても Google Cloud や Google Maps Platform の利用・請求は自動では止まらないと明記されています。予算アラートは「気づくための仕組み」、クォータは「止めるための仕組み」と役割を分けて考えてください。
📰 出典:Google Maps Platform「Manage your cost of use」
料金体系は2025年3月に大きく変わり、毎月の共通クレジットから、SKU(課金の単位)ごとの無料枠と区分(Essentials / Pro / Enterprise)の方式になりました。それ以前の記事や見積もりの試算はそのまま使えないので、公式の料金ページで最新の単価を確認してください。
📰 出典:Google Maps Platform「March 2025 changes」
3. Map ID を本番用に差し替える
Advanced Markers(第2回)には Map ID が必須で、ここまでは開発用の DEMO_MAP_ID を使ってきました。本番では Cloud コンソールで Map ID を発行し、NUXT_PUBLIC_GOOGLE_MAPS_MAP_ID で渡します。Map ID には地図のスタイル(道路や施設の見せ方)をクラウド側で設定でき、コードを変えずに見た目を調整できます。
本番では node .output/server/index.mjs で起動し、.env は読まれません。キーや Map ID は、サーバーやコンテナの環境変数として設定します(第1回参照)。
Map ID やクラウドでのスタイル設定そのものに追加費用がかかるかは、執筆時点(2026年9月)で公式ドキュメントに明確な記載を確認できませんでした。利用前に料金ページで確認してください。
4. 性能を実際の端末で確かめる
性能は、次の手順で計測して記録しておくと、後から「遅くなった」と言われたときの比較材料になります。
npm run seedで想定件数のダミー地点を入れる(第9回)npm run build→node .output/server/index.mjsで本番と同じビルドを起動する(開発サーバーは本番より遅い)- Chrome DevTools の Lighthouse でモバイル・デスクトップの両方を計測し、Performance と Accessibility の点数を記録する
- 実際に使う端末(社用のスマートフォンやノートPC)で、地図の移動・絞り込み・一覧の操作を試す
この計測は、執筆環境に本物のAPIキーがなく地図が表示できないため行えていません。 地図の読み込み(Google 側のスクリプトやタイル画像)は点数に大きく影響するので、キーを設定した状態で計測してください。
5. 本番データベースを PostgreSQL に切り替える
ここまでの SQLite(Node.js 組み込みの node:sqlite)は、1台のサーバーで動かす試作には十分ですが、複数台での運用やバックアップ・監視の仕組みを考えると、本番では PostgreSQL などのデータベースサーバーを使うのが一般的です。第3回で「SQL は db0 の sql タグ経由に限定する」と決めていたので、切り替えは設定とテーブル作成の2か所で済みます。
nuxt.config.ts(変更部分)
// 第12回: ビルド時に DB_CONNECTOR=postgresql を指定すると PostgreSQL に切り替わる。
// 接続先はビルドに含めず、実行時の環境変数 PGHOST / PGPORT / PGUSER / PGPASSWORD / PGDATABASE
// から読む(pg ライブラリの既定の動作)。パスワードをビルド成果物に残さないため
nitro: {
experimental: {
database: true,
},
database: {
default: process.env.DB_CONNECTOR === 'postgresql'
? { connector: 'postgresql', options: {} }
: { connector: 'node-sqlite', options: { name: 'gmap' } },
},
},
データベースの設定はビルド時に成果物へ書き込まれます。接続先の URL やパスワードをここに書くと、ビルド成果物にパスワードが残ってしまいます。そこで options は空にし、PostgreSQL 用ライブラリ(pg)が実行時の PGHOST などの環境変数を読む既定の動作に任せました。実際にビルド成果物を確認すると、設定は options: {} だけになっていました。
server/plugins/db-init.ts(変更部分・要点)
// 第12回: 連番と小数の型だけ、SQLite と PostgreSQL で書き方が違う
if (db.dialect === 'postgresql') {
await db.sql`CREATE TABLE IF NOT EXISTS points (
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
category TEXT NOT NULL,
lat DOUBLE PRECISION NOT NULL,
lng DOUBLE PRECISION NOT NULL,
-- …(memo, place_id, source, fetched_at, updated_at は SQLite と同じ)
)`
} else {
// …(第3回からの SQLite 用の CREATE TABLE)
}
使い方は次のとおりです(依存関係に pg を追加しています)。
DB_CONNECTOR=postgresql npm run build
PGHOST=... PGUSER=... PGPASSWORD=... PGDATABASE=... node .output/server/index.mjs
筆者の環境では、Docker で起動した PostgreSQL 18 に対してビルド後のサーバーを接続し、テーブルと索引の自動作成、サンプル20件の投入、地点の一覧・表示範囲での検索・登録(201)・更新(200)・削除(204、2回目は404)、CSV の取り込みと重複判定が、SQLite のときと同じ結果になることを curl で確認しました。
ただし、これは「試作をそのまま PostgreSQL で動かせる」ところまでです。本番では、起動時の CREATE TABLE IF NOT EXISTS ではなくマイグレーション(=テーブル構造の変更を履歴として管理する仕組み)を使うこと、SQLite のデータの移行、1本の接続を共有している今の作りを接続プール(複数の接続を使い回す仕組み)に変えることなどが別途必要です。広い範囲の検索や「近い順」の並べ替えが必要なら、PostgreSQL の地理データ拡張(PostGIS)も検討します。
6. Google 由来の座標の保存期間に備える
第6・7回で確認したとおり、Google Maps Platform の利用規約(Service Specific Terms)では、Geocoding API と Places API から得た緯度・経度は連続30日までの一時保存が基本です(Geocoding には例外規定がありますが、複数の利用者で共有する台帳が当てはまるかは慎重な判断が必要です)。一方、place_id は期限なく保存できます。
📰 出典:Google Maps Platform「Service Specific Terms」
本連載では、座標の出どころ(source)と Google から取得した日時(fetched_at)を保存してきました。最終回では、期限を過ぎた・過ぎそうな地点を一覧にするスクリプトを追加しました。
scripts/report-google-coords.ts(要点)
const db = new DatabaseSync('.data/gmap.sqlite', { readOnly: true })
// fetched_at は ISO 8601 の文字列なので、文字列の大小で日時を比べられる
const rows = db.prepare(`SELECT id, name, source, place_id, fetched_at FROM points
WHERE source IN ('geocoding', 'places') AND fetched_at IS NOT NULL AND fetched_at < ?
ORDER BY fetched_at`).all(warnAt) as {
id: number; name: string; source: string; place_id: string | null; fetched_at: string
}[]
const expired = rows.filter((r) => r.fetched_at < limitAt)
const soon = rows.filter((r) => r.fetched_at >= limitAt)
// …(件数と一覧の表示は省略)
// 期限切れがあれば終了コード 1(定期実行の仕組みから通知しやすくするため)
process.exit(expired.length > 0 ? 1 : 0)
npm run report:google-coords で実行します。検証用のデータ(取得から31日・27日・3日の Google 由来の地点と、100日前の手動登録の地点)で試すと、「期限切れ1件・期限間近1件」と表示され、手動登録の地点は対象外になりました。
このスクリプトは一覧を出すだけで、データは変更しません。期限を過ぎた座標を place_id から再取得するのか、座標を消して「要確認」にするのか、利用者が地図上で位置を確認し直した座標を自社データとして扱ってよいのかは、規約の解釈が絡むため、法務確認のうえで運用として決める必要があります。再取得にするなら、その分のAPI費用も運用費に含めます。
コラム:vue3-google-map との比較
本連載では、Google 公式の @googlemaps/js-api-loader を薄い composable で包む方式を選びました。Vue 向けには、<GoogleMap> や <AdvancedMarker> のようにコンポーネントとして地図を書ける vue3-google-map(コミュニティ製、執筆時点で 0.27 系)もあります。
| 観点 | 本連載の方式(js-api-loader) | vue3-google-map |
|---|---|---|
| 書き方 | 公式ドキュメントのコードに近い命令的な書き方 | テンプレートに部品を並べる宣言的な書き方 |
| 新機能への追従 | 公式の新しいクラスをすぐ使える | ライブラリ側の対応を待つことがある |
| 大量の地点 | クラスタリングや差分更新を細かく制御しやすい(第9回) | 地点1つ=部品1つになり、数千件では工夫が必要 |
| 向いている場面 | 地点数が多い、公式の最新機能を使う | 地点数が少なく、画面を素早く作りたい |
どちらが正解というより、地点数と求める機能で選ぶものです。小さな社内ツールなら、vue3-google-map のほうが早く作れる場合もあります。
動作確認の方法
npm run build→node .output/server/index.mjsを起動し、curl -I http://localhost:3000/でReferrer-Policy: strict-origin-when-cross-originが返ることを確認- ブラウザ用キーの「ウェブサイト」制限に
http://localhost:3000だけを登録し、http://127.0.0.1:3000で開くと地図が拒否される(第1回のエラー表示が出る)ことを確認 - Cloud コンソールでクォータと予算アラートを設定し、設定画面を記録に残す
DB_CONNECTOR=postgresqlでビルドし、PostgreSQL に接続して地点の登録・表示ができることを確認npm run report:google-coordsを実行し、Google 由来の地点の件数を確認- 本番と同じビルドで Lighthouse を計測し、点数を記録
筆者の環境では、npm run build と npm run typecheck の成功、手順1のヘッダー、手順4(PostgreSQL 18)、手順5(検証用データ)を確認しました。手順2のリファラー制限の体感、手順3の Cloud コンソールでの設定、手順6の Lighthouse 計測は、執筆環境に本物のAPIキーとプロジェクトがないため確認できていません。 ご自身のプロジェクトで確認してください。
本番前に残っている作業
このサンプルは社内の試作(PoC)向けで、本番運用には次の作業が残っています。
| 残っている作業 | 内容 |
|---|---|
| 認証・権限 | ログイン、閲覧のみ/編集可の区別、誰がいつ何を変えたかの記録 |
| Google 由来の座標の定期処理 | fetched_at から30日を超えた座標を再取得または削除するバッチ(今回は一覧まで) |
| 法務確認 | 共有台帳での座標の保存、利用者が確認した座標の扱い、地図以外での結果表示の可否 |
| マイグレーション | テーブル構造の変更履歴の管理(今は起動時に作成する簡易版) |
| CSV 取り込みのジョブ化 | 大量の住所変換を裏側で順に処理し、進捗を表示する |
| 運用基盤 | HTTPS、監視、ログ、バックアップ、障害時の連絡体制 |
発注者向けメモ
- 「キーの制限」「クォータ」「予算アラート」の3点セットが見積もりや設計書に入っているかを確認してください。どれか1つでも欠けていると、キーの悪用や操作ミスで想定外の請求が起きたときに止められません
- 予算アラートは通知だけで、利用は止まりません。上限を止めるのはクォータです。通知先には開発会社だけでなく、発注側の担当者も入れておきましょう
- GCP プロジェクトと請求先アカウントは発注側の名義で(第1回)。料金体系は2025年3月に大きく変わっているので、古い試算が使われていないかも確認しましょう
- Google 由来の座標の保存期間は法務確認事項です。定期的な再取得にするなら、その費用も運用費に含めて見積もってもらいます
- 性能の受け入れ基準は「どの端末・何件で・何秒以内」という形で決めておくと、検収時の議論が短くなります
発注者がやることのチェックリストです。
- ☐ GCP プロジェクト・請求先アカウントが自社の名義になっている
- ☐ ブラウザ用・サーバー用のキーが分かれ、それぞれ制限されていることを確認した
- ☐ APIごとのクォータと予算アラートが設定され、通知先に自社の担当者が入っている
- ☐ 本番の Map ID とデータベースが用意されている
- ☐ Google 由来の座標の保存期間について法務確認を依頼した
- ☐ 性能の受け入れ基準(端末・件数・時間)を決めた
開発会社への確認に使える質問例です。
- 「キーが漏れた場合、どこまで被害が出て、どう止めますか?」
- 「APIごとのクォータはいくつに設定しますか?その根拠は?」
- 「予算アラートの通知先と、通知が来たときの対応手順を教えてください」
- 「住所変換や施設検索で得た座標は、利用規約上どのように保存・更新しますか?」
連載の全回一覧
| 回 | タイトル |
|---|---|
| 第1回 | APIキーとMap IDを用意して、Nuxt 4で地図を1枚表示する |
| 第2回 | Advanced Markersでカテゴリ別の色付きピンを立てる |
| 第3回 | 地点データをNuxt server API + SQLiteに保存する |
| 第4回 | マーカーをクリックしてInfoWindowと詳細パネルを出す |
| 第5回 | 地図をクリックして新しい地点を登録する |
| 第6回 | 住所を入力して緯度経度に変換する(Geocoding API v4をサーバー経由で) |
| 第7回 | 施設名で検索して地点を取り込む(Places API (New)) |
| 第8回 | カテゴリで絞り込み、一覧と地図を連動させる |
| 第9回 | マーカーが数千件でも重くしない:クラスタリングと表示範囲読み込み |
| 第10回 | 現在地から半径◯km/描いたエリア内の地点を探す |
| 第11回 | CSVで既存の台帳を一括取り込みする |
| 第12回 | 本番公開前チェック:APIキー制限・クォータ・予算アラート・性能(この記事) |
まとめ:連載を振り返って
最終回では、公開前にAPIキーの制限、クォータと予算アラート、Map ID、性能、本番データベース、Google 由来の座標の保存期間を点検しました。キーの制限・クォータ・予算アラートの3点セットで費用とリスクを仕組みで抑えること、予算アラートは止める仕組みではないこと、座標の出どころと取得日時を残して規約に備えることが押さえどころです。
全12回を通して、地図の表示から始めて、ピン・保存・詳細表示・登録・住所変換・施設検索・絞り込み・大量データ・エリア検索・一括取り込みと、1回1機能ずつ積み上げてきました。どの回でも意識してきたのは、「その操作で Google の課金が発生するか」「そのデータを保存してよいか」を機能ごとに確認することです。地図のシステムは、画面の見た目以上に、費用と規約の設計が品質を左右します。開発会社と話すときや、自社で小さく作るときの手がかりになればうれしいです。
この連載の記事一覧
この記事は連載「NuxtとGoogle Mapsで作る情報集約マップ」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【NuxtとGoogle Mapsで作る情報集約マップ 第1回】APIキーとMap IDを用意して、Nuxt 4で地図を1枚表示する
- 【NuxtとGoogle Mapsで作る情報集約マップ 第2回】Advanced Markersでカテゴリ別の色付きピンを立てる
- 【NuxtとGoogle Mapsで作る情報集約マップ 第3回】地点データをNuxt server API + SQLiteに保存する
- 【NuxtとGoogle Mapsで作る情報集約マップ 第4回】マーカーをクリックしてInfoWindowと詳細パネルを出す
- 【NuxtとGoogle Mapsで作る情報集約マップ 第5回】地図をクリックして新しい地点を登録する
- 【NuxtとGoogle Mapsで作る情報集約マップ 第6回】住所を入力して緯度経度に変換する(Geocoding API v4をサーバー経由で)
- 【NuxtとGoogle Mapsで作る情報集約マップ 第7回】施設名で検索して地点を取り込む(Places API (New))
- 【NuxtとGoogle Mapsで作る情報集約マップ 第8回】カテゴリで絞り込み、一覧と地図を連動させる
- 【NuxtとGoogle Mapsで作る情報集約マップ 第9回】マーカーが数千件でも重くしない:クラスタリングと表示範囲読み込み
- 【NuxtとGoogle Mapsで作る情報集約マップ 第10回】現在地から半径◯km/描いたエリア内の地点を探す
- 【NuxtとGoogle Mapsで作る情報集約マップ 第11回】CSVで既存の台帳を一括取り込みする
- 【NuxtとGoogle Mapsで作る情報集約マップ 第12回】本番公開前チェック:APIキー制限・クォータ・予算アラート・性能(この記事)









