Ajouter un compteur de visiteurs GoatCounter à un site Jekyll sans requête réseau côté navigateur
Contourner les blocages CORS/bloqueurs sur les endpoints GoatCounter en récupérant le nombre de visiteurs côté serveur, au moment du build Jekyll, plutôt que depuis le navigateur du visiteur.
Table des matières
GoatCounter est une alternative légère et respectueuse de la vie privée à Google Analytics. Afficher un simple compteur “visiteurs / mois” sur la page d’accueil semble trivial — en pratique, chaque approche côté navigateur se heurte à un blocage différent. Ce guide documente le problème et la solution qui fonctionne réellement : tout récupérer côté serveur, au moment du build.
Ce qui ne fonctionne pas
L’API stats officielle (/api/v0/stats/total) exige systématiquement un token Bearer, même avec le dashboard configuré en public :
curl -s https://MONCODE.goatcounter.com/api/v0/stats/total
# → {"error":"no Authorization header"}
Le endpoint public /counter/*.json, pensé pour être embarqué sans token, fonctionne en curl mais casse en fetch() depuis le navigateur :
Access to fetch at 'https://moncode.goatcounter.com/counter//.json?...'
from origin 'https://mondomaine.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present
La variante <img> avec /counter/*.svg contourne en théorie le CORS (une image n’y est pas soumise) — mais échoue quand même avec un 499/net::ERR_FAILED, reproductible aussi bien sur PC que sur mobile, sans lien avec un bloqueur de pub local.
La solution : fetch côté serveur, au build
Plutôt que de faire confiance au navigateur du visiteur, un plugin Jekyll récupère le chiffre une seule fois, pendant jekyll build/jekyll serve, et l’injecte directement dans le HTML statique généré — plus aucune requête réseau au chargement de la page.
# _plugins/goatcounter_stats.rb
require "net/http"
require "json"
require "date"
Jekyll::Hooks.register :site, :after_reset do |site|
code = site.config.dig("analytics", "goatcounter_code")
next if code.nil? || code.empty?
begin
end_date = Date.today
start_date = end_date - 30
uri = URI("https://#{code}.goatcounter.com/counter//.json?start=#{start_date}&end=#{end_date}")
Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5) do |http|
res = http.get(uri.request_uri)
if res.is_a?(Net::HTTPSuccess)
data = JSON.parse(res.body)
site.config["goatcounter_visitors"] = (data["count_unique"] || data["count"] || "0").to_i
end
end
rescue StandardError => e
Jekyll.logger.warn "goatcounter_stats:", "fetch failed (#{e.message})"
end
end
Une image ruby:3.3-alpine ne contient pas les certificats CA par défaut — Net::HTTP en HTTPS échoue silencieusement (ou plante le process Jekyll au démarrage) tant que le paquet ca-certificates n’est pas installé dans le Dockerfile.
Affichage côté template
Le widget se contente de lire une valeur déjà calculée — aucun script, aucun appel réseau côté visiteur :
<div class="stat-card">
{% if site.goatcounter_visitors %}
{% if site.goatcounter_visitors >= 1000 %}
<div class="stat-value">{{ site.goatcounter_visitors | divided_by: 1000.0 | round: 1 }}k</div>
{% else %}
<div class="stat-value">{{ site.goatcounter_visitors }}</div>
{% endif %}
{% else %}
<div class="stat-value">—</div>
{% endif %}
<div class="stat-label">Visitors / month</div>
</div>
Configuration
# _config.yml
analytics:
goatcounter_code: "moncode"
Vérification
docker compose -f docker-compose.public.yml up -d --build
docker logs infops-public | grep -i goatcounter # aucune erreur = OK
curl -s http://localhost:4000/ | grep -A3 "Visitors / month"
# → le chiffre doit apparaître directement dans le HTML, pas de "—"
Résultat
- Aucun appel réseau côté navigateur : ni CORS, ni bloqueur, ni 499 possible.
- Le chiffre est déjà présent dans le HTML au moment où la page arrive chez le visiteur.
- Contrepartie assumée : la valeur ne se met à jour qu’au prochain build/rechargement, pas en temps réel — largement suffisant pour un compteur mensuel.
Comments