DocumentationCollecte côté serveur

Collecte côté serveur

La plupart des robots n’exécutent pas le JavaScript : seul votre serveur les voit. Une clé d’API et quelques lignes suffisent pour les compter, ainsi que les événements que seul le serveur connaît.

Sur cette page
  1. Pourquoi côté serveur
  2. Créer une clé d’API
  3. Next.js
  4. Node.js et Express
  5. PHP
  6. Autres langages (HTTP)
  7. Bonnes pratiques

Pourquoi côté serveur#

Deux usages :

  • Les robots : Googlebot, GPTBot, ClaudeBot, les outils SEO… lisent vos pages sans charger k.js. Votre serveur transmet leurs passages, qui alimentent le rapport Robots.
  • Les événements serveur : paiement confirmé par webhook, abonnement, commande saisie en administration. Transmis avec l’IP et le User-Agent du visiteur, ils rejoignent la visite commencée dans son navigateur.

Sur WordPress, le plugin fait déjà tout cela : cette page concerne les autres sites.

Créer une clé d’API#

  1. Ouvrez la collecte côté serveur

    Installation › Collecte côté serveur › Clés d’API (propriétaires et administrateurs du site).

  2. Nommez la clé et créez-la

    Par exemple « Serveur de production ». Elle commence par ki_live_.

  3. Copiez-la tout de suite

    Elle ne s’affiche qu’une fois : seule son empreinte est conservée.

  4. Rangez-la dans une variable d’environnement

    KREOMNIS_API_KEY, sur le serveur uniquement.

La clé ne quitte jamais le serveur

Jamais dans le code envoyé au navigateur, jamais dans une variable préfixée NEXT_PUBLIC_, jamais dans un fichier servi par le web ni dans un dépôt de code. Une clé exposée se révoque dans la même page : elle cesse de fonctionner en 10 secondes au plus. Vingt clés actives au plus par site.

Next.js#

Un seul fichier à la racine du projet (ou dans src/) : middleware.ts pour Next.js 13 à 15, proxy.ts pour Next.js 16. L’envoi part en arrière-plan (event.waitUntil) : la réponse n’attend jamais Kreomnis. Seuls les robots sont transmis, sans la partie ?… de l’adresse.

proxy.ts ou middleware.tsts
// middleware.ts (Next.js 13 à 15) ou proxy.ts (Next.js 16), à la racine du projet (ou dans src/).
// Les robots n'exécutent pas le JavaScript : on les compte côté serveur, sans ralentir la réponse.
// Clé : KREOMNIS_API_KEY dans les variables d'environnement du serveur (jamais préfixée NEXT_PUBLIC_).
// Vous avez déjà un middleware ? Copiez kreomnisBots et appelez-la au début du vôtre.
import { NextResponse, type NextFetchEvent, type NextRequest } from 'next/server';

const KREOMNIS_API = 'https://kreomnisindexation.com/api/v1/events';
const BOT = /bot|crawl|spider|slurp|preview|fetch|scan|monitor|facebookexternalhit|whatsapp|curl|wget|python|go-http|java\/|okhttp|axios|headless|gpt|claude|perplexity|bytespider|ahrefs|semrush|lighthouse/i;

function kreomnisBots(req: NextRequest, event: NextFetchEvent) {
  const key = process.env.KREOMNIS_API_KEY;
  const ua = req.headers.get('user-agent') || '';
  if (!key || (ua && !BOT.test(ua))) return;
  const host = req.headers.get('x-forwarded-host') || req.headers.get('host') || req.nextUrl.host;
  const ip = (req.headers.get('x-real-ip') || req.headers.get('x-forwarded-for')?.split(',')[0] || '').trim() || null;
  event.waitUntil(fetch(KREOMNIS_API, {
    method: 'POST',
    headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'pageview', url: `https://${host}${req.nextUrl.pathname}`, user_agent: ua, ip, method: req.method }),
    signal: typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(3000) : undefined,
  }).then(() => {}, () => {}));
}

