Summary
piik.me's analytics dashboard currently tracks device type, browser, referrer, and click history with timestamps. However, there is no geographic data — users cannot see which countries or regions their links are being clicked from. Geographic analytics is one of the most-requested features in link analytics tools and is offered by every major competitor (Bitly, Short.io, Rebrandly). The existing clickHistory Firestore schema has room for a country and city field.
Problem
- The
GET /:shortCode redirect handler processes a click and tracks device, browser, and referrer — but the request IP address is not used for geolocation.
- The
analytics Firestore collection has no countries or regions map — geographic data is nowhere in the current schema.
- The Socket.IO
analyticsUpdate event sends impressions, clicks, shares, etc. — no geographic payload exists.
- The globe visualization (
Globe.gl and Three.js are listed as frontend dependencies!) appears to be in the codebase but likely has no real data powering it — it may be displaying placeholder data. Geographic click data would make this globe meaningful and visually impressive.
- Marketing users — the core audience for a URL shortener — specifically need geographic data to evaluate campaign performance by region.
Proposed Solution
1. Add IP geolocation using geoip-lite (offline, no API key required):
// src/utils/geo.utils.js
const geoip = require('geoip-lite');
function getGeoFromIP(ip) {
// Handle IPv4-mapped IPv6 addresses (e.g., ::ffff:1.2.3.4)
const cleanIP = ip.replace('::ffff:', '');
const geo = geoip.lookup(cleanIP);
if (!geo) return { country: 'Unknown', city: 'Unknown', ll: null };
return {
country: geo.country || 'Unknown',
city: geo.city || 'Unknown',
ll: geo.ll, // [latitude, longitude]
};
}
module.exports = { getGeoFromIP };
2. Update the click tracking in the redirect handler:
// server.js or routes/redirect.routes.js
const { getGeoFromIP } = require('./src/utils/geo.utils.js');
app.get('/:shortCode', async (req, res) => {
// ... existing link lookup and expiry check ...
const ip = req.headers['x-forwarded-for']?.split(',')[0] || req.socket.remoteAddress;
const geo = getGeoFromIP(ip);
// Update Firestore analytics
await db.collection('analytics').doc(shortCode).update({
clicks: admin.firestore.FieldValue.increment(1),
[`countries.${geo.country}`]: admin.firestore.FieldValue.increment(1),
clickHistory: admin.firestore.FieldValue.arrayUnion({
timestamp: admin.firestore.FieldValue.serverTimestamp(),
device: detectDevice(req.headers['user-agent']),
browser: detectBrowser(req.headers['user-agent']),
referrer: req.headers['referer'] || 'direct',
country: geo.country, // NEW
city: geo.city, // NEW
ll: geo.ll, // NEW — [lat, lng] for globe visualization
}),
});
// Emit real-time update via Socket.IO including geo data
io.emit('analyticsUpdate', {
shortCode,
country: geo.country,
city: geo.city,
ll: geo.ll,
});
res.redirect(link.originalUrl);
});
3. Update the analytics Firestore schema:
{
// ... existing fields ...
countries: { // NEW — map of country code to click count
"US": 145,
"IN": 87,
"GB": 34,
},
clickHistory: [{
// ... existing fields ...
country: string, // NEW — "US", "IN", "Unknown"
city: string, // NEW — "New York", "Unknown"
ll: [number, number] | null, // NEW — [lat, lng] for globe
}]
}
4. Frontend — power the existing Globe.gl visualization with real data:
Since globe.gl is already a dependency, connect the real-time geo click events to the globe:
// public/js/app.js — globe section
socket.on('analyticsUpdate', (data) => {
if (data.ll) {
globe.pointsData([...existingPoints, {
lat: data.ll[0],
lng: data.ll[1],
size: 0.5,
color: 'rgba(0, 200, 255, 0.8)',
label: `${data.city}, ${data.country}`,
}]);
}
});
Additional Notes
geoip-lite uses a bundled MaxMind GeoLite2 database (~30MB) — no external API call is made during redirect, so geolocation adds under 1ms latency.
- For privacy compliance: IP addresses are never stored in Firestore — only the derived
country and city are persisted.
geoip-lite is accurate to country-level for ~99% of IPs and city-level for ~70%.
- The existing globe.gl dependency suggests geographic visualization was always intended — this issue delivers the data layer to make it real.
Could you assign this issue to me?
Labels: enhancement, feature, analytics, GSSoC 2026
Summary
piik.me's analytics dashboard currently tracks device type, browser, referrer, and click history with timestamps. However, there is no geographic data — users cannot see which countries or regions their links are being clicked from. Geographic analytics is one of the most-requested features in link analytics tools and is offered by every major competitor (Bitly, Short.io, Rebrandly). The existing
clickHistoryFirestore schema has room for acountryandcityfield.Problem
GET /:shortCoderedirect handler processes a click and tracksdevice,browser, andreferrer— but the request IP address is not used for geolocation.analyticsFirestore collection has nocountriesorregionsmap — geographic data is nowhere in the current schema.analyticsUpdateevent sendsimpressions,clicks,shares, etc. — no geographic payload exists.Globe.glandThree.jsare listed as frontend dependencies!) appears to be in the codebase but likely has no real data powering it — it may be displaying placeholder data. Geographic click data would make this globe meaningful and visually impressive.Proposed Solution
1. Add IP geolocation using
geoip-lite(offline, no API key required):2. Update the click tracking in the redirect handler:
3. Update the
analyticsFirestore schema:4. Frontend — power the existing Globe.gl visualization with real data:
Since
globe.glis already a dependency, connect the real-time geo click events to the globe:Additional Notes
geoip-liteuses a bundled MaxMind GeoLite2 database (~30MB) — no external API call is made during redirect, so geolocation adds under 1ms latency.countryandcityare persisted.geoip-liteis accurate to country-level for ~99% of IPs and city-level for ~70%.Could you assign this issue to me?
Labels:
enhancement,feature,analytics,GSSoC 2026