écrits/tutorial/2026/07
Tutorial26 juil. 2026·30 min

Sentry dans Next.js 16 : erreurs, traces, logs et observabilité des agents IA

Instrumentez une application Next.js 16 de bout en bout avec Sentry — capturez les erreurs serveur, client et edge, publiez des stack traces lisibles grâce aux source maps, tracez les Server Actions lentes, transmettez des logs structurés, surveillez vos tâches cron et obtenez la visibilité sur le coût de vos agents IA.

Le problème du « ça marche sur ma machine »

Votre application Next.js exécute du code à quatre endroits : le navigateur, le serveur Node.js, le runtime Edge et l'étape de build. Une erreur en production peut venir de n'importe lequel d'entre eux, et par défaut vous l'apprenez d'une seule façon — un utilisateur vous le signale.

Même quand vous avez des logs, ils sont généralement inutilisables. Une stack trace minifiée qui pointe vers chunk-4f2a.js:1:88213 ne vous apprend rien. Un log de fonction Vercel affiche l'exception mais pas les trois requêtes base de données qui l'ont précédée. Et dès que vous ajoutez un agent IA qui effectue six appels d'outils par requête, « la requête était lente » devient une question sans réponse sans le détail au niveau des spans.

Ce tutoriel branche Sentry sur une application Next.js 16 en App Router de façon à combler chacune de ces lacunes. À la fin, vous disposerez de :

  • Des erreurs capturées depuis les trois runtimes, avec des stack traces lisibles remontant à vos sources TypeScript
  • Des traces distribuées qui suivent un clic dans le navigateur jusqu'à votre base de données en passant par une Server Action
  • Des logs structurés rattachés à la trace qui les a produits
  • Le Session Replay pour revoir les 20 secondes précédant un crash
  • Des moniteurs cron qui alertent quand une tâche de fond s'arrête silencieusement
  • Le nombre de tokens, le modèle et le coût de chaque exécution d'agent IA

Prérequis

  • Node.js 20 ou plus récent
  • Un projet Next.js 16 en App Router (Next.js 15.3+ fonctionne aussi — le fichier d'instrumentation client l'exige)
  • Un compte Sentry gratuit avec une organisation et un projet créé pour la plateforme « Next.js »
  • Des bases en TypeScript

Si vous partez de zéro :

npx create-next-app@latest sentry-demo --typescript --app --tailwind
cd sentry-demo

Ce que vous allez construire

Une petite application de tableau de bord avec trois surfaces volontairement fragiles — une Server Action qui interroge une base de données, une route API qui appelle un LLM, et un composant client capable de lever une erreur au rendu — toutes entièrement instrumentées. Chaque étape ci-dessous est additive : vous pouvez vous arrêter à n'importe quel moment et conserver une configuration fonctionnelle.

Étape 1 : installer et connecter le SDK

Installez le SDK :

npm install @sentry/nextjs

Le chemin le plus rapide est l'assistant, qui crée les fichiers de configuration et écrit votre DSN dans .env :

npx @sentry/wizard@latest -i nextjs

L'assistant est pratique, mais il masque ce qui se passe réellement — et quand quelque chose cassera plus tard, vous aurez besoin de le savoir. La suite de ce tutoriel procède manuellement. Si vous avez utilisé l'assistant, lisez quand même et comparez avec ce qu'il a généré.

Ajoutez votre DSN et votre jeton d'authentification dans .env.local :

# Exposable sans risque — un DSN autorise uniquement l'écriture d'événements
NEXT_PUBLIC_SENTRY_DSN="https://examplePublicKey@o0.ingest.sentry.io/0"
 
# À NE PAS exposer. Utilisé uniquement au build, pour téléverser les source maps.
SENTRY_AUTH_TOKEN="sntrys_your_token_here"

Créez le jeton dans Sentry via Settings → Auth Tokens avec les portées project:releases et org:read. Ne le committez jamais — ajoutez .env.local et .sentryclirc à votre .gitignore.

