All writing
EngineeringJuly 28, 20263 min read

We Were Paying for Geolocation Cloudflare Already Sends Us

Two marketing sites called a paid IP-geolocation API from the browser, with the key in the bundle. The country was already in a request header.

Henry Iddirisu

Henry Iddirisu

AI product engineer

A request passing through an edge node that stamps a country code header

The country was in the header all along.

Two of the Next.js marketing sites I work on needed the visitor's country, to pick a phone dial code on lead forms and to show local defaults. Both got it by calling a paid IP-geolocation API from the browser, with the API key in a NEXT_PUBLIC_ variable. That meant the key was in the client bundle, and every page view spent quota.

Both sites sit behind Cloudflare, and Cloudflare adds a CF-IPCountry header to every request it forwards. The answer was already arriving for free, on the server.

The fallback chain

I moved detection server-side, into one small module with an explicit order:

  1. CF-IPCountry, when present and valid. Cloudflare's XX (unknown) and T1 (Tor) values count as missing.
  2. The paid API, server-side, only while its key is still configured. This was a temporary safety net for the rollout.
  3. None. The locale's defaults apply.

The forms now fetch a same-origin /api/geo route instead of the third-party API. The route returns the same field names the old API used, so the components barely changed. It also returns a source field saying which step answered.

A bug that had always been there

The middleware also set a country cookie. It read request.geo, or a Vercel-specific header. On our Docker hosting, neither exists, so the cookie had always said unknown, and the hook that read it silently fell back every time. Reading CF-IPCountry first made that code path work for the first time.

Rolling it out safely

The source field made the rollout checkable from any browser: open /api/geo on each domain and look for source: "cloudflare". Only once every domain reported that was it safe to delete the key and cancel the subscription.

Before shipping, I tested the chain against a local server:

  • the header is used first, with no outbound call
  • the real API fallback works (8.8.8.8 resolves to US, +1)
  • with no key, the result is empty instead of an error
  • page responses set the country cookie from the header

When I added the sites' first test suites later, the geo module was one of the first things covered, including a test that a Cloudflare hit never calls the paid API.

The result

  • No API key in the browser. The environment variable lost its NEXT_PUBLIC_ prefix and became server-only.
  • A subscription that can be cancelled. The paid API is only a fallback during rollout.
  • A cookie that finally works on the actual hosting.

The general lesson: before adding a service, check what your edge already tells you. CDNs forward a lot of useful context, like country, protocol, and client IP. Reading a header is cheaper, faster, and safer than calling someone else's API from the browser.

#nextjs#cloudflare#security#cost
Henry Iddirisu

Henry Iddirisu

AI product engineer · Accra, Ghana · Remote

Keep reading