如何找到你的ip與local
判斷使用者 Locale / IP
TL;DR三種方法:navigator.language(瀏覽器設定,最簡單)、Geolocation API(經緯度,要使用者授權)、IP 地理查詢(server-side / 第三方 API,最隱蔽)。2026 年 SSR / Edge runtime 推薦走 Accept-Language header + Cloudflare CF-IPCountry。
三種方法對比
| 方法 | 精準度 | 需要授權 | 適合 |
|---|---|---|---|
navigator.language | 中(==語言偏好不一定 = 地點==) | ❌ 不需要 | 預設語言判斷 |
| Geolocation API | 超精準(GPS) | ✅ 使用者要同意彈窗 | 地圖 / 在地服務 |
| IP 地理查詢 | 中-高(會被 VPN 騙) | ❌ 不需要 | 區域內容 / 法規合規 |
Accept-Language header | 同 navigator.language | ❌ 不需要 | SSR / Edge |
Cloudflare CF-IPCountry | 中-高 | ❌ 不需要 | 走 CF 的網站 |
方法 1:navigator.language(最簡單)
navigator.language // "zh-TW"
navigator.languages // ["zh-TW", "en-US", "ja"](優先順序)
格式:BCP 47 語言標籤:
language[-script][-region]
zh-TW 中文-臺灣
zh-Hans-CN 中文-簡體-中國
en-US 英文-美國
ja 日文(無地區)
function getInitialLocale(): string {
if (typeof window === 'undefined') return 'zh-TW'; // SSR fallback
const browserLang = navigator.language;
// 對應到我們支援的 locale
if (browserLang.startsWith('zh')) return 'zh-TW';
if (browserLang.startsWith('ja')) return 'ja';
return 'en';
}
不等於地理位置在台灣的人 可能設成 en-US(工程師習慣英文 OS),用 navigator.language 判 = 顯示英文 — 不一定是他要的。實務上 給使用者 手動切換的選項,navigator.language 只當 預設值。
方法 2:Geolocation API(精準經緯度)
function getLocation(): Promise<GeolocationPosition> {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject(new Error('Geolocation not supported'));
return;
}
navigator.geolocation.getCurrentPosition(
resolve,
reject,
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
},
);
});
}
// 使用
try {
const pos = await getLocation();
console.log(`Lat: ${pos.coords.latitude}, Lng: ${pos.coords.longitude}`);
// 接下來呼叫 reverse geocoding API 把座標轉成國家
} catch (err) {
console.warn('User denied or error:', err);
}
Permission 流程處理:
async function getLocationWithPermission() {
const result = await navigator.permissions.query({ name: 'geolocation' });
switch (result.state) {
case 'granted':
return await getLocation(); // 已授權
case 'prompt':
return await getLocation(); // 觸發授權彈窗
case 'denied':
throw new Error('Permission denied');
}
}
經緯度 → 國家(Reverse Geocoding)
async function coordsToCountry(lat: number, lng: number): Promise<string> {
// Nominatim(OpenStreetMap)免費
const res = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`
);
const data = await res.json();
return data.address?.country_code; // "tw" / "us" / "jp"
}
其他選擇:Google Geocoding API(更準但要錢)、Mapbox Geocoding。
方法 3:IP 地理查詢
Client-side 第三方 API(免費):
async function getCountryByIP(): Promise<string> {
const res = await fetch('https://ipapi.co/json/');
const data = await res.json();
return data.country_code; // "TW" / "US"
}
| API | 免費額度 | 備註 |
|---|---|---|
| ipapi.co | 1000 req/天 | 簡潔好用 |
| ip-api.com | 45 req/分鐘 | 免費,但 production 要 https 付費 |
| ipinfo.io | 50K req/月 | 商業級 |
| geolocation-db.com | 免費 | 老牌 |
CORS / Rate LimitClient-side 直接 call 第三方 API 容易被 rate limit(訪客一多就爆)。建議走 server-side proxy:
// app/api/geo/route.ts(Next.js)
export async function GET(req: Request) {
const ip = req.headers.get('x-forwarded-for') ?? '';
const res = await fetch(`https://ipapi.co/${ip}/json/`);
const data = await res.json();
return Response.json({ country: data.country_code });
}
方法 4:Server-side(SSR / Edge)— 推薦
Accept-Language Header
// Next.js middleware.ts
export function middleware(request: NextRequest) {
const accept = request.headers.get('accept-language') ?? '';
const lang = accept.split(',')[0].trim(); // "zh-TW"
const country = lang.split('-')[1] ?? 'TW';
// ... 用這個資料 redirect / 設 cookie
}
Cloudflare CF-IPCountry(免費,自動)
如果你的網站走 Cloudflare CDN,Cloudflare 自動加 header:
export function middleware(request: NextRequest) {
const country = request.headers.get('cf-ipcountry'); // "TW"
// 完全免費,不用打第三方 API
}
這是 2026 年最划算的做法:精準、免費、快、無 rate limit。
Vercel @vercel/functions(Vercel 平台)
import { geolocation } from '@vercel/functions';
export function GET(req: Request) {
const { country, city } = geolocation(req);
return Response.json({ country, city });
}
Vercel Edge Function 內建,跟 Cloudflare 差不多概念。
各方法的隱私 / 法規
| 方法 | GDPR 友善? | 用戶感知 |
|---|---|---|
navigator.language | ✅ 完全 OK | 無感 |
| Accept-Language | ✅ 完全 OK | 無感 |
| IP 地理 | ⚠️ 算個資,需告知 | 無感 |
| Geolocation API | ✅ 因為要授權 | 有彈窗,清楚 |
| Cloudflare CF-IPCountry | ⚠️ 同 IP 地理 | 無感 |
在台灣 / 美國IP 地理查詢通常不算高敏感個資,但 GDPR 區域(EU)要在 隱私政策列出。Termly(Termly 第三方cookie教學)可以幫你管。
實戰範例:多語站自動跳轉
// middleware.ts(Next.js Edge)
import { NextRequest, NextResponse } from 'next/server';
const SUPPORTED_LOCALES = ['zh-TW', 'en', 'ja'];
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// 已經有 locale 前綴就不動
if (SUPPORTED_LOCALES.some((l) => pathname.startsWith(`/${l}`))) {
return NextResponse.next();
}
// 用 Cloudflare(若有)或 Accept-Language 偵測
const country = request.headers.get('cf-ipcountry');
const accept = request.headers.get('accept-language') ?? '';
let locale = 'en'; // 預設
if (country === 'TW' || country === 'HK') locale = 'zh-TW';
else if (country === 'JP') locale = 'ja';
else if (accept.includes('zh')) locale = 'zh-TW';
else if (accept.includes('ja')) locale = 'ja';
// 重導
const url = request.nextUrl.clone();
url.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(url);
}
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)'],
};
最後 給使用者一個 手動切換 UI,別假設機器永遠對。