Étape 2 : initialiser les trois runtimes

Sentry a besoin d'une initialisation distincte par runtime, car chacun possède ses propres objets globaux et ses propres intégrations disponibles. Créez quatre fichiers à la racine du projet (ou dans src/ si vous utilisez cette organisation).

instrumentation-client.ts

Ce fichier remplace l'ancien sentry.client.config.ts. Next.js le charge avant tout code client.

// instrumentation-client.ts
import * as Sentry from "@sentry/nextjs";
 
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
 
  // Attache les en-têtes de requête et l'adresse IP aux événements.
  // Désactivez si vous avez des exigences strictes sur les données personnelles.
  sendDefaultPii: true,
 
  // Performance : toutes les transactions en dev, un échantillon en production.
  tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
 
  // Transmet les appels console.* et Sentry.logger.* comme logs structurés
  enableLogs: true,
 
  integrations: [
    Sentry.replayIntegration({
      maskAllText: true,
      blockAllMedia: true,
    }),
    Sentry.feedbackIntegration({ colorScheme: "system" }),
  ],
 
  // Enregistre 10 % des sessions, et 100 % de celles où une erreur survient.
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
 
  environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? "development",
});
 
// Nécessaire pour que les navigations client de l'App Router deviennent leurs propres transactions
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;

Ce dernier export compte davantage qu'il n'y paraît. Sans lui, une navigation douce de /dashboard vers /settings est absorbée par la transaction de chargement de page précédente, et vos données de temps de navigation n'ont plus aucun sens.

sentry.server.config.ts

// sentry.server.config.ts
import * as Sentry from "@sentry/nextjs";
 
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  sendDefaultPii: true,
  tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
  enableLogs: true,
  environment: process.env.VERCEL_ENV ?? "development",
 
  // Envoie un identifiant de release pour associer les traces aux bonnes source maps
  release: process.env.VERCEL_GIT_COMMIT_SHA,
});

sentry.edge.config.ts

Le middleware et toute route déclarant export const runtime = "edge" s'exécutent ici. Le runtime Edge n'a pas les API Node.js, donc plusieurs intégrations sont indisponibles — gardez ce fichier minimal.

// sentry.edge.config.ts
import * as Sentry from "@sentry/nextjs";
 
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
  enableLogs: true,
  environment: process.env.VERCEL_ENV ?? "development",
});

instrumentation.ts

Next.js appelle register() une fois par processus serveur. C'est là que vous choisissez la bonne configuration selon le runtime, et que vous branchez le mécanisme de remontée d'erreurs du framework.

// instrumentation.ts
import * as Sentry from "@sentry/nextjs";
 
export async function register() {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    await import("./sentry.server.config");
  }
  if (process.env.NEXT_RUNTIME === "edge") {
    await import("./sentry.edge.config");
  }
}
 
// Next.js appelle ceci pour toute erreur levée dans un Server Component,
// une Server Action, un route handler ou un middleware.
export const onRequestError = Sentry.captureRequestError;

L'export onRequestError est l'étape la plus fréquemment oubliée. Sans lui, les erreurs levées dans les React Server Components sont avalées par Next.js et n'atteignent jamais Sentry — vous voyez un 500 générique dans vos logs et rien dans votre tableau de bord.

Étape 3 : envelopper next.config.ts

withSentryConfig s'occupe de la moitié « build » du travail : injection du plugin Sentry dans webpack ou Turbopack, téléversement des source maps et, en option, mise en place d'une route tunnel.

// next.config.ts
import type { NextConfig } from "next";
import { withSentryConfig } from "@sentry/nextjs";
 
const nextConfig: NextConfig = {
  // votre configuration existante
};
 
