Repository navigation
feat(web): fill view locations from a GeoIP database when self-hosted - #21
Merged
Merged
Conversation
Views only get a country and city from Vercel's x-vercel-ip-* headers, so self-hosted instances record every view with no location and the analytics map stays empty. When those headers are missing and CAP_GEOIP_DATABASE_PATH points to a GeoIP2/GeoLite2 City .mmdb file (MaxMind, DB-IP Lite), look up the client address instead. Covers the share page tracker and the mobile view event. Without the variable nothing changes; a missing or unreadable file logs one warning and views keep no location.
The route test mocks @/lib/rate-limit as a whole, so it now provides clientAddress, and mocks the GeoIP lookup to check that Vercel's headers still win and the database fills the location without them.
This was referenced Nov 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The analytics map ("Country / City") is always empty on our instance. View locations only come from Vercel's
x-vercel-ip-country,x-vercel-ip-country-regionandx-vercel-ip-cityheaders, and Railway's edge sends none of them (onlyX-Real-IP/X-Forwarded-For).What
CAP_GEOIP_DATABASE_PATH: path to a GeoIP2/GeoLite2 City.mmdbfile (MaxMind GeoLite2 or DB-IP City Lite).apps/web/lib/visitor-location.tsopens the file once (lazily, on the first view) withmmdb-liband maps a record to{ country, region, city }. Region uses the subdivision code, or its name when the database has no codes (DB-IP Lite)./api/analytics/trackand the mobile view event use it only when the Vercel country header is missing, with the client address fromclientAddress(lastX-Forwarded-Forhop off Vercel, so the client can't pick it).mmdb-lib@3.0.3pinned exactly: MIT, no dependencies, no install scripts.Past views stay without a location: the view event never stored the IP.
The database itself ships with our image through
smg/web.Dockerfilein a separateinternal/*PR. This branch is upstream-able as is.Verified
visitor-location.test.ts: GeoIP2 and DB-IP record shapes, missing/private/invalid IPs, missing database file (warns once).8.8.8.8→ US / California / Mountain View,190.202.0.1→ VE / Zulia / San Carlos del Zulia,2800:3f0:4001::1→ BR / São Paulo,127.0.0.1→ null.bun run typecheckand Biome clean;visitor-countryandmobile-eventstests still pass.