D-OPEN

Comment configurer l observabilite OpenTelemetry pour des microservices Next.js en 8 etapes

Andreas Kowalski

Andreas Kowalski

SRE senior et mainteneur open source · 21 avril 2026 · 16 min de lecture

TL;DR

  • Guide en 8 etapes pour instrumenter une architecture microservices Next.js 15 avec OpenTelemetry en 2026 : paquets, HTTP, DB, spans custom, OTel Collector, export Jaeger ou Tempo, dashboards SLO, alerting.
  • Temps total d implementation : 10 a 14 heures pour un dev senior, plus 2 semaines d iterations de tuning sampling et alertes.
  • Budget type self-host sur Hetzner pour 5 microservices a 500 req/s : 120 a 180 EUR/mois. Alternative SaaS : 400 a 800 EUR/mois.
  • Focus sur le tail sampling et les exemplars pour garder un cout maitrise sans perdre les traces critiques.

Quand une architecture microservices Next.js commence a grandir, le moment devient critique ou vous ne savez plus pourquoi une requete utilisateur met 2,8 secondes a repondre. Est-ce le service auth qui traine ? Une requete N+1 dans le service catalogue ? Un appel gRPC qui boucle entre deux services ? OpenTelemetry (OTel) repond precisement a cette question en produisant des traces distribuees qui recollent chaque requete bout a bout. Ce guide propose une implementation complete en 8 etapes pour une equipe tech qui part d un Next.js 15 instrumente de zero.

OBSERVABILITE OPENTELEMETRY EN 8 ETAPES12345678De l installation des paquets jusqu a l alerting SLO en production

Etape 1 : installer les paquets OpenTelemetry

Pour une application Next.js 15 en architecture microservices, le socle minimal tient en six dependances. Installez-les dans chaque service Next.js du monorepo :

pnpm add \
  @opentelemetry/api \
  @opentelemetry/sdk-node \
  @opentelemetry/auto-instrumentations-node \
  @opentelemetry/exporter-trace-otlp-http \
  @opentelemetry/resources \
  @opentelemetry/semantic-conventions

Creez ensuite un fichier instrumentation.ts a la racine de chaque app Next.js. C est le hook officiel Next.js qui demarre avant tout code metier. Il est reconnu automatiquement si instrumentationHook est active dans next.config.js (valeur par defaut depuis 15.0). Un piege classique : si vous utilisez Turbopack en dev, assurez-vous d etre en version 15.2+ ou la compatibilite instrumentation est stable.

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./instrumentation.node");
  }
}

Etape 2 : instrumenter les appels HTTP entrants et sortants

Le SDK Node initialise le tracer, le resource et les instrumentations automatiques. C est dans ce fichier que vous definissez le nom du service, sa version et les attributs pertinents.

// instrumentation.node.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: "nextjs-catalogue",
    [ATTR_SERVICE_VERSION]: process.env.APP_VERSION ?? "0.0.0",
    "deployment.environment": process.env.NODE_ENV,
  }),
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://otel-collector:4318/v1/traces",
  }),
  instrumentations: [getNodeAutoInstrumentations({
    "@opentelemetry/instrumentation-fs": { enabled: false },
    "@opentelemetry/instrumentation-http": { ignoreIncomingRequestHook: (req) => req.url?.startsWith("/_next") ?? false },
  })],
});

sdk.start();

Le getNodeAutoInstrumentations active automatiquement HTTP, Express/Fastify, fetch, undici et bien d autres. Deux ajustements importants : desactivez fs (trop bruyant en Next.js a cause des appels webpack) et ignorez les requetes /_next (assets, hot reload). Sans ces reglages, vous inondez votre backend avec 80% de spans inutiles.

Etape 3 : instrumenter les requetes base de donnees

Les bibliotheques databases ne sont pas toutes couvertes par l auto-instrumentation. Pour les stacks modernes (Drizzle, Prisma, Kysely), ajoutez les instrumentations specifiques. Pour Postgres direct, le paquet @opentelemetry/instrumentation-pg fait l affaire. Pour Prisma, activez l exporter natif :

