Next.js 中的 IP 地理位置:浏览器与服务器模式
Next.js 可以在服务器上或在浏览器中渲染同一个页面,而这一选择会决定 Watch-IP 的哪个端点实际解析出访问者的位置——还是解析出您服务器自身的位置。
开始之前
- 客户端模式需要一个公开可用的访问者密钥,服务器端模式需要一个私密查询密钥——参见身份验证。
- 一个使用 Next.js App Router 的项目(示例假定采用 Next.js 16 的服务器组件/客户端组件划分)。
- 对于服务器端模式,需要您的托管平台在入站请求上设置的、携带访问者真实 IP 的请求头(例如 Cloudflare 上的 CF-Connecting-IP)——而不是客户端自己可以设置的请求头。
1. 理解每种模式实际调用的内容
GET /v1/geo 识别的是与 API 建立连接的那一方——可能是直接调用它的访问者浏览器,也可能是您自己的代码在服务器渲染期间调用它时的 Next.js 服务器本身。从服务器组件或路由处理程序中调用 GET /v1/geo,解析出的是您服务器的网络位置,而不是访问者的位置,这通常并不是您想要的结果。真正能够解析出访问者位置的模式有两种:在浏览器中运行请求(客户端组件),或在您的服务器上针对提供的 IP 使用 GET /v1/lookup/{ip}。
2. 客户端组件:从浏览器调用 GET /v1/geo
这与 JavaScript 和 React 指南中使用的请求相同——一个使用公开可用密钥发起 fetch 的客户端组件。浏览器自身的 Origin 请求头会满足该密钥的允许来源校验,因此您应用的公开 URL 需要在该列表中。
TypeScript (Client Component)
"use client";
import { useEffect, useState } from "react";
export function VisitorLocation() {
const [city, setCity] = useState<string | null>(null);
useEffect(() => {
const controller = new AbortController();
fetch("https://api.watch-ip.com/v1/geo", {
headers: { "X-Api-Key": "YOUR_PUBLISHABLE_KEY" },
signal: controller.signal,
})
.then((res) => (res.ok ? res.json() : null))
.then((geo) => setCity(geo?.city ?? null))
.catch(() => {});
return () => controller.abort();
}, []);
return <span>{city ?? "您所在的地区"}</span>;
}3. 服务器组件或路由处理程序:显式查询访问者的 IP
要在服务器渲染期间解析访问者的位置,请不要调用 GET /v1/geo——而是使用私密密钥,针对从入站请求所设置的相应请求头中读取到的访问者真实 IP,调用 GET /v1/lookup/{ip}。除非您的托管平台会在边缘节点剥离并重新设置该请求头,否则切勿信任客户端可以自行设置的普通 X-Forwarded-For 值。
TypeScript (Server Component)
import { headers } from "next/headers";
export async function VisitorLocationServer() {
const ip = (await headers()).get("cf-connecting-ip");
if (!ip) return null;
const response = await fetch(`https://api.watch-ip.com/v1/lookup/${ip}`, {
headers: { Authorization: "Bearer YOUR_SECRET_KEY" },
cache: "no-store",
});
if (!response.ok) return null;
const geo = await response.json();
return <span>{geo.city ?? "您所在的地区"}</span>;
}4. 不要将一个访问者的结果缓存给另一个访问者
上文中的 cache: 'no-store' 是刻意为之的:查询结果是与发起请求的那位访问者绑定的。如果该组件渲染在 Next.js 或 CDN 可以缓存的路由中——例如静态页面、ISR 页面,或共享的 fetch 缓存——被缓存的响应就会在此后把某一位访问者的城市展示给所有人。请保持这一个性化分支为动态渲染,并且切勿将查询响应纳入页面级或 CDN 级的缓存。
错误与边界情况
缺失或为空的 IP 请求头(某些本地或代理环境不会设置该请求头)意味着没有可供查询的访问者 IP——此时应渲染回退内容,而不是用空字符串调用该端点。GET /v1/lookup/{ip} 返回 403 通常意味着在需要私密密钥的路由上使用了公开可用密钥;返回 400 则意味着该请求头中的值不是有效的 IPv4/IPv6 地址。参见错误与速率限制。
下一步
如果您需要一个既能在纯客户端 React 应用中复用、也能在 Next.js 中复用的组件,请参阅 React 指南——上文的客户端组件模式正是围绕同一请求构建的 Next.js 专属封装。