Network · Traffic map accuracy
How to get city/state-level truth on the /admin/traffic map — Cloudflare location headers, the places contract extension, and what the map does and does not claim today.
---
.. toc::
What the map claims today (be honest with yourself first)
Two very different data sources share one map:
- Hub visits (2plot.ai) plot at real coordinates — ip-api.com
returns lat/lon + city + region per public IP, and markers group into ~10 km cells. City-level, genuinely useful.
- Satellite reports only carry
countries: {CC: hits}— so their
markers sit on country centroids. Every US visitor to dash-email becomes one marker in Kansas. The per-app ring offset around each centroid is *presentation* (so overlapping apps stay hoverable), not accuracy — the tooltip still names only the country.
That is the ceiling of the current satellite contract, and why the map clusters at country centers. Raising the ceiling means satellites must *capture* finer location, then *report* it.
Step 1 — turn on Cloudflare visitor-location headers (free)
For every zone proxied through Cloudflare (2plot.media, 2plot.xyz, 2plot.dev, …): Dashboard → your zone → Rules → Settings → Managed Transforms → enable "Add visitor location headers". Available on all plans, including Free. Every request then arrives with:
`` CF-IPCity: Denver CF-IPLatitude: 39.739 CF-Region: Colorado CF-IPLongitude: -104.984 CF-Region-Code: CO CF-IPCountry: US CF-IPContinent / CF-Timezone / CF-PostalCode also available ``
No API calls, no latency, no rate limits — the accuracy problem becomes a header read. The headers only appear on proxied (orange-cloud) records, and proxying is decided per DNS record, not per zone — which matters for this network:
- Clerk's records must stay DNS-only (
clerk.<domain>,
accounts.<domain>, and the email DKIM CNAMEs) — proxying those breaks Clerk's TLS provisioning. But those are Clerk's own subdomains; the app host record (2plot.media, www, …) is a separate record and CAN be proxied without touching them. In this network only the hub carries Clerk DNS anyway — satellites use the redirect model (sign-in happens on 2plot.ai), so their zones have no Clerk records at all.
- Render behind the Cloudflare proxy works with SSL/TLS mode set
to *Full (Strict)* on the zone. Test the sign-in round trip after flipping any record.
If you'd rather not proxy anything, that's fine too — the map does not depend on Cloudflare. The hub already gets city-level lat/lon from ip-api.com per public IP (free tier ~45 lookups/min over plain HTTP: cache per IP, resolve outside the request path), and that is what powers the map's "Precise" layer today. Cloudflare headers are the zero-cost upgrade path, not a requirement; without them, satellites simply stay on the country-centroid layer.
Step 2 — the places contract extension (v2.1)
Alongside countries, satellites report a capped list of place rollups:
``json "places": [ {"cc": "US", "region": "CO", "city": "Denver", "lat": 39.7, "lon": -105.0, "hits": 41}, {"cc": "DE", "region": "BE", "city": "Berlin", "lat": 52.5, "lon": 13.4, "hits": 9} ] ``
Rules that keep it truthful and private:
- Round lat/lon to one decimal (~10 km) — enough to see a metro,
impossible to see a house. Never ship raw coordinates.
- Cap at ~30 places, most-visited first; everything below the cap stays
country-level in countries (which remains the complete count).
region/cityoptional — send what you have. A place with only
cc + rounded lat/lon still beats a centroid.
- Humans only, same as every other v2 field.
Hub-side this rides the same accumulate-across-restarts reader as pages/countries; the map prefers a report's places and falls back to centroid-countries per app. (Reader + map support is the next hub change; satellites can start sending places first — unknown fields are ignored, nothing breaks.)
Step 3 — reading the map like a marketing tool
- Visits mode answers "where is my audience" — size ads/regions by
marker area, which scales with √hits so big markers don't lie.
- Session-length mode answers "where is my *engaged* audience" — a
small marker with long sessions is a better ad target than a big marker of bounces. Cross-check both before spending.
- Satellite session-length markers all use the app's overall median
(that's the reported granularity); only hub markers have per-place session medians until places ships with per-place durations.
- Bot filtering happens before any of this, but scrapers with browser
UAs still slip through — a "city" showing hundreds of hits, seconds of session, from one visitor is a crawler, not a market.
Current limitations, stated plainly
| Limitation | Cause | Fix | |---|---|---| | Satellite traffic clusters at country centers | contract sends countries only | Steps 1–2 | | Ring offsets around centroids | presentation, not data | tooltip names the country; ignore the spread | | Hub geo misses some visitors | private/CGNAT IPs, ip-api misses | unavoidable; counts stay truthful, only the map thins | | One app median for satellite session markers | reported granularity | per-place durations in a later places rev |
---
*Source: /docs/traffic-map*
Note for AI agents: This is the static, prerendered view of an interactive Dash application served because we detected a non-JS user agent. Full prose docs:
- /docs/traffic-map/llms.txt — LLM-friendly documentation
- /sitemap.xml
- /robots.txt