// prisma-client.ts
import { PrismaClient } from "@prisma/client";

export const prisma = new PrismaClient({
  log: [
    { emit: "event", level: "query" },
  ],
}).$extends({
  query: {
    async $allOperations({ operation, model, args, query }) {
      const span = trace.getTracer("prisma").startSpan(`prisma.${model}.${operation}`);
      try {
        const result = await query(args);
        span.setAttribute("db.rows_affected", Array.isArray(result) ? result.length : 1);
        return result;
      } catch (e) {
        span.recordException(e as Error);
        throw e;
      } finally {
        span.end();
      }
    },
  },
});

Deux attributs critiques : db.statement (la requete SQL, optionnelle pour des raisons de confidentialite) et db.rows_affected (tres utile pour detecter les requetes N+1). Un anti-pattern classique est d oublier span.end() dans un bloc catch : les spans restent ouverts, la memoire fuit, le backend rejette les exports.

Etape 4 : ajouter des spans custom pour la logique metier

L auto-instrumentation couvre le technique (HTTP, DB, cache). Pour comprendre le comportement metier, il faut des spans custom. La regle pratique : un span par operation metier nommable, jamais pour une simple fonction utilitaire.

import { trace, SpanStatusCode } from "@opentelemetry/api";

const tracer = trace.getTracer("checkout-service", "1.0.0");

export async function processCheckout(cartId: string) {
  return tracer.startActiveSpan("checkout.process", async (span) => {
    span.setAttribute("cart.id", cartId);
    try {
      const cart = await fetchCart(cartId);
      span.setAttribute("cart.items.count", cart.items.length);
      span.setAttribute("cart.total_cents", cart.totalCents);

      const payment = await chargePayment(cart);
      span.setAttribute("payment.method", payment.method);
      span.addEvent("payment.captured", { amount_cents: payment.amountCents });

      await saveOrder(cart, payment);
      span.setStatus({ code: SpanStatusCode.OK });
      return { orderId: cart.id };
    } catch (error) {
      span.setStatus({ code: SpanStatusCode.ERROR, message: (error as Error).message });
      span.recordException(error as Error);
      throw error;
    } finally {
      span.end();
    }
  });
}

Les trois bons reflexes : setAttribute pour les donnees stables (id, methode), addEvent pour les moments discrets (payment.captured), et setStatus systematique en fin de span pour que les traces d erreur soient filtrables. Sur nos projets chez D-Open, ce pattern augmente de 40% la vitesse de resolution d un incident en production.

Etape 5 : deployer un OTel Collector

Pousser les spans directement de Next.js vers Jaeger ou Tempo est tentant mais fragile. En production, le pattern recommandant est d interposer un OTel Collector qui decouple les services de la backend, gere le batch, la compression, le retry et le sampling. Voici un otel-collector-config.yaml minimaliste mais solide :

receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 5s
    send_batch_size: 512
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow
        type: latency
        latency: { threshold_ms: 500 }
      - name: baseline
        type: probabilistic
        probabilistic: { sampling_percentage: 5 }

exporters:
  otlphttp/tempo:
    endpoint: http://tempo:4318
  prometheus:
    endpoint: 0.0.0.0:8889

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, tail_sampling]
      exporters: [otlphttp/tempo]
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

La partie la plus importante est le tail_sampling. Vous gardez 100% des erreurs, 100% des traces lentes (plus de 500ms), et un echantillon aleatoire de 5% pour le baseline. Resultat typique : vous conservez toutes les traces critiques tout en divisant le volume stocke par 10 a 15. C est ce qui fait la difference entre une facture Tempo/Datadog a 200 EUR et une facture a 2 000 EUR.

Besoin d aide pour deployer votre stack observabilite ?

Nous accompagnons les equipes tech a travers l instrumentation OpenTelemetry, le choix de backend et la mise en place de SLO realistes sur leurs microservices Next.js.

