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
| Feature | Edge Functions | Serverless Functions |
|---|---|---|
| Latency | ~1-10ms (runs at CDN edge) | ~50-200ms (regional) |
| Built-in geo | Usually yes | Sometimes (via headers) |
| Runtime | V8 isolates (limited APIs) | Full Node.js/Python/etc. |
| Best for | Routing, redirects, A/B tests | API endpoints, heavy logic |
| Cold starts | Near zero | 100ms-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
- Vercel and Cloudflare provide built-in geo headers — use them when available
- For platforms without built-in geo, a quick API call adds minimal latency
- Edge functions are ideal for geo-routing and redirects
- Cache geo responses to avoid redundant lookups
- Always have a fallback for when geo detection fails
Deploy Your Own
Self-host the geo API on Vercel for zero-latency edge geolocation.
Self-Hosting Guide →