Watch-IP

Geolocalización de IP en Next.js: patrones de navegador y servidor

Next.js puede renderizar la misma página en el servidor o en el navegador, y esa elección cambia qué endpoint de Watch-IP resuelve realmente la ubicación del visitante, o resuelve la de tu servidor en su lugar.

Antes de empezar

  • Una clave de visitante publicable para el patrón de cliente, o una clave secreta de lookup para el patrón de servidor: consulta autenticación.
  • Un proyecto de Next.js con App Router (los ejemplos asumen la separación entre Server y Client Component de Next.js 16).
  • Para el patrón de servidor, un encabezado que tu plataforma de hosting configure en las solicitudes entrantes con la IP real del visitante (por ejemplo, CF-Connecting-IP en Cloudflare), no uno que un cliente pudiera establecer por sí mismo.

1. Entiende qué llama realmente cada patrón

GET /v1/geo identifica a quien sea que la conexión llegue a la API — el navegador de un visitante llamándolo directamente, o tu servidor de Next.js si tu propio código lo llama durante el renderizado en servidor. Llamar a GET /v1/geo desde un Server Component o un Route Handler resuelve la ubicación de red de tu servidor, no la del visitante, lo cual normalmente no es lo que quieres. Dos patrones sí resuelven la del visitante: ejecutar la solicitud en el navegador (Client Component), o ejecutarla en tu servidor contra una IP proporcionada con GET /v1/lookup/{ip}.

2. Client Component: llama a GET /v1/geo desde el navegador

Esta es la misma solicitud que usan las guías de JavaScript y React: un Client Component que hace fetch con una clave publicable. El propio encabezado Origin del navegador satisface la verificación de orígenes permitidos de la clave, así que la URL pública de tu app debe estar en esa lista.

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 ?? "tu zona"}</span>;
}

3. Server Component o Route Handler: consulta explícitamente la IP del visitante

Para resolver la ubicación de un visitante durante el renderizado en servidor, no llames a GET /v1/geo: llama a GET /v1/lookup/{ip} con una clave secreta y la IP real del visitante, leída del encabezado que tu plataforma de hosting configure en la solicitud entrante. Nunca confíes en un valor simple de X-Forwarded-For que un cliente podría establecer por sí mismo, a menos que tu plataforma lo elimine y lo vuelva a establecer en el edge.

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 ?? "tu zona"}</span>;
}

4. No almacenes en caché el resultado de un visitante para otro

cache: 'no-store' arriba es deliberado: un resultado de lookup es específico del visitante que lo solicitó. Si este componente se renderiza dentro de una ruta que Next.js o una CDN pueden cachear —una página estática, una página ISR, una caché de fetch compartida—, una respuesta en caché mostraría la ciudad de un visitante a todos los demás después de eso. Mantén la rama personalizada renderizada de forma dinámica, y nunca incluyas una respuesta de lookup en una caché a nivel de página o de CDN.

Errores y casos límite

Un encabezado de IP faltante o vacío (algunas configuraciones locales o de proxy no establecen ninguno) significa que no hay ninguna IP de visitante que consultar: renderiza la alternativa en lugar de llamar al endpoint con una cadena vacía. Un 403 de GET /v1/lookup/{ip} normalmente significa que se usó una clave publicable donde la ruta necesita una clave secreta; un 400 significa que el encabezado contenía algo que no es una dirección IPv4/IPv6 válida. Consulta errores y límites de tasa.

Siguiente paso

Para un componente que puedas reutilizar tanto en apps de React solo de cliente como en Next.js, consulta la guía de React: el patrón de Client Component de arriba es un envoltorio específico de Next.js sobre la misma solicitud.

Páginas relacionadas