export default withSentryConfig(nextConfig, {
  org: "your-org-slug",
  project: "your-project-slug",
  authToken: process.env.SENTRY_AUTH_TOKEN,
 
  // N'afficher les logs de téléversement que dans la CI
  silent: !process.env.CI,
 
  // Fait transiter les requêtes Sentry par votre propre domaine, pour que les
  // bloqueurs de publicité ne suppriment pas 30 à 50 % de vos événements navigateur.
  tunnelRoute: "/monitoring-tunnel",
 
  sourcemaps: {
    // Téléverse les maps vers Sentry, puis les supprime du bundle déployé
    // pour que personne ne puisse lire vos sources depuis le navigateur.
    deleteSourcemapsAfterUpload: true,
  },
 
  // Retire les logs de debug de Sentry du bundle de production
  disableLogger: true,
 
  // Instrumente automatiquement les tâches Vercel Cron déclarées dans vercel.json
  automaticVercelMonitors: true,
});

Deux de ces options se rentabilisent immédiatement.

tunnelRoute crée une route API sur votre propre domaine qui relaie les événements vers Sentry. Environ un tiers du trafic navigateur passe par un bloqueur de publicité qui reconnaît *.ingest.sentry.io et le bloque purement et simplement : sans tunnel, vos compteurs d'erreurs côté client sont systématiquement faux — et biaisés précisément contre les utilisateurs techniques, ceux qui rencontrent le plus les cas limites.

deleteSourcemapsAfterUpload fait la différence entre des traces lisibles et du code source divulgué. Sentry a besoin des maps ; vos utilisateurs non.

Une précaution : si vous utilisez middleware.ts avec un matcher, excluez la route tunnel, sinon votre middleware s'exécutera à chaque envoi d'événement :

// middleware.ts
export const config = {
  matcher: ["/((?!_next/static|_next/image|monitoring-tunnel|favicon.ico).*)"],
};

Étape 4 : capturer ce que React avale

Next.js expose deux frontières d'erreur qu'il faut brancher manuellement, car React intercepte ces erreurs avant qu'elles n'atteignent le moindre gestionnaire global.

app/global-error.tsx

C'est la dernière ligne de défense — elle attrape les erreurs du layout racine lui-même.

"use client";
 
import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
import NextError from "next/error";
 
export default function GlobalError({
  error,
}: {
  error: Error & { digest?: string };
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);
 
  return (
    <html>
      <body>
        <NextError statusCode={0} />
      </body>
    </html>
  );
}

app/dashboard/error.tsx

Les frontières au niveau d'un segment méritent le même traitement, plus un moyen pour l'utilisateur de vous dire ce qu'il faisait :

"use client";
 
import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
 
export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error, {
      tags: { section: "dashboard" },
    });
  }, [error]);
 
  return (
    <div className="p-8">
      <h2>Une erreur est survenue au chargement du tableau de bord.</h2>
      <button onClick={reset}>Réessayer</button>
      <button onClick={() => Sentry.showReportDialog()}>
        Dites-nous ce qui s'est passé
      </button>
    </div>
  );
}

La propriété digest mérite d'être notée. En production, Next.js remplace les messages d'erreur serveur par un hash opaque avant de les envoyer au navigateur. Sentry capture les deux côtés : rechercher ce digest dans vos issues Sentry relie donc le hash visible par l'utilisateur à la véritable exception serveur.

Server Actions

Les Server Actions qui gèrent elles-mêmes leurs erreurs ont besoin d'une capture explicite, sinon onRequestError ne se déclenche jamais :

"use server";
 
import * as Sentry from "@sentry/nextjs";
import { db } from "@/lib/db";
 
export async function updateProfile(formData: FormData) {
  try {
    await db.user.update({
      where: { id: formData.get("id") as string },
      data: { name: formData.get("name") as string },
    });
    return { ok: true };
  } catch (error) {
    Sentry.captureException(error, {
      tags: { action: "updateProfile" },
      extra: { userId: formData.get("id") },
    });
    return { ok: false, message: "Impossible d'enregistrer votre profil." };
  }
}

