I aftes byggede jeg min egen webstatistik til Folkets Avis. Den viser i realtid, hvad læserne læser, hvor langt de når, og om de klikker på «Støt». Den gemmer ingen IP-adresser, sætter ingen cookies, og den er skåret præcis til mit eget medie. Jeg har ventet på det her i årevis.
Klokken er lidt over midnat. En artikel om en kvinde, der bare ville beholde sit fine porcelæn, dukker op i et live-feed på min skærm. Sekundet efter en til. Så kommer der en læser ind fra Infomedias medieovervågning. Det er første gang, jeg ser Folkets Avis blive læst, mens det sker.
Jeg har brugt Google Analytics i mange år, fordi alle gør det. Og jeg har aldrig kunnet lide det. Det er tungt, det sender mine læseres adfærd til Google, det kræver et cookiebanner, og når jeg endelig åbner det, skal jeg igennem fem menuer for at finde ud af, hvilken artikel der blev læst i går. Det er bygget til en webshop med et marketingbudget, ikke til en lille avis.
Nu har jeg noget, der er bygget til netop min avis. Det tog en aften.
Hvad dashboardet viser
Øverst fire tal: visninger de sidste fem minutter, visninger i dag, besøgende i dag og støtteklik i dag. Under dem:
- Hot lige nu: de artikler, der tager fart, og hvor læserne kommer fra.
- Artikler i dag: visninger, hvor mange der nåede halvvejs, hvor mange der læste færdig, hvor længe siden var fremme, og hvor mange der klikkede videre til at støtte avisen.
- Live: hver eneste sidevisning, med klokkeslæt, i det øjeblik den sker.
- Henvisninger, enheder, browsere og en oversigt over de foregående dage.
Det er kolonnen «Færdig», der gør mig glad. Google Analytics kan fortælle mig, at en artikel blev åbnet. Det kan ikke uden videre fortælle mig, om nogen læste den. Det kan webstat. Når halvdelen af artiklens brødtekst har været på skærmen, går der ét signal. Når 95 procent har, går der ét til. I morges kunne jeg se, at to ud af tre, der åbnede «Hvem betaler din avis?», læste den helt til ende. Det er et tal, en redaktør kan bruge.
Og så «Støt»-kolonnen. Folkets Avis lever af læsere, der vælger at støtte. Nu kan jeg se, hvilken artikel der fik nogen til at klikke. Det har jeg aldrig kunnet før.
Byggeklodserne
Fire ting gør det muligt, og ingen af dem er store.
xs (cross.stream) er en eventstrøm. Hver sidevisning, hver læsedybde, hvert klik bliver gemt som en lille hændelse med et emne som webstat.folkets.pageview og fakta i et felt ved siden af. Intet bliver overskrevet. Alt kan spilles igennem igen fra første hændelse.
http-nu er en webserver, hvor hele håndteringen er skrevet i Nushell. Ét script, serve.nu, tager imod hændelserne, tegner dashboardet og streamer det ud.
SSE (Server-Sent Events) er den gamle, kedelige og fuldstændig undervurderede måde at lade en server skubbe data til en browser på. Én forbindelse, der står åben. Ingen WebSockets, ingen polling.
Datastar er et lille JavaScript-bibliotek, der tager imod HTML over SSE og sætter det ind på siden. Serveren tegner, browseren viser. Der er ingen React, ingen build-trin og ingen klientlogik at vedligeholde.
Det smukkeste er, hvor lidt kode der skal til, for at det hele bliver live. Her er hele ruten, der holder dashboardet opdateret:
(route {method: "GET", path: "/updates"} {|req ctx|
.cat --follow --new -T "webstat-stats.out"
| each {|f| board | to datastar-patch-elements }
| prepend (board | to datastar-patch-elements)
| to sse
})
Læst højt: Følg strømmen. Hver gang der kommer nye tal, tegn tavlen og send den. Start med at sende tavlen, som den ser ud lige nu. Det er det hele. På siden står der data-init="@get('/updates')", og så tager Datastar sig af resten.
En aktør, der regner
Mellem de rå hændelser og dashboardet sidder en aktør: et lille Nushell-program, som xs kører, hver gang der kommer en ny hændelse. Den lægger tallene sammen for dagen, holder styr på læsedybde og tid per artikel, og ruller dagen over i historikken ved midnat. Resultatet skriver den som én hændelse, webstat-stats.out, og det er dén, dashboardet lytter efter.
Det betyder, at dashboardet aldrig regner. Det læser ét færdigt resultat. Og fordi aktøren starter fra første hændelse, kan jeg ændre i, hvordan tallene bliver regnet ud, og få hele historikken regnet om, uden at miste noget.
«Hot lige nu» er regnet på en måde, jeg er lidt stolt af. Hver visning den seneste time tæller, men den vejer mindre, jo ældre den er. Halveringstiden er et kvarter. En artikel, der fik ti visninger for en halv time siden, vejer derfor mindre end en, der fik fem inden for de sidste fem minutter. Listen køler af af sig selv, også når der ikke sker noget.
Den samme liste står nu på forsiden af folkets.dk som «Hot lige nu», så læserne kan se, hvad andre læser lige nu. Og når en artikel krydser en tærskel, får jeg en besked på telefonen via ntfy: «Tager fart: …», med den vigtigste henvisning og et link. Klokken syv hver morgen kommer en rapport om i går. Ingen af delene skulle købes som et tilvalg.
Privacy first, og det er ikke en floskel
Det var det vigtigste krav. Jeg vil ikke vide, hvem mine læsere er. Jeg vil vide, hvad de læser.
Sådan er det skruet sammen:
- Ingen cookies, intet gemt på læserens enhed. Hver sidevisning får et tilfældigt id, der lever lige så længe som siden er åben, og så er det væk.
- Ingen IP-adresser gemt. Besøgende tælles som et hash af IP-adresse, browser og et salt. Saltet ligger kun i serverens hukommelse og skiftes hver dag. Derfor kan en læser tælles én gang i dag, men aldrig følges fra den ene dag til den næste. Selv jeg kan ikke.
- Grove kategorier. Browser og styresystem bliver til «Chrome» og «iOS», ikke en fuld user agent. Skærmen bliver til mobil, tablet eller computer.
- Ingen tredjepart. Data sendes ikke til Google, Meta eller nogen andre. Det ligger i én eventstrøm, som jeg ejer.
- Bots tæller ikke. Crawlere, forhåndsindlæsning og sprogmodellernes scrapere bliver sorteret fra, før noget gemmes.
Det betyder også, at tallene er mere ærlige. Google Analytics ser kun de læsere, der har sagt ja til cookies og ikke bruger en adblocker. Webstat ser dem alle.
Den lille PHP-fil, der gør forskellen
Én detalje er værd at fremhæve, fordi den løser to problemer på én gang.
Scriptet i læserens browser sender ikke direkte til webstat. Det sender til /nyt.php på folkets.dk selv. Det er en almindelig PHP-fil på cirka hundrede linjer, som svarer læseren med det samme og derefter sender hændelsen videre til webstat sammen med en hemmelig nøgle.
Det første problem, den løser, er adblockere. De blokerer forespørgsler til fremmede domæner, men de lader et sites egne forespørgsler være i fred. Det andet er IP-adressen. Webstat kører bag to lag proxy hos cross.stream, og dér kommer læserens adresse ikke med igennem. Det gør den hos folkets.dk, hvor Cloudflare giver den videre. PHP-filen sender adressen med til webstat, som hasher den og glemmer den. Kun en forespørgsel med den rigtige nøgle må oplyse en IP-adresse.
Filen giver op efter ét sekund, hvis webstat ikke svarer. Et nedbrud i statistikken må aldrig kunne gøre avisen langsom.
Skåret til mit eget medie
Det her er den del, en standardløsning aldrig kan give mig.
Scriptet kender Drupal. Det læser nodens id og indholdstype, artiklens overskrift og brødtekstens felt. Derfor kan det måle læsedybde i selve artiklen og ikke i sidefoden. Derfor kan dashboardet vise overskrifter frem for URL'er. Og derfor kan jeg se, om det er en videnshistorie, en nyhed eller en kommentar, der bliver læst.
Det ved, hvad «støtte» betyder på Folkets Avis: et klik på /abonnent, /stot eller et link til Stripe. Det ved, hvilken artikel læseren kom fra, da de landede på abonnementssiden. Det er præcis det spørgsmål, jeg har stillet mig selv i årevis: Hvilke artikler får folk til at betale?
Og når jeg i morgen finder på et nyt spørgsmål, er det en linje i aktøren. Hændelserne ligger der allerede.
Hvad det betyder for din virksomhed
Jeg skriver det her med begejstring, fordi det var sjovt. Men der er en pointe, der rækker ud over en lille avis.
- Standardværktøjer er bygget til gennemsnittet. Google Analytics svarer på de spørgsmål, Google tror, de fleste stiller. Hvis dine vigtigste spørgsmål er dine egne, kan et lille, skræddersyet værktøj være både billigere og bedre.
- Det er blevet billigt at bygge selv. Jeg byggede det her på en aften sammen med en AI-assistent. For to år siden ville det have været et projekt på flere uger. Grænsen for, hvornår det kan betale sig at bygge frem for at købe, er flyttet markant.
- Privatliv kan være et designvalg fra starten. Det er meget lettere at lade være med at gemme en IP-adresse end at slette den bagefter. Og data, du aldrig har gemt, kan ikke lække.
- Ejerskab af data er en strategisk fordel. Mine tal ligger i en eventstrøm, jeg ejer, i et format, jeg kan læse med én kommando. De forsvinder ikke, når en leverandør ændrer sine vilkår.
Jeg har ikke lukket Google Analytics ned endnu. Jeg vil se de to side om side et stykke tid. Men jeg ved allerede, hvilket af dem jeg åbner om morgenen.