Track feed readers with a campaign tag and a pixel
Feed readers send no referrer and run no JavaScript. Tag each item link with utm_campaign=rss so clicks land under GoatCounter's Campaigns, and end each item's description with a tracking pixel. The guid keeps the bare permalink.
d59cce3 parent: c6cef41 modified
blog/layouts/_default/rss.xml +16 -4 | @@ -1,14 +1,21 @@ | ||
| 1 | 1 | {{- /* |
| 2 | 2 | A copy of Hugo's embedded rss.xml (tpl/tplimpl/embedded/templates/rss.xml, |
| 3 | 3 | v0.166.0) kept deliberately close to it, so a future Hugo's version can be |
| 4 | - diffed against this one. Three things are ours: | |
| 4 | + diffed against this one. Five things are ours: | |
| 5 | 5 | |
| 6 | 6 | 1. the <?xml-stylesheet?> below, which asks a browser to render the feed |
| 7 | 7 | through feed.xsl instead of showing its tag tree; |
| 8 | 8 | 2. the <script>, which does the same job for browsers that no longer run |
| 9 | 9 | XSLT - see the note in hugo.toml; |
| 10 | 10 | 3. the channel <description>, which said "Recent content on mykiwi.blog" |
| 11 | - and now says what the site's own <meta name="description"> says. | |
| 11 | + and now says what the site's own <meta name="description"> says; | |
| 12 | + 4. each item's <link> carries ?utm_campaign=rss, which GoatCounter files | |
| 13 | + under Campaigns - a feed reader sends no referrer, so those clicks | |
| 14 | + would otherwise all be "(unknown)". The <guid> stays the bare | |
| 15 | + permalink: changing it would mark every post as new in every reader; | |
| 16 | + 5. a GoatCounter tracking pixel at the end of each item's description. | |
| 17 | + Readers run no JavaScript, so count.js never sees them; an <img> is | |
| 18 | + the one thing many of them do fetch. See the note where it is built. | |
| 12 | 19 | |
| 13 | 20 | Both files are home-page outputs, so a section feed (posts/index.xml) has to |
| 14 | 21 | reach across to site.Home for them rather than ask its own .OutputFormats. |
| @@ -59,6 +66,11 @@ | ||
| 59 | 66 | {{- else }} |
| 60 | 67 | {{- $pages = $pctx.Pages }} |
| 61 | 68 | {{- end }} |
| 69 | +{{- /* Same host-then-path shape as baseof.html's window.goatcounter.path, so | |
| 70 | + the feed shows up in GoatCounter next to the pages of the language it is | |
| 71 | + the feed for. Only readers that load remote images are counted, once per | |
| 72 | + item they display, so this is a floor and not a subscriber count. */}} | |
| 73 | +{{- $pixel := printf "<img src=\"https://stats.mykiwi.blog/count?p=%s/feed\" alt=\"\" width=\"1\" height=\"1\">" (urls.Parse site.Home.Permalink).Host }} | |
| 62 | 74 | {{- $limit := .Site.Config.Services.RSS.Limit }} |
| 63 | 75 | {{- if ge $limit 1 }} |
| 64 | 76 | {{- $pages = $pages | first $limit }} |
| @@ -109,11 +121,11 @@ | ||
| 109 | 121 | {{- range $pages }} |
| 110 | 122 | <item> |
| 111 | 123 | <title>{{ .Title }}</title> |
| 112 | - <link>{{ .Permalink }}</link> | |
| 124 | + <link>{{ .Permalink }}?utm_campaign=rss</link> | |
| 113 | 125 | <pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> |
| 114 | 126 | {{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }} |
| 115 | 127 | <guid>{{ .Permalink }}</guid> |
| 116 | - <description>{{ .Summary | transform.XMLEscape | safeHTML }}</description> | |
| 128 | + <description>{{ printf "%s%s" .Summary $pixel | transform.XMLEscape | safeHTML }}</description> | |
| 117 | 129 | </item> |
| 118 | 130 | {{- end }} |
| 119 | 131 | </channel> |
| @@ -1,14 +1,21 @@ | |||
| 1 | {{- /* | 1 | {{- /* |
| 2 | A copy of Hugo's embedded rss.xml (tpl/tplimpl/embedded/templates/rss.xml, | 2 | A copy of Hugo's embedded rss.xml (tpl/tplimpl/embedded/templates/rss.xml, |
| 3 | v0.166.0) kept deliberately close to it, so a future Hugo's version can be | 3 | v0.166.0) kept deliberately close to it, so a future Hugo's version can be |
| 4 | - diffed against this one. Three things are ours: | 4 | + diffed against this one. Five things are ours: |
| 5 | 5 | ||
| 6 | 1. the <?xml-stylesheet?> below, which asks a browser to render the feed | 6 | 1. the <?xml-stylesheet?> below, which asks a browser to render the feed |
| 7 | through feed.xsl instead of showing its tag tree; | 7 | through feed.xsl instead of showing its tag tree; |
| 8 | 2. the <script>, which does the same job for browsers that no longer run | 8 | 2. the <script>, which does the same job for browsers that no longer run |
| 9 | XSLT - see the note in hugo.toml; | 9 | XSLT - see the note in hugo.toml; |
| 10 | 3. the channel <description>, which said "Recent content on mykiwi.blog" | 10 | 3. the channel <description>, which said "Recent content on mykiwi.blog" |
| 11 | - and now says what the site's own <meta name="description"> says. | 11 | + and now says what the site's own <meta name="description"> says; |
| 12 | + 4. each item's <link> carries ?utm_campaign=rss, which GoatCounter files | ||
| 13 | + under Campaigns - a feed reader sends no referrer, so those clicks | ||
| 14 | + would otherwise all be "(unknown)". The <guid> stays the bare | ||
| 15 | + permalink: changing it would mark every post as new in every reader; | ||
| 16 | + 5. a GoatCounter tracking pixel at the end of each item's description. | ||
| 17 | + Readers run no JavaScript, so count.js never sees them; an <img> is | ||
| 18 | + the one thing many of them do fetch. See the note where it is built. | ||
| 12 | 19 | ||
| 13 | Both files are home-page outputs, so a section feed (posts/index.xml) has to | 20 | Both files are home-page outputs, so a section feed (posts/index.xml) has to |
| 14 | reach across to site.Home for them rather than ask its own .OutputFormats. | 21 | reach across to site.Home for them rather than ask its own .OutputFormats. |
| @@ -59,6 +66,11 @@ | |||
| 59 | {{- else }} | 66 | {{- else }} |
| 60 | {{- $pages = $pctx.Pages }} | 67 | {{- $pages = $pctx.Pages }} |
| 61 | {{- end }} | 68 | {{- end }} |
| 69 | +{{- /* Same host-then-path shape as baseof.html's window.goatcounter.path, so | ||
| 70 | + the feed shows up in GoatCounter next to the pages of the language it is | ||
| 71 | + the feed for. Only readers that load remote images are counted, once per | ||
| 72 | + item they display, so this is a floor and not a subscriber count. */}} | ||
| 73 | +{{- $pixel := printf "<img src=\"https://stats.mykiwi.blog/count?p=%s/feed\" alt=\"\" width=\"1\" height=\"1\">" (urls.Parse site.Home.Permalink).Host }} | ||
| 62 | {{- $limit := .Site.Config.Services.RSS.Limit }} | 74 | {{- $limit := .Site.Config.Services.RSS.Limit }} |
| 63 | {{- if ge $limit 1 }} | 75 | {{- if ge $limit 1 }} |
| 64 | {{- $pages = $pages | first $limit }} | 76 | {{- $pages = $pages | first $limit }} |
| @@ -109,11 +121,11 @@ | |||
| 109 | {{- range $pages }} | 121 | {{- range $pages }} |
| 110 | <item> | 122 | <item> |
| 111 | <title>{{ .Title }}</title> | 123 | <title>{{ .Title }}</title> |
| 112 | - <link>{{ .Permalink }}</link> | 124 | + <link>{{ .Permalink }}?utm_campaign=rss</link> |
| 113 | <pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> | 125 | <pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> |
| 114 | {{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }} | 126 | {{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }} |
| 115 | <guid>{{ .Permalink }}</guid> | 127 | <guid>{{ .Permalink }}</guid> |
| 116 | - <description>{{ .Summary | transform.XMLEscape | safeHTML }}</description> | 128 | + <description>{{ printf "%s%s" .Summary $pixel | transform.XMLEscape | safeHTML }}</description> |
| 117 | </item> | 129 | </item> |
| 118 | {{- end }} | 130 | {{- end }} |
| 119 | </channel> | 131 | </channel> |