Free and open-source link-in-bio tool with privacy-friendly click analytics
This is an enhanced fork of 0x4bs3nt/urlinks with the following fixes and additions:
[Fix #69] Public directory assets served at wrong paths
index.html was referencing favicons as public/favicon.svg instead of /favicon.svg. In Vite, files in the public/ directory are served at the root — the public/ prefix must be omitted in HTML references. Also removed a stray <link rel="stylesheet" href="src/index.css" /> tag that is handled by the module bundler, not the HTML.
[Fix] ALLOWED_HOSTS / CORS empty-string parsing
"".split(",") produces [""] not [], meaning Django would allow requests from a host with an empty name. Fixed with a list comprehension filter.
[Fix] JWT refresh token rotation
Enabled ROTATE_REFRESH_TOKENS: True and BLACKLIST_AFTER_ROTATION: True to prevent refresh token reuse after logout — a standard security practice.
Implements click tracking and analytics without storing any PII — fully GDPR-compliant by design.
| What we store | What we don't store |
|---|---|
Country code (e.g. US) |
Raw IP addresses |
Referrer domain (e.g. instagram.com) |
Full referrer URLs |
Device class (mobile/desktop/tablet) |
Full User-Agent strings |
| SHA-256 hash of IP + link + date | The IP used to create it |
Deduplication: the same visitor clicking the same link on the same day counts as one click. The hash is salted with the link ID so hashes can't be correlated across pages.
Track a click (public, no auth)
POST /profile/link/{link_id}/track_click/
Returns {"tracked": true}. Call this when a visitor clicks a link.
Get analytics (authenticated)
GET /profile/page/analytics/?days=30
Returns:
{
"period_days": 30,
"total_clicks": 847,
"per_link": [
{"link_id": 3, "link__title": "My Substack", "clicks": 412},
...
],
"daily_trend": [
{"date": "2026-05-01", "clicks": 23},
...
],
"device_split": [
{"device_type": "mobile", "clicks": 601},
{"device_type": "desktop", "clicks": 246}
],
"top_referrers": [
{"referrer_domain": "instagram.com", "clicks": 530},
...
]
}- Backend: Django 5 + Django REST Framework + PostgreSQL
- Frontend: React 19 + Vite + TypeScript + Tailwind CSS
- Auth: JWT (SimpleJWT) with refresh token rotation
- Storage: Local filesystem (dev) / Cloudflare R2 (prod)
See the original README setup instructions. After cloning this fork, run the additional migration:
cd backend
python manage.py migrateThis analytics layer is the exact feature that separates Linktree's free tier from its $9/month Pro plan. A SaaS built on this stack could charge creators for:
- Extended analytics history (30 days free → 1 year paid)
- Country-level breakdowns (geo analytics)
- CSV export
- Custom domain support
- White-label embedding
See BUSINESS.md for the full analysis.
BSD-3-Clause (same as upstream)