feat: add Umami reader-source tracking tags

Classify visitors by referrer into segments (direct, search, fediverse,
bluesky, indieweb, web) via a custom umami.track('reader-source') event.
Add noscript pixels to RSS/JSON feeds so feed-reader activity appears
under virtual /rss/* paths in Umami.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
svemagie
2026-03-25 09:04:34 +01:00
parent 811a182540
commit 89fbd222f2
3 changed files with 33 additions and 2 deletions

View File

@@ -2,6 +2,35 @@
<html lang="{{ site.locale | default('en') }}" class="loading">
<head>
<script defer src="https://cloud.umami.is/script.js" data-website-id="bbaa63e7-6dd2-4a4e-bbb2-68e9e75600f0"></script>
<script>
(function () {
function track() {
if (typeof umami === 'undefined') { setTimeout(track, 200); return; }
var ref = document.referrer;
var host = ref ? new URL(ref).hostname.replace(/^www\./, '') : '';
var segment;
var searches = ['google.com','bing.com','duckduckgo.com','yahoo.com','kagi.com','yandex.com','baidu.com','ecosia.org','startpage.com','search.brave.com'];
var fediInstances = ['mastodon.social','fosstodon.org','hachyderm.io','infosec.exchange','chaos.social','ruby.social','indieweb.social','social.lol','octodon.social','mas.to','mstdn.social','techhub.social','universeodon.com','kolektiva.social','scholar.social','mathstodon.xyz','social.coop','functional.cafe','c.im','aus.social','social.tchncs.de','masto.ai','sigmoid.social'];
var fediPatterns = [/^mastodon\./,/\.mastodon\./,/^lemmy\./,/^kbin\./,/^pixelfed\./,/^pleroma\./,/^misskey\./,/^friendica\./,/^diaspora\./,/^hometown\./];
var indieDomains = ['indienews.org','indieblog.page','news.indieweb.org','indieweb.org','micro.blog','telegraph.p3k.io'];
if (!host) {
segment = 'direct';
} else if (searches.some(function (d) { return host === d || host.slice(-(d.length + 1)) === '.' + d; })) {
segment = 'search';
} else if (host === 'bsky.app') {
segment = 'bluesky';
} else if (fediInstances.indexOf(host) !== -1 || fediPatterns.some(function (p) { return p.test(host); })) {
segment = 'fediverse';
} else if (indieDomains.some(function (d) { return host === d || host.slice(-(d.length + 1)) === '.' + d; })) {
segment = 'indieweb';
} else {
segment = 'web';
}
umami.track('reader-source', { segment: segment, ref: host || 'direct' });
}
track();
}());
</script>
{# OG image resolution handled by og-fix transform in eleventy.config.js
to bypass Eleventy 3.x parallel rendering race condition (#3183).
Template outputs __OG_IMAGE_PLACEHOLDER__ and __TWITTER_CARD_PLACEHOLDER__

View File

@@ -50,7 +50,8 @@ eleventyImport:
"id": "{{ absolutePostUrl }}",
"url": "{{ absolutePostUrl }}",
"title": {% if post.data.title %}{{ post.data.title | jsonEncode | safe }}{% else %}null{% endif %},
"content_html": {{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | jsonEncode | safe }},
{%- set _jsonPixel = '<img src="https://cloud.umami.is/noscript.gif?website-id=bbaa63e7-6dd2-4a4e-bbb2-68e9e75600f0&amp;url=/rss' + post.url + '" width="1" height="1" alt="" style="display:none">' %}
"content_html": {{ (post.content | htmlToAbsoluteUrls(absolutePostUrl) + _jsonPixel) | jsonEncode | safe }},
"content_text": {{ post.content | striptags | jsonEncode | safe }},
"date_published": "{{ post.date | dateToRfc3339 }}",
"date_modified": "{{ (post.data.updated or post.date) | dateToRfc3339 }}"

View File

@@ -35,7 +35,8 @@ eleventyImport:
<link>{{ absolutePostUrl }}</link>
<guid isPermaLink="true">{{ absolutePostUrl }}</guid>
<pubDate>{{ post.date | dateToRfc822 }}</pubDate>
<description>{{ post.content | htmlToAbsoluteUrls(absolutePostUrl) | escape }}</description>
{%- set _rssPixel = '<img src="https://cloud.umami.is/noscript.gif?website-id=bbaa63e7-6dd2-4a4e-bbb2-68e9e75600f0&url=/rss' + post.url + '" width="1" height="1" alt="" style="display:none">' %}
<description>{{ (post.content | htmlToAbsoluteUrls(absolutePostUrl) + _rssPixel) | escape }}</description>
{%- if postImage and postImage != "" and (postImage | length) > 10 %}
{%- set imageUrl = postImage | url | absoluteUrl(site.url) %}
<enclosure url="{{ imageUrl }}" type="image/jpeg" length="0"/>