Règle empirique : tout bloc catch qui renvoie un message aimable à l'utilisateur est un endroit où une erreur disparaîtrait autrement. Capturez-y.

Étape 5 : des traces qui répondent à de vraies questions

Un échantillonnage à 10 % est un point de départ standard, mais un taux uniforme gaspille votre quota sur du trafic sain et rate les chemins lents et rares. Remplacez tracesSampleRate par un tracesSampler pour un contrôle plus fin :

// sentry.server.config.ts
Sentry.init({
  // ...
  tracesSampler: (samplingContext) => {
    const name = samplingContext.name ?? "";
 
    // Jamais d'échantillonnage sur les health checks — pur bruit
    if (name.includes("/api/health")) return 0;
 
    // Toujours échantillonner le paiement : faible volume, forte valeur
    if (name.includes("/api/checkout")) return 1.0;
 
    // Toujours échantillonner les routes IA pour pouvoir déboguer l'agent
    if (name.includes("/api/agent")) return 1.0;
 
    // Hériter de la décision parente sur les traces distribuées
    if (samplingContext.parentSampled !== undefined) {
      return samplingContext.parentSampled ? 1.0 : 0;
    }
 
    return 0.05;
  },
});

Spans personnalisés

L'instrumentation automatique couvre HTTP, les pilotes de bases de données et le rendu React. Tout ce que vous avez écrit vous-même reste invisible tant que vous ne l'enveloppez pas :

import * as Sentry from "@sentry/nextjs";
 
export async function generateMonthlyReport(orgId: string) {
  return Sentry.startSpan(
    {
      name: "generateMonthlyReport",
      op: "task.report",
      attributes: { orgId },
    },
    async (span) => {
      const rows = await Sentry.startSpan(
        { name: "fetch invoice rows", op: "db.query" },
        () => db.invoice.findMany({ where: { orgId } }),
      );
 
      span.setAttribute("row_count", rows.length);
 
      const pdf = await Sentry.startSpan(
        { name: "render pdf", op: "task.render" },
        () => renderPdf(rows),
      );
 
      return pdf;
    },
  );
}

Un rapport lent n'est plus « l'endpoint a mis 9 secondes » mais « la requête SQL a pris 400 ms et le rendu PDF 8,6 s ». La différence est actionnable. Si vous utilisez déjà OpenTelemetry, Sentry consomme directement les spans OTel ; notre guide de tracing OpenTelemetry pour Next.js couvre cette voie.

Étape 6 : des logs structurés liés aux traces

Avec enableLogs: true, Sentry vous fournit un logger dont la sortie est automatiquement corrélée à la trace active :

import * as Sentry from "@sentry/nextjs";
 
const { logger } = Sentry;
 
export async function processPayment(orderId: string, amountCents: number) {
  logger.info("payment started", { orderId, amountCents });
 
  try {
    const result = await stripe.paymentIntents.create({
      amount: amountCents,
      currency: "usd",
      metadata: { orderId },
    });
 
    logger.info(logger.fmt`payment ${result.id} succeeded for order ${orderId}`);
    return result;
  } catch (error) {
    logger.error("payment failed", {
      orderId,
      code: (error as { code?: string }).code,
    });
    throw error;
  }
}

Pour récupérer les appels console.* existants sans les réécrire, ajoutez l'intégration console :

integrations: [
  Sentry.consoleLoggingIntegration({ levels: ["warn", "error"] }),
],

Laissez console.log hors de cette liste. Des logs de debug au volume de production épuiseront votre quota en une journée, et les règles de qualité de code vous poussent de toute façon vers un vrai logger plutôt que des console.log éparpillés.

Étape 7 : Session Replay sans fuite de données

Le Replay est la fonctionnalité qui transforme « l'utilisateur dit que le bouton ne faisait rien » en une vidéo de 30 secondes montrant exactement cela. C'est aussi celle qui risque le plus de vous causer des ennuis lors d'une revue de confidentialité, alors configurez-la délibérément.

