Django Analytics: Templates, HTMX, and Server Events
The Django setup for cookieless web analytics: base template installation, HTMX partial navigation, and server-side conversion events.
Django's classic strength — server-rendered pages with real URLs — is the easy case for analytics: every navigation is a genuine page load. The interesting parts are the modern additions (HTMX partials) and Django's natural fit for server-side conversion events. Both, below.
Base template installation
{# templates/base.html #}
<head>
<script
defer
src="https://clycyo.com/tracker.js"
data-tracking-id="{{ CLYCYO_TRACKING_ID }}"
></script>
</head>Expose the ID via a context processor reading settings — one place to change per environment. Full page loads arrive with complete navigation timing; nothing else to configure.
HTMX: partials vs navigations
HTMX swaps fragments without changing the URL — those are interactions, not pageviews, and are correctly not counted. When you use hx-push-url="true", the URL changes via the History API and the tracker records a pageview automatically. The rule of thumb falls out naturally: if it deserves a URL, it gets counted; if not, track it as an event where it matters:
<button hx-post="/subscribe" hx-swap="outerHTML"
onclick="window.webanalytics?.track('newsletter_subscribed')">
Subscribe
</button>Identity
{% if user.is_authenticated %}
<script>
window.webanalytics?.identify("{{ user.email|escapejs }}");
</script>
{% endif %}Persist getVisitorId() onto the user model with a tiny authenticated endpoint — the join key for everything that follows.
Server-side conversions, the Django way
# e.g. in a Stripe webhook view or a post_save signal
import requests
from django.conf import settings
requests.post("https://clycyo.com/api/collect", json={
"tracking_id": settings.CLYCYO_TRACKING_ID,
"type": "event",
"visitor_id": user.clycyo_visitor_id,
"event_name": "subscription_paid",
"event_properties": {
"revenue": invoice.amount_paid / 100,
"currency": invoice.currency.upper(),
"order_id": invoice.id,
},
})Run it in a Celery task for retries; order_id keeps duplicates harmless. Signals make Django unusually good at this pattern — any model event (activation, upgrade, cancellation) can emit an analytics event without touching view code.
The takeaway
- One template tag covers the whole site, classic or HTMX-flavored.
- Cookieless means no consent banner on Django's GDPR checklist.
- Server-side events + webhook revenue complete the attribution loop with tools Django developers already hold.