Demander un audit observabilite

Etape 6 : exporter vers Jaeger ou Grafana Tempo

Le choix du backend depend surtout de votre stack existante. Jaeger reste la reference open source historique, simple a deployer avec Docker Compose ou Helm, excellente UI de recherche par trace ID. Son stockage par defaut (Badger, Cassandra, Elasticsearch) est correct jusqu a quelques dizaines de milliers de spans par seconde. Grafana Tempo est conceptuellement different : stockage objet pur (S3, GCS, Azure Blob), pas d index secondaire. La recherche passe par TraceQL, un langage dedie, et par les exemplars exposes dans Prometheus. Si votre equipe est deja sur la stack Grafana (Loki, Mimir, Prometheus), Tempo s integre sans friction.

En 2026, pour une equipe de taille moyenne, le meilleur compromis cout/fonctionnalite est souvent : Tempo + Grafana Cloud Free en phase pilote (jusqu a 50 GB/mois gratuits), puis passage a un self-host Tempo sur Hetzner des que la facture depasse 300 EUR/mois. Ne vous laissez pas enfermer dans Datadog ou New Relic : les couts explosent des que le volume monte, et les donnees appartiennent au fournisseur. Notre guide pour creer un SaaS detaille d ailleurs ce raisonnement cout/souverainete dans un contexte d architecture plus large.

Etape 7 : construire des dashboards et des SLO

Les traces ne servent que si elles sont agregees en metriques exploitables. Le triptyque a mettre en place est : RED (Rate, Errors, Duration) sur chaque service, USE (Utilization, Saturation, Errors) sur chaque ressource, et SLO sur les parcours metier critiques. Les spans OTel produisent via le spanmetrics connector des metriques Prometheus utilisables directement :

# Prometheus rule pour SLO checkout
- record: sli_checkout_good_ratio:5m
  expr: |
    sum(rate(traces_spanmetrics_calls_total{
      service_name="checkout-service",
      span_name="checkout.process",
      span_status_code="STATUS_CODE_OK"
    }[5m]))
    /
    sum(rate(traces_spanmetrics_calls_total{
      service_name="checkout-service",
      span_name="checkout.process"
    }[5m]))

- alert: CheckoutSLOBurnRateHigh
  expr: sli_checkout_good_ratio:5m < 0.995
  for: 5m

Un SLO realiste pour un service de checkout se situe autour de 99.5% de succes, avec une latence P95 inferieure a 800ms. Une fois l SLO fixe, l error budget devient votre outil de priorisation : quand il est consomme a plus de 50%, vous gelez les features et remboursez la dette technique. C est une discipline, pas un outil. Sans cette discipline, les dashboards restent cosmetiques.

Notre avis d expert : les exemplars changent la partie

La vraie magie en 2026 arrive avec les exemplars : chaque point Prometheus pointe vers une trace concrete. Vous regardez un pic de latence P95 dans Grafana, vous cliquez sur l exemplar, vous sautez directement dans la trace Tempo associee, avec le span exact qui a cause le pic. Ce workflow diminue le temps moyen de resolution (MTTR) de 60 a 80% dans nos retours clients. Sans exemplars, vous continuez a jongler entre Grafana, Tempo et les logs : vous perdez 20 minutes par incident.

PIPELINE OBSERVABILITE NEXT.JSNext.js 15SDK + spansOTel CollectorBatch + samplingTempo/JaegerStorage tracesGrafanaDashboards SLOPrometheusspanmetricsAlertmanagerSLO burn rateExemplarsMetric -> traceTrois pieces maitresses pour un MTTR reduit de 60 a 80%

Etape 8 : mettre en place alerting et retention

L alerting sur SLO doit suivre la methode des burn rates multi-fenetres (Google SRE Book). Deux alertes principales par service critique : burn rate rapide (consommation de 2% de l error budget en 1 heure = page immediate) et burn rate lent (consommation de 10% en 6 heures = ticket non urgent). Evitez la surcharge d alertes : un PagerDuty qui sonne 10 fois par nuit n est pas un systeme d alerting, c est un generateur de burn-out.

