Watch-IP

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 专属封装。

相关页面