Kamero

IP Geolocation in Serverless & Edge Functions

Edge and serverless functions run close to your users, making them ideal for location-based logic. Many platforms provide built-in geo headers, and for those that don't, a quick API call fills the gap. Here's how to use IP geolocation across the major serverless platforms.

Vercel Edge Middleware

Vercel injects geolocation headers automatically on Edge Runtime. No external API call needed.

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const country = request.geo?.country || "US";
  const city = request.geo?.city || "Unknown";
  const latitude = request.geo?.latitude;
  const longitude = request.geo?.longitude;

  // Add geo data to request headers
  const response = NextResponse.next();
  response.headers.set("x-geo-country", country);
  response.headers.set("x-geo-city", city);

  // Geo-based routing
  if (country === "DE" || country === "AT") {
    const url = request.nextUrl.clone();
    url.pathname = "/de" + url.pathname;
    return NextResponse.rewrite(url);
  }

  return response;
}

export const config = {
  matcher: ["/((?!api|_next|favicon.ico).*)"],
};

Vercel Serverless Function

// app/api/location/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  // Option 1: Use Vercel's built-in headers
  const country = request.headers.get("x-vercel-ip-country");
  const city = request.headers.get("x-vercel-ip-city");

  // Option 2: Call the geo API for full data
  const geo = await fetch("https://geo.kamero.ai/api/geo")
    .then(r => r.json());

  return NextResponse.json({
    vercelHeaders: { country, city },
    apiData: geo,
  });
}

Cloudflare Workers

Cloudflare provides geo data through the cf object on every request.

export default {
  async fetch(request: Request): Promise<Response> {
    // Built-in Cloudflare geo data
    const cf = request.cf;
    const country = cf?.country || "Unknown";
    const city = cf?.city || "Unknown";
    const timezone = cf?.timezone || "UTC";
    const latitude = cf?.latitude;
    const longitude = cf?.longitude;

    // Use for routing decisions
    if (country === "CN") {
      return new Response("This content is not available",
        { status: 451 });
    }

    // Or enrich with external API for more data
    const geo = await fetch("https://geo.kamero.ai/api/geo")
      .then(r => r.json());

    return new Response(JSON.stringify({
      cfData: { country, city, timezone },
      apiData: geo,
    }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

AWS Lambda@Edge

// Lambda@Edge viewer request function
exports.handler = async (event) => {
  const request = event.Records[0].cf.request;

  // CloudFront provides country via headers
  const countryHeader =
    request.headers["cloudfront-viewer-country"];
  const country = countryHeader?.[0]?.value || "US";

  // For full geo data, call the API
  const https = require("https");
  const geo = await new Promise((resolve, reject) => {
    https.get("https://geo.kamero.ai/api/geo", (res) => {
      let data = "";
      res.on("data", (chunk) => data += chunk);
      res.on("end", () => resolve(JSON.parse(data)));
    }).on("error", reject);
  });

  // Add geo headers for downstream processing
  request.headers["x-geo-city"] =
    [{ value: geo.city }];
  request.headers["x-geo-timezone"] =
    [{ value: geo.timezone }];

  return request;
};

Deno Deploy

Deno.serve(async (request: Request) => {
  // Deno Deploy doesn't have built-in geo,
  // so use the API
  const geo = await fetch("https://geo.kamero.ai/api/geo")
    .then(r => r.json());

  // Localized greeting
  const greetings: Record<string, string> = {
    JP: "こんにちは",
    ES: "¡Hola!",
    FR: "Bonjour",
    DE: "Hallo",
  };

  const greeting = greetings[geo.country] || "Hello";

  return new Response(JSON.stringify({
    greeting,
    location: `${geo.city}, ${geo.country}`,
    timezone: geo.timezone,
  }), {
    headers: { "Content-Type": "application/json" },
  });
});

Edge vs Serverless: When to Use Which

FeatureEdge FunctionsServerless Functions
Latency~1-10ms (runs at CDN edge)~50-200ms (regional)
Built-in geoUsually yesSometimes (via headers)
RuntimeV8 isolates (limited APIs)Full Node.js/Python/etc.
Best forRouting, redirects, A/B testsAPI endpoints, heavy logic
Cold startsNear zero100ms-several seconds

Caching Geo Data at the Edge

// Vercel Edge with Cache API
export async function middleware(request: NextRequest) {
  const cache = caches.default;
  const cacheKey = new Request(
    "https://geo.kamero.ai/api/geo",
    { method: "GET" }
  );

  let response = await cache.match(cacheKey);
  if (!response) {
    response = await fetch("https://geo.kamero.ai/api/geo");
    // Cache for 10 minutes
    const cached = new Response(response.body, response);
    cached.headers.set("Cache-Control", "max-age=600");
    await cache.put(cacheKey, cached);
  }

  const geo = await response.json();
  // Use geo data for routing...
}

Key Takeaways

Deploy Your Own

Self-host the geo API on Vercel for zero-latency edge geolocation.

Self-Hosting Guide →