Pour la retention, le pattern qui fonctionne bien est la hot-warm-cold : 7 jours chauds en stockage rapide (Tempo SSD ou Jaeger Elasticsearch), 30 jours en stockage objet moins cher, puis archivage long terme optionnel. Les logs applicatifs associes via logs-to-traces correlation doivent suivre la meme politique pour garder une coherence lors des post-mortems.

Sur la partie securite et conformite, pensez NIS2 : si vous traitez des donnees europeennes, les traces peuvent contenir des elements personnels. Configurez les span_processors cote Collector pour masquer les donnees sensibles (email, noms, tokens). Nos partenaires WebGuard documentent precisement le volet NIS2 pour les stacks d observabilite. Plug-Tech publie de son cote des retours d experience sur l instrumentation des agents IA qui completent bien ce guide cote metier IA.

Pieges recurrents a eviter

Trois erreurs courantes chez les equipes qui adoptent OpenTelemetry. Instrumenter trop : un span par fonction utilitaire, au lieu d un span par operation metier. Resultat : la UI de trace devient illisible, le cout explose. Oublier le tail sampling : vous payez pour stocker 100% des traces, dont 95% sont sans interet. Ne pas faire de dashboards : vous avez des traces riches mais personne ne les consulte, car rien ne pointe vers elles depuis les metriques business. Investir 2 jours dans 3 dashboards Grafana bien faits transforme l usage de tout le pipeline.

Aller plus loin : la piste open source complete

Pour une equipe qui veut pousser la demarche au bout, la pile 100% open source qui se degage en 2026 est : OpenTelemetry SDK cote app, OTel Collector au centre, Grafana Tempo pour les traces, Mimir pour les metriques, Loki pour les logs, Grafana OSS pour la UI et Alertmanager pour le paging. Aucun composant proprietaire, aucune dependance cloud specifique. Vous pouvez heberger le tout sur Hetzner, Scaleway ou OVH et rester sous 200 EUR/mois jusqu a des charges assez consequentes. Cette pile incarne la meme philosophie que notre guide de creation d agent IA open source : souverainete technique, cout maitrise, portabilite complete.

Migrer vers une observabilite souveraine ?

Nous aidons les equipes tech francaises a quitter Datadog ou New Relic pour une stack OpenTelemetry open source sans perdre en visibilite.

Planifier une migration

FAQ

Next.js 15 supporte-t-il nativement OpenTelemetry ?

Oui. Next.js depuis la version 13.4 integre un support stable de l instrumentation hook, et la version 15 generalise les conventions sementiques OpenTelemetry. Il suffit d exporter une fonction register dans instrumentation.ts a la racine du projet pour brancher le SDK Node.

Faut-il un OTel Collector ou peut-on pousser directement vers le backend ?

En production, il faut un Collector. Il decouple vos services du backend de telemetrie, gere la batch, la compression, le sampling et les retries. Il evite de coupler chaque service a Jaeger ou Tempo, ce qui est indispensable pour un changement de backend sans tout redeployer.

Jaeger ou Grafana Tempo, lequel choisir ?

Jaeger est plus simple a operer, excellent en recherche par trace ID et pour des volumes faibles a moyens. Tempo passe mieux a l echelle, integre parfaitement avec Loki et Prometheus dans la stack Grafana, et offre un stockage objet S3 moins couteux. Pour une equipe deja sur Grafana, Tempo est naturel.

Quel cout prevoir pour une stack OTel en production ?

Pour une stack self-host (Collector + Tempo + Grafana + Prometheus + Alertmanager) qui absorbe 5 microservices Next.js a 500 req/s moyens, comptez 120 a 180 EUR par mois sur Hetzner ou Scaleway. En SaaS (Grafana Cloud Pro, New Relic, Datadog), les factures grimpent rapidement a 400-800 EUR par mois pour le meme volume.