Building a Fast IP-to-Country Lookup in Your Stack
A hands-on guide to adding country detection to a web app: where to call it, how to cache it, and how to keep it off your critical path.
# Building a Fast IP-to-Country Lookup in Your Stack
Country detection should add single-digit milliseconds to a request. Here is how to keep it that way.
Get the client IP right first
Behind a proxy or CDN the socket address is your own edge. Read the header your infrastructure actually sets, and trust it only from your own network:
- Cloudflare:
CF-Connecting-IP - Most load balancers: leftmost untrusted entry in
X-Forwarded-For - Direct: the socket peer address
Parsing X-Forwarded-For blindly is a spoofing hole, because a client can prepend anything it likes.
Call it once per session, not once per request
Resolve on session creation, put the country and classification in the session or a signed cookie, and reuse it. Re-resolve when the IP changes. This turns thousands of lookups into one.
Cache with a sane TTL
Geolocation for a given /24 is stable for hours. A one-hour in-memory cache keyed by IP removes most repeat traffic. Cache negative results too, with a shorter TTL, so an outage does not turn into a lookup storm.
Never block rendering on it
Give the lookup a hard timeout of a few hundred milliseconds and a defined fallback:
ts const geo = await Promise.race([ lookup(ip), timeout(300).then(() => null), ]); const country = geo?.country ?? defaultCountry;
A page that renders in the wrong currency is a minor annoyance. A page that does not render is lost revenue.
Do it at the edge when you can
If your app runs on an edge runtime, the lookup happens geographically close to the user and the added latency is negligible. Resolve there, attach the result to the request context, and let the application read it as plain data.
Keep the decision, drop the data
Store the outcome your business cares about (currency shown, rule applied) rather than the raw location record. It is cheaper, it is easier to explain to a privacy reviewer, and it is all you will actually query later.