export default function proxy(req: NextRequest, event: NextFetchEvent) {
  kreomnisBots(req, event);
  return NextResponse.next();
}

// Fichiers statiques et internes exclus : seules les pages passent par ici.
export const config = {
  matcher: ['/((?!_next/static|_next/image|api/|favicon.ico|.*\\.(?:png|jpe?g|gif|webp|avif|svg|ico|css|js|map|woff2?|ttf|mp4|webm|pdf)$).*)'],
};

Vous avez déjà un middleware ? Copiez la fonction kreomnisBots et appelez-la au début du vôtre.

Node.js et Express#

Pour Express 4 ou 5 (Node 18 et plus). Le passage du robot est envoyé une fois la réponse terminée (res.on('finish')), avec un délai de 3 secondes au plus et les erreurs ignorées. Derrière un proxy ou un CDN, activez app.set('trust proxy', true) pour que req.ip soit celle du visiteur.

server.jsjs
// Express 4 ou 5 (Node 18+) : robots et événements serveur, envoyés après la réponse.
// Clé : KREOMNIS_API_KEY dans les variables d'environnement du serveur, jamais dans le code navigateur.
// Derrière un proxy ou un CDN : app.set('trust proxy', true) pour que req.ip soit celle du visiteur.
const KREOMNIS_API = 'https://kreomnisindexation.com/api/v1/events';
const KREOMNIS_BOT = /bot|crawl|spider|slurp|preview|fetch|scan|monitor|facebookexternalhit|whatsapp|curl|wget|python|go-http|java\/|okhttp|axios|headless|gpt|claude|perplexity|bytespider|ahrefs|semrush|lighthouse/i;

function kreomnis(event) {
  const key = process.env.KREOMNIS_API_KEY;
  if (!key) return;
  try {
    fetch(KREOMNIS_API, {
      method: 'POST',
      headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
      body: JSON.stringify(event),
      signal: AbortSignal.timeout(3000),
    }).catch(() => {});
  } catch (e) { /* jamais d'erreur visible pour le site */ }
}

app.use((req, res, next) => {
  const ua = req.get('user-agent') || '';
  if (!ua || KREOMNIS_BOT.test(ua)) {
    // Origine et chemin seulement : la partie ?… peut contenir des données personnelles.
    res.on('finish', () => kreomnis({
      name: 'pageview', url: `${req.protocol}://${req.get('host')}${req.originalUrl.split('?')[0]}`,
      user_agent: ua, ip: req.ip, status: res.statusCode, method: req.method,
    }));
  }
  next();
});

// Achat confirmé côté serveur (ip et user_agent du visiteur pour rattacher sa visite) :
function kreomnisAchat(req, commande) {
  kreomnis({
    name: 'purchase', url: `${req.protocol}://${req.get('host')}/merci`,
    ip: req.ip, user_agent: req.get('user-agent'), order_id: String(commande.id),
    revenue: { amount: commande.total, currency: 'EUR' }, items: commande.lignes,
  });
}

PHP#

Pour un site PHP sans WordPress (PHP 7.4 et plus, extension curl), en tête de index.php. La page part d’abord (fastcgi_finish_request ou litespeed_finish_request quand ils existent), l’appel à Kreomnis se fait ensuite, avec un délai court.

