DEV Community

Alex Neamtu
Alex Neamtu

Posted on • Originally published at sendrec.eu

How We Made Watch Page Analytics Provider-Agnostic

We wanted analytics on our shared video watch pages. Who's watching, when, from where. We added Umami support in the morning and ripped it out by the afternoon — not because it didn't work, but because we'd hardcoded ourselves into a corner.

The first version: UMAMI_WEBSITE_ID

The initial implementation was a single environment variable:

UMAMI_WEBSITE_ID=9abee88c-b595-41ea-b4cf-659c01e57101
Enter fullscreen mode Exit fullscreen mode

The Go template rendered a hardcoded Umami script tag:

{{if .UmamiWebsiteID}}
<script defer src="/script.js"
  data-website-id="{{.UmamiWebsiteID}}"
  nonce="{{.Nonce}}"></script>
{{end}}
Enter fullscreen mode Exit fullscreen mode

This worked. But it only worked for Umami. If a self-hoster wanted to use Plausible, Matomo, Simple Analytics, or Fathom, they were out of luck. We'd coupled the app to a specific provider for no good reason.

The fix: accept any <script> tag

We replaced the Umami-specific variable with a generic one:

ANALYTICS_SCRIPT='<script defer src="/script.js" data-website-id="xxx"></script>'
Enter fullscreen mode Exit fullscreen mode

The value is a complete <script> tag. Whatever your analytics provider gives you to paste into your HTML, you paste it here. Umami, Plausible, Matomo — they all work the same way: a script tag with a src and some data attributes.

The Go code reads the env var and renders it verbatim in the watch page:

type watchPageData struct {
    // ...
    AnalyticsScript template.HTML
}
Enter fullscreen mode Exit fullscreen mode

Using template.HTML instead of string tells Go's template engine to render the value as raw HTML, not escape it. This is safe because the value comes from a server environment variable — it's set by the operator, not by end users.

The CSP nonce problem

There's a catch. SendRec uses Content Security Policy with nonces. Every <script> tag needs a nonce attribute that matches the CSP header for that request, or the browser blocks it.

The operator's script tag won't have a nonce — they don't know what nonce the server will generate for each request. So we inject it:

func injectScriptNonce(scriptTag, nonce string) template.HTML {
    if scriptTag == "" {
        return ""
    }
    injected := strings.Replace(scriptTag,
        "<script",
        `<script nonce="`+nonce+`"`,
        1)
    return template.HTML(injected)
}
Enter fullscreen mode Exit fullscreen mode

Simple string replacement. Find <script, insert nonce="..." after it. The 1 limits it to the first occurrence — if someone's analytics snippet somehow has multiple script tags, only the first gets the nonce (the second would need to be loaded by the first).

Each request generates a fresh nonce, so the injected value is always current:

AnalyticsScript: injectScriptNonce(h.analyticsScript, nonce),
Enter fullscreen mode Exit fullscreen mode

What different providers look like

The same env var works for all of these:

Umami (self-hosted, proxied through your domain):

ANALYTICS_SCRIPT='<script defer src="/script.js" data-website-id="your-id"></script>'
Enter fullscreen mode Exit fullscreen mode

Plausible (cloud or self-hosted):

ANALYTICS_SCRIPT='<script defer data-domain="videos.example.com" src="https://plausible.io/js/script.js"></script>'
Enter fullscreen mode Exit fullscreen mode

Matomo:

ANALYTICS_SCRIPT='<script src="https://analytics.example.com/matomo.js"></script>'
Enter fullscreen mode Exit fullscreen mode

No code changes, no rebuilds, no new env vars per provider. One variable, any provider.

The deploy script that broke

This feature shipped with a bug that broke both staging and production deploys. The .env file on our server had:

ANALYTICS_SCRIPT=<script defer src="/script.js" data-website-id="xxx"></script>
Enter fullscreen mode Exit fullscreen mode

Notice the missing quotes. Our deploy script sources the .env file with source .env, and bash interpreted <script as a redirect operator. The error:

/opt/sendrec/.env: line 19: syntax error near unexpected token `<'
Enter fullscreen mode Exit fullscreen mode

The fix is single quotes:

ANALYTICS_SCRIPT='<script defer src="/script.js" data-website-id="xxx"></script>'
Enter fullscreen mode Exit fullscreen mode

Single quotes prevent bash from interpreting the contents. The value gets loaded verbatim, angle brackets and all.

The same quoting matters in docker run -e arguments. We switched from double quotes to single quotes there too:

docker run -e 'ANALYTICS_SCRIPT=<script ...></script>' ...
Enter fullscreen mode Exit fullscreen mode

A reminder that HTML in shell variables is a quoting minefield, and that even a straightforward change deserves a deploy test.

No analytics by default

When ANALYTICS_SCRIPT is empty or unset, nothing renders. No tracking pixel, no script tag, no network request. Self-hosters get zero analytics tracking out of the box.

This is a deliberate choice. Analytics should be opt-in, not opt-out. If you want tracking on your watch pages, set the variable. If you don't, your viewers' browsers stay quiet.

The full change

The diff is small — six files, 139 additions, 11 deletions:

  • video.go: analyticsScript field replaces umamiWebsiteID
  • watch_page.go: injectScriptNonce() helper, template.HTML rendering
  • server.go and main.go: wiring the new env var through
  • watch_page_test.go: three new tests (nonce injection, script rendered, no script when empty)
  • SELF-HOSTING.md: updated docs

No migrations, no database changes, no frontend changes. The analytics script only affects the server-rendered watch page.

Try it

SendRec is open source (AGPL-3.0) and self-hostable. Set ANALYTICS_SCRIPT to your provider's snippet and every shared video page gets tracking — with the CSP nonce handled for you. The implementation is in watch_page.go.

Top comments (0)