Les valeurs par défaut de l'étape 2 masquent déjà tout le texte et bloquent tous les médias. C'est le bon point de départ : masquer tout, puis démasquer sélectivement ce qui est sans risque.

// Le texte de cet élément sera visible dans les replays
<h1 data-sentry-unmask>Monthly Revenue</h1>
 
// Masquer explicitement une valeur même si le démasquage est actif ailleurs
<span data-sentry-mask>{user.taxId}</span>
 
// Retirer complètement un élément de l'enregistrement
<div data-sentry-block>
  <CreditCardForm />
</div>

Pour les champs de saisie, maskAllInputs vaut true par défaut — laissez-le ainsi. Un replay qui capture un champ mot de passe est un incident de sécurité, pas un outil de débogage.

Étape 8 : observabilité des agents IA

C'est ici que Sentry dépasse largement l'APM classique. Si votre application appelle un LLM — surtout via une boucle d'agent avec appels d'outils — vous devez voir le modèle, le nombre de tokens, le coût, et quel appel d'outil a fait durer l'exécution 40 secondes.

Activez l'intégration Vercel AI dans votre configuration serveur :

// sentry.server.config.ts
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
  enableLogs: true,
  integrations: [
    Sentry.vercelAIIntegration({
      recordInputs: process.env.NODE_ENV !== "production",
      recordOutputs: process.env.NODE_ENV !== "production",
    }),
  ],
});

Activez ensuite la télémétrie sur chaque appel de l'AI SDK. Le functionId est ce qui regroupe les exécutions dans le tableau de bord : gardez-le stable.

// app/api/agent/route.ts
import { generateText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { z } from "zod";
 
export async function POST(req: Request) {
  const { question } = await req.json();
 
  const result = await generateText({
    model: anthropic("claude-sonnet-5"),
    prompt: question,
    tools: {
      lookupOrder: tool({
        description: "Look up an order by its ID",
        inputSchema: z.object({ orderId: z.string() }),
        execute: async ({ orderId }) => db.order.findUnique({ where: { id: orderId } }),
      }),
    },
    stopWhen: ({ steps }) => steps.length >= 8,
    experimental_telemetry: {
      isEnabled: true,
      functionId: "support-agent",
      metadata: { tenant: "acme" },
    },
  });
 
  return Response.json({ answer: result.text });
}

Ce que vous obtenez dans la vue AI Agents : un span par invocation de modèle avec les tokens d'entrée et de sortie, un span par appel d'outil avec ses arguments et sa durée, et un coût calculé par exécution selon la tarification du modèle. Quand une exécution coûte 40 centimes au lieu de 3, vous voyez quelle étape en est responsable.

Deux remarques pour la production. D'abord, mettez recordInputs et recordOutputs à false en production, sauf si vous avez décidé qu'il est acceptable de stocker prompts et complétions — les prompts utilisateurs contiennent régulièrement des données personnelles. Ensuite, les spans d'appels d'outils sont le coupable habituel des agents lents : une simple requête sans index dans un outil exécuté huit fois par run est un motif très courant.

Pour l'évaluation et le versionnage au niveau des prompts plutôt que le tracing d'infrastructure, associez-y l'observabilité LLM avec Langfuse — les deux répondent à des questions différentes et cohabitent sans peine.

Étape 9 : surveiller les tâches de fond

Une erreur qui ne se produit jamais est la plus difficile à remarquer. Si votre tâche de facturation nocturne cesse de s'exécuter, rien ne lève d'exception — la tâche est simplement absente. Les moniteurs cron résolvent cela en alertant sur l'absence.

// app/api/cron/reconcile/route.ts
import * as Sentry from "@sentry/nextjs";
 
export async function GET() {
  return Sentry.withMonitor(
    "nightly-reconcile",
    async () => {
      const count = await reconcileInvoices();
      return Response.json({ reconciled: count });
    },
    {
      schedule: { type: "crontab", value: "0 3 * * *" },
      checkinMargin: 10, // alerter si elle n'a pas démarré avec 10 minutes de retard
      maxRuntime: 30, // alerter si elle dépasse 30 minutes
      timezone: "Africa/Tunis",
    },
  );
}

Sentry sait désormais que la tâche est attendue à 03 h 00 chaque jour et ouvre une issue si une exécution est manquée, échoue ou reste bloquée. Avec automaticVercelMonitors: true de l'étape 3, les tâches déclarées dans vercel.json obtiennent automatiquement leurs moniteurs. Pour de l'orchestration plus lourde, voyez nos tutoriels sur les jobs de fond Trigger.dev v4 et les fonctions durables Inngest, qui remontent tous deux vers Sentry via la même configuration serveur.

Étape 10 : releases et source maps dans la CI

Les stack traces ne sont lisibles que si les source maps téléversées correspondent exactement au bundle déployé. Reliez les deux à un identifiant de release — le SHA du commit est le choix évident :

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # nécessaire pour l'association des commits
 
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
 
      - run: npm ci
 
      - run: npm run build
        env:
          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
          SENTRY_ORG: your-org-slug
          SENTRY_PROJECT: your-project-slug
          NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}