index.phpphp
<?php
// En tête de votre index.php (site PHP sans WordPress) : compte les robots, après l'envoi de la page.
// Clé : variable d'environnement KREOMNIS_API_KEY (jamais dans un fichier servi par le web).
if (!function_exists('kreomnis_event')) {
    function kreomnis_event(array $event) {
        $key = getenv('KREOMNIS_API_KEY') ?: ($_SERVER['KREOMNIS_API_KEY'] ?? '');
        if (!$key || !function_exists('curl_init')) {
            return;
        }
        try {
            $ch = curl_init('https://kreomnisindexation.com/api/v1/events');
            curl_setopt_array($ch, [
                CURLOPT_POST => true,
                CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
                CURLOPT_POSTFIELDS => json_encode($event, JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE),
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_NOSIGNAL => true,
                CURLOPT_CONNECTTIMEOUT_MS => 300,
                CURLOPT_TIMEOUT_MS => 800,
            ]);
            curl_exec($ch);
            if (PHP_VERSION_ID < 80000) {
                curl_close($ch);
            }
        } catch (\Throwable $e) {
            // jamais d'erreur visible pour le site
        }
    }
}
$kreomnis_ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
if ($kreomnis_ua === '' || preg_match('~bot|crawl|spider|slurp|preview|fetch|scan|monitor|facebookexternalhit|whatsapp|curl|wget|python|go-http|java/|okhttp|axios|headless|gpt|claude|perplexity|bytespider|ahrefs|semrush|lighthouse~i', $kreomnis_ua)) {
    register_shutdown_function(function () use ($kreomnis_ua) {
        $status = http_response_code();
        // La page part d'abord ; l'appel à Kreomnis se fait ensuite, hors du temps de réponse.
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (function_exists('litespeed_finish_request')) {
            litespeed_finish_request();
        } else {
            while (ob_get_level() > 0) {
                @ob_end_flush();
            }
            flush();
        }
        // Origine et chemin seulement : la partie ?… peut contenir des données personnelles.
        $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
        kreomnis_event([
            'name' => 'pageview',
            'url' => ($https ? 'https' : 'http') . '://' . ($_SERVER['HTTP_HOST'] ?? '') . strtok($_SERVER['REQUEST_URI'] ?? '/', '?'),
            'user_agent' => $kreomnis_ua,
            'ip' => $_SERVER['REMOTE_ADDR'] ?? null,
            'status' => $status ?: 200,
            'method' => $_SERVER['REQUEST_METHOD'] ?? 'GET',
        ]);
    });
}

Autres langages (HTTP)#

Tout langage capable d’envoyer une requête HTTP peut utiliser l’API. Exemple d’achat confirmé côté serveur :

Terminalsh
# Clé dans une variable d'environnement (jamais en clair dans un script versionné ni côté navigateur)
curl -X POST https://kreomnisindexation.com/api/v1/events \
  -H "Authorization: Bearer $KREOMNIS_API_KEY" \
  -H "Content-Type: application/json" \
  --max-time 3 \
  -d '{
    "name": "purchase",
    "url": "https://VOTRE-SITE/merci",
    "ip": "IP_DU_VISITEUR",
    "user_agent": "USER_AGENT_DU_VISITEUR",
    "order_id": "A-1001",
    "revenue": { "amount": 42.5, "currency": "EUR" },
    "items": [{ "id": "SKU-42", "name": "Pain au levain", "price": 4.5, "quantity": 2 }]
  }'
# Lot : { "events": [ … ] } (100 au plus, 512 ko au plus). Robots : "name": "pageview" avec leur user_agent et "status".
# N'envoyez que l'origine et le chemin de l'URL : la partie ?… peut contenir des données personnelles.

Champs, limites et réponses : voir la référence de l’API.

Bonnes pratiques#

  • Transmettez l’IP et le User-Agent du visiteur (champs ip et user_agent) : sans eux, ce sont l’IP et le User-Agent de votre serveur qui sont utilisés : l’événement ne rejoint pas la visite, et une page vue est en général classée robot.
  • N’envoyez que l’origine et le chemin de l’adresse : la partie ?… peut contenir des données personnelles. Seuls les paramètres utm_* sont exploités.
  • Pages vues serveur : seulement les robots si le traceur est installé. Une page vue serveur avec un User-Agent de navigateur est comptée dans l’audience, et ferait doublon avec celle du traceur.
  • Délai court et erreurs ignorées : comme dans les extraits, l’envoi ne doit jamais retarder ni faire échouer vos pages.

Une question sans réponse ici ? Consultez les questions fréquentes.