Claude CodeでGeolocation APIを安全に実装する完全ガイド
位置情報APIをClaude Codeで安全に実装。権限UX、HTTPS、fallback、テストまで解説。
位置情報は便利ですが、ユーザーにとってはかなり敏感な情報です。近くの店舗を出すだけの機能でも、実装を間違えると「なぜ今ここで位置情報を求めるのか」が伝わらず、許可拒否、離脱、問い合わせにつながります。
Claude Codeに「現在地を取るボタンを作って」とだけ頼むと、getCurrentPositionを呼ぶだけの実装になりがちです。公開サービスではそれでは足りません。HTTPS、権限の説明、拒否時の手入力、タイムアウト、地図SDKとの責務分離、ログの匿名化、テスト用の位置モックまでを最初から設計に入れる必要があります。
この記事では、ブラウザ標準のGeolocation APIをClaude Codeで実装する実務手順をまとめます。一次情報として MDN Geolocation API、getCurrentPosition、watchPosition、Permissions API、W3C Geolocation仕様、Chromeの安全なオリジン要件、Chrome DevTools Sensors、Playwrightのemulation、Claude Code permissionsを確認しています。地図UI側は Claude Code地図連携ガイド、セキュリティ全体は Claude Codeセキュリティ監査、モバイル表示は レスポンシブデザインも合わせて読むとつながります。
先に決めること
Geolocation APIは、ブラウザに「この端末の位置を教えてほしい」と依頼するAPIです。位置の取得元はGPS、Wi-Fi、携帯基地局、IPアドレス、端末設定などで、アプリ側が直接選べるものではありません。W3C仕様も、APIが実際の位置を保証するものではないと説明しています。
初心者が最初に押さえるべき判断は、次の4つです。
| 判断 | 推奨 | 理由 |
|---|---|---|
| いつ許可を求めるか | 機能を使う直前 | ページ表示直後の許可要求は拒否されやすい |
| 精度 | 初期値は高精度OFF | GPS高精度は遅く、電池消費も大きい |
| 失敗時 | 郵便番号や住所入力を必ず用意 | 拒否、圏外、企業端末の制限に耐える |
| 保存 | 原則は保存しない | 緯度経度は個人情報に近い扱いで設計する |
「近くの店舗を探す」なら市区町村レベルでも十分な場合があります。「配送員の走行軌跡」ならwatchPositionが必要ですが、常時追跡ではなく開始と停止を明示します。「不正検知」なら座標そのものではなく、国、都道府県、距離帯のような粗い特徴量で足りないかを検討します。
具体的なユースケース
1つ目は店舗検索です。飲食店、クリニック、コワーキングスペース、イベント会場の「近くを探す」機能では、現在地を中心に候補を並べます。ただし「現在地を使う」ボタンの横に「駅名・郵便番号で探す」を置くのが重要です。許可しないユーザーも自然に進めます。
2つ目は配送や出張サービスの到着見込みです。ユーザーの位置と担当者の位置を比べて、配達範囲、追加料金、最短到着枠を出します。この場合も、ユーザーの正確な移動履歴を保存する必要はありません。計算に使ったあと、粗いエリアや判定結果だけを残す設計にできます。
3つ目は現場作業のチェックインです。メンテナンス、清掃、イベント運営、営業訪問では、作業開始時に「対象地点から何メートル以内か」を確認できます。ここで大事なのは、位置が取れなかったときに作業を止めないことです。写真、管理者承認、手入力メモなどの代替フローを用意します。
4つ目は地域別コンテンツです。天気、自治体のお知らせ、近隣イベント、在庫のある店舗などを出せます。ただし、記事や広告の出し分けだけで正確な位置を求めるのは過剰です。IPベースの粗い地域判定や手入力の地域設定で足りるなら、そちらを優先します。
getCurrentPositionの最小実装
下のHTMLは1ファイルで動くサンプルです。ローカルではlocalhostで試し、本番ではHTTPSで配信してください。file://や通常のHTTPではブラウザによって位置情報が拒否されます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Geolocation demo</title>
<style>
body {
font-family: system-ui, sans-serif;
line-height: 1.6;
margin: 2rem;
}
button,
input {
font: inherit;
padding: 0.7rem 0.9rem;
}
.panel {
border: 1px solid #ddd;
max-width: 36rem;
padding: 1rem;
}
</style>
</head>
<body>
<main class="panel">
<h1>近くの店舗を探す</h1>
<p>
現在地は店舗検索だけに使います。
正確な住所や移動履歴は保存しません。
</p>
<button id="useLocation" type="button">現在地を使う</button>
<p id="status" role="status" aria-live="polite"></p>
<pre id="result"></pre>
<form id="manualForm">
<label for="postcode">郵便番号または駅名</label>
<input id="postcode" name="postcode" autocomplete="postal-code" />
<button type="submit">手入力で探す</button>
</form>
</main>
<script type="module">
const status = document.querySelector("#status");
const result = document.querySelector("#result");
const button = document.querySelector("#useLocation");
const form = document.querySelector("#manualForm");
function showManual(reason) {
status.textContent =
`${reason}。郵便番号または駅名でも検索できます。`;
}
function onSuccess(position) {
const { latitude, longitude, accuracy } = position.coords;
status.textContent = "現在地を取得しました。";
result.textContent = JSON.stringify(
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracyMeters: Math.round(accuracy),
},
null,
2,
);
}
function onError(error) {
const messages = {
1: "位置情報の許可が拒否されました",
2: "端末の位置を判定できませんでした",
3: "位置情報の取得がタイムアウトしました",
};
showManual(messages[error.code] ?? "位置情報を取得できません");
}
button.addEventListener("click", () => {
if (!("geolocation" in navigator)) {
showManual("このブラウザはGeolocation APIに対応していません");
return;
}
status.textContent = "位置情報の許可を確認しています...";
navigator.geolocation.getCurrentPosition(onSuccess, onError, {
enableHighAccuracy: false,
timeout: 8000,
maximumAge: 60000,
});
});
form.addEventListener("submit", (event) => {
event.preventDefault();
const data = new FormData(form);
status.textContent =
`「${data.get("postcode")}」を基準に検索します。`;
});
</script>
</body>
</html>
enableHighAccuracyは「できるだけ高精度にする」依頼であり、必ずGPSになるわけではありません。店舗検索の初回はfalseで十分です。timeoutは待ち時間の上限、maximumAgeはキャッシュされた位置情報を何ミリ秒まで許容するかです。毎回maximumAge: 0にすると新鮮な位置を取りやすい一方で、取得が遅くなります。
ReactでwatchPositionを扱う
watchPositionは移動に合わせて位置を受け取り続けるAPIです。便利ですが、停止処理を忘れると画面を離れても追跡が続き、電池消費や不要なログにつながります。ReactではclearWatchを必ずクリーンアップに入れます。
import { useEffect, useRef, useState } from "react";
type LocationPoint = {
lat: number;
lng: number;
accuracy: number;
at: string;
};
export function TrackingPanel() {
const watchId = useRef<number | null>(null);
const [points, setPoints] = useState<LocationPoint[]>([]);
const [error, setError] = useState<string | null>(null);
function start() {
if (!navigator.geolocation || watchId.current !== null) return;
watchId.current = navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude, accuracy } = position.coords;
setPoints((current) => [
{
lat: Number(latitude.toFixed(5)),
lng: Number(longitude.toFixed(5)),
accuracy: Math.round(accuracy),
at: new Date(position.timestamp).toISOString(),
},
...current.slice(0, 9),
]);
},
(err) => {
setError(`追跡できません: ${err.code}`);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 5000,
},
);
}
function stop() {
if (watchId.current === null) return;
navigator.geolocation.clearWatch(watchId.current);
watchId.current = null;
}
useEffect(() => stop, []);
return (
<section>
<button type="button" onClick={start}>追跡開始</button>
<button type="button" onClick={stop}>停止</button>
{error && <p role="alert">{error}</p>}
<ol>
{points.map((point) => (
<li key={point.at}>
{point.lat}, {point.lng}
{" / "}
{point.accuracy}m
</li>
))}
</ol>
</section>
);
}
移動ログを保存する場合は、業務上の必要性、保存期間、削除方法を先に決めます。たとえば現場チェックインなら「開始時と終了時だけ」で足りることが多いです。1秒ごとの軌跡は実装が派手に見えますが、レビュー、問い合わせ、漏えい時の負担が一気に増えます。
プライバシーとログ設計
緯度経度をそのままログに出すのは避けます。例外ログ、分析イベント、検索パラメータ、サーバーアクセスログ、サポート用スクリーンショットに残ると、意図せず位置データを長期保存することになります。
type GeoLogInput = {
lat: number;
lng: number;
accuracy: number;
permission: "granted" | "prompt" | "denied" | "unknown";
};
export function toPrivacySafeGeoLog(input: GeoLogInput) {
return {
permission: input.permission,
accuracyBucket:
input.accuracy <= 50 ? "high" :
input.accuracy <= 500 ? "medium" : "low",
latBucket: Number(input.lat.toFixed(2)),
lngBucket: Number(input.lng.toFixed(2)),
};
}
console.info("geo_request_finished", toPrivacySafeGeoLog({
lat: 35.681236,
lng: 139.767125,
accuracy: 42,
permission: "granted",
}));
小数点2桁の緯度経度は数キロ単位の粗い位置になります。サービスによってはそれでも細かすぎます。広告計測やA/Bテストに使うなら、座標ではなく「店舗候補を表示できた」「手入力fallbackを使った」「タイムアウトした」のようなイベントで十分なことが多いです。
権限状態を先に見る場合はPermissions APIを使えます。ただしpromptは「まだ許可も拒否もされていない」状態で、実際の許可ダイアログはgetCurrentPositionやwatchPositionを呼んだ時に出ます。ブラウザ差もあるため、表示文言の切り替え程度に留めるのが実務的です。
export async function readGeoPermission() {
if (!("permissions" in navigator)) return "unknown";
try {
const status = await navigator.permissions.query({
name: "geolocation",
});
return status.state;
} catch {
return "unknown";
}
}
地図連携の境界
Geolocation APIは「座標を取る」だけです。地図タイルを表示する、住所を座標に変換する、経路を計算する、周辺施設を検索する、といった処理はGoogle Maps Platform、Mapbox、OpenStreetMap系サービス、サーバー側の住所データなど別の責務です。
境界を曖昧にすると、Claude Codeがブラウザの位置取得コードに地図APIキーを直書きしたり、クライアントからジオコーディングAPIを大量に叩いたりします。安全な依頼は「現在地取得コンポーネントは座標だけを親に返す。地図SDKの初期化、APIキー、サーバー検索は既存の地図層に閉じ込める」です。
export type BrowserLocation = {
lat: number;
lng: number;
accuracy: number;
};
export function getBrowserLocation(): Promise<BrowserLocation> {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error("Geolocation is not supported"));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
lat: position.coords.latitude,
lng: position.coords.longitude,
accuracy: position.coords.accuracy,
});
},
reject,
{
enableHighAccuracy: false,
timeout: 8000,
maximumAge: 60000,
},
);
});
}
この関数は地図ライブラリを知りません。地図側はBrowserLocationを受け取り、中心位置を変えるだけにします。こうしておくと、現在地取得のテスト、地図表示のテスト、APIキー管理を別々に扱えます。
よくある失敗例
最初の失敗はHTTP配信です。Chromeは保護されていないオリジンからのGeolocation APIを制限しており、MDNも安全なコンテキストが必要だと説明しています。開発中はhttp://localhostで動いても、本番の一部サブドメインや埋め込みiframeがHTTP混在だと失敗します。
2つ目は権限拒否を例外扱いすることです。拒否は正常なユーザー選択です。「位置情報を許可してください」と何度も迫るより、「郵便番号で検索できます」と次の導線を出す方が信頼されます。
3つ目はwatchPositionの停止忘れです。配送や移動記録で実装したあと、画面遷移時のclearWatchが漏れると、電池消費と不要なデータ処理が残ります。ReactならuseEffectのcleanup、通常JSなら「停止」ボタンとページ離脱時の処理を用意します。
4つ目は地図SDKとの混同です。Geolocation APIは無料の地図APIではありません。住所検索、逆ジオコーディング、経路、地図表示には別サービスが必要で、APIキー制限、課金、キャッシュルールを確認します。
5つ目はログ漏えいです。console.log(position)、Sentryの追加コンテキスト、アクセス解析イベントに緯度経度を入れると、想定外の保管先が増えます。Claude Codeには「座標をログ出力しない。必要なら粗いbucketだけにする」と明示してください。
テストとモック
手動確認ではChrome DevToolsのSensorsパネルが便利です。位置をTokyo、Berlin、Custom location、Location unavailableに切り替え、成功、拒否、取得不能、手入力fallbackを確認します。
E2EではPlaywrightのgeolocationとpermissionsを使うと安定します。
import { expect, test } from "@playwright/test";
test.use({
geolocation: {
latitude: 35.681236,
longitude: 139.767125,
accuracy: 50,
},
permissions: ["geolocation"],
});
test("shows nearby stores from mocked location", async ({ page }) => {
await page.goto("/stores");
await page.getByRole("button", { name: "現在地を使う" }).click();
await expect(page.getByText("現在地を取得しました")).toBeVisible();
});
拒否のテストも必要です。権限を与えない状態でボタンを押し、手入力フォームが表示されることを確認します。位置情報は端末、OS設定、ブラウザ、企業ポリシー、iframeのPermissions-Policyに左右されるため、「成功するケース」だけでは品質保証になりません。
Claude Codeへの安全な依頼
Claude Codeには実装範囲と禁止事項を先に渡します。特に位置情報は「動けばOK」ではなく、データを残さない、許可拒否を尊重する、地図APIキーに触らない、テストを追加する、という制約が重要です。
claude <<'PROMPT'
Implement a beginner-friendly Geolocation feature.
Scope:
- Edit only src/features/location and related tests.
- Do not change billing, analytics, or map provider config.
- Preserve existing API keys and environment variable names.
Requirements:
- Request location only after the user clicks a button.
- Explain why location is needed before the browser prompt.
- Use getCurrentPosition with timeout and maximumAge.
- Add manual postcode/address fallback for denied or timeout cases.
- Do not log raw latitude or longitude.
- Add a Playwright test with mocked geolocation.
- Return a short verification checklist.
PROMPT
チームでClaude Codeを使うなら、.claude/settings.jsonで危険な読み取りやpushを制限する運用も検討します。公式docsのpermission ruleはTool(specifier)形式です。
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(git push *)"
],
"allow": [
"Bash(npm test *)",
"Bash(npm run lint)"
]
}
}
位置情報機能は、プロダクトの信頼に直結します。Claude Codeに作らせるほど、プロンプト側で「何を保存しないか」「どの失敗を正常系にするか」「どこまで地図層に任せるか」を指定してください。Claude Code導入や社内レビュー基準を整えたい場合は、Claude Code研修・相談で、既存コードを前提にした実装プロンプトとチェックリスト化まで支援しています。
実際に試した結果
この記事のサンプルは、Windows上のChromeでlocalhost配信、DevTools SensorsのTokyo指定、Location unavailable、Playwrightのmock位置を使って確認しました。成功時は座標が5桁に丸めて表示され、取得不能時は手入力フォームに誘導されます。実案件で使う場合は、OS側の位置情報設定をOFFにしたケース、企業端末でブラウザ権限が固定されたケース、iframe埋め込みでPermissions-Policyにブロックされたケースも追加で確認してください。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。