Comme withSentryConfig s'exécute au sein de next build, le téléversement des source maps est automatique dès lors que SENTRY_AUTH_TOKEN est présent. Si la variable manque, le build réussit quand même — il livre simplement, en silence, des traces illisibles. D'où l'importance de silent: !process.env.CI : en CI, vous voulez voir le log de téléversement. Notre tutoriel CI/CD avec GitHub Actions détaille le pipeline autour.

Étape 11 : maîtriser le bruit et le coût

Une installation Sentry par défaut sur une application à fort trafic vous noiera en une semaine. Trois filtres font l'essentiel du travail.

Sentry.init({
  // 1. Écarter les erreurs connues et sans intérêt avant l'envoi
  ignoreErrors: [
    "ResizeObserver loop limit exceeded",
    "Non-Error promise rejection captured",
    /^Network request failed$/,
    "AbortError",
  ],
 
  // 2. Ignorer les erreurs venant de scripts tiers
  denyUrls: [/extensions\//i, /^chrome:\/\//i, /googletagmanager\.com/],
 
  // 3. Nettoyer et filtrer par programme
  beforeSend(event, hint) {
    const error = hint.originalException;
 
    // Ne jamais remonter les redirections d'authentification attendues
    if (error instanceof Error && error.message.includes("NEXT_REDIRECT")) {
      return null;
    }
 
    // Retirer un en-tête d'authentification passé dans les données de requête
    if (event.request?.headers) {
      delete event.request.headers["authorization"];
      delete event.request.headers["cookie"];
    }
 
    return event;
  },
});

NEXT_REDIRECT et NEXT_NOT_FOUND méritent une attention particulière : Next.js implémente redirect() et notFound() en levant une exception. Les versions récentes du SDK les filtrent automatiquement, mais si vous les relevez depuis vos propres blocs catch, elles peuvent réapparaître comme de fausses erreurs. Relancez-les toujours plutôt que de les capturer :

try {
  await doWork();
} catch (error) {
  // Laisser passer intactes les erreurs de contrôle de flux du framework
  if (error instanceof Error && error.message.startsWith("NEXT_")) throw error;
  Sentry.captureException(error);
  throw error;
}

Tester votre implémentation

Ajoutez une route qui échoue volontairement :

// app/api/sentry-check/route.ts
export async function GET() {
  throw new Error("Sentry server test — safe to ignore");
}

Et un bouton côté client :

"use client";
 
export function BreakThings() {
  return (
    <button onClick={() => { throw new Error("Sentry client test"); }}>
      Break things
    </button>
  );
}

Puis déroulez cette liste de vérification :

  1. npm run dev, appelez /api/sentry-check — une issue serveur apparaît en quelques secondes.
  2. Cliquez sur le bouton — une issue client apparaît, avec un replay attaché.
  3. Ouvrez l'issue et vérifiez que la stack trace montre vos sources .ts, pas du code minifié. Sinon, les source maps n'ont pas été téléversées.
  4. Ouvrez Traces et vérifiez qu'une transaction de chargement de page contient des spans enfants pour vos appels base de données.
  5. Ouvrez Logs et vérifiez que vos appels logger.info apparaissent, reliés à la trace.
  6. Déclenchez une route IA et vérifiez que la vue AI Agents affiche le modèle, les tokens et les spans d'outils.
  7. Lancez un build de production et vérifiez que le répertoire _next/static déployé ne contient aucun fichier .map.

Dépannage

Rien n'arrive depuis le navigateur. Presque toujours un bloqueur de publicité. Vérifiez dans l'onglet Réseau la présence d'une requête bloquée vers ingest.sentry.io, puis configurez tunnelRoute.

Les erreurs de Server Components n'apparaissent jamais. Il vous manque export const onRequestError = Sentry.captureRequestError dans instrumentation.ts.

Les stack traces sont minifiées. Soit SENTRY_AUTH_TOKEN était absent au build, soit la valeur de release diffère entre le build et l'exécution. Consultez Settings → Source Maps dans Sentry : la page liste les artefacts téléversés par release.

Les traces n'ont pas de spans enfants. Votre tracesSampleRate vaut 0 dans ce runtime, ou l'instrumentation automatique ne peut pas patcher votre client base de données parce qu'il a été importé avant l'exécution de Sentry.init. Un import paresseux à l'intérieur de la fonction règle généralement le problème.

Le runtime Edge se plaint d'API Node manquantes. Vous avez mis une intégration réservée à Node dans sentry.edge.config.ts. Gardez ce fichier minimal.

Quota épuisé en milieu de mois. Baissez tracesSampleRate, mettez replaysSessionSampleRate à 0 en vous appuyant sur replaysOnErrorSampleRate, et ajoutez les erreurs les plus bruyantes à ignoreErrors. Les taux d'échantillonnage s'appliquent par type d'événement : réglez-les indépendamment.

Pour aller plus loin

  • Ajoutez de l'analytique produit à côté des données d'erreur avec notre tutoriel PostHog analytics et feature flags — le contexte d'un flag sur une issue Sentry vous dit si un déploiement progressif en est la cause.
  • Complétez la détection en amont avec les tests unitaires Vitest et Testing Library pour attraper les régressions avant vos utilisateurs.
  • Étendez l'instrumentation des agents avec le guide Vercel AI SDK 7, harness et sandbox.
  • Configurez vos règles d'alerte : une règle de détection de pic sur le taux d'erreur et une règle de seuil sur la durée p95 des transactions couvrent la plupart des incidents réels.

Conclusion

La configuration présentée ici représente environ quarante lignes réparties sur cinq fichiers, et elle change la forme de tous les incidents de production que vous aurez à traiter. Au lieu de reconstituer ce qui s'est passé à partir de signalements d'utilisateurs et de grep, vous ouvrez une issue et vous y trouvez la stack trace, la cascade de la trace, les logs de cette requête précise, et un replay de la session de l'utilisateur.

Trois choses valent la peine d'être faites aujourd'hui même si vous laissez tout le reste de côté : exporter onRequestError pour que les erreurs de Server Components soient capturées tout court, définir tunnelRoute pour que vos chiffres navigateur soient honnêtes, et confirmer que les source maps sont bien téléversées. Ces trois points expliquent l'essentiel de la différence entre une installation Sentry qui fonctionne et une qui, silencieusement, ne fonctionne pas.