Changer le thème

Guide complet de surveillance Next.js en production : Sentry, logs et alertes en pratique

Easton editorial illustration: deployment dock

Vendredi soir, 21 h 17, le téléphone vibre.

Le groupe explose — « la page de paiement ne s’ouvre pas », « la commande a échoué », « écran blanc ». En local, tout roule. Les logs serveur ne montrent qu’une ligne « Internal Server Error ». L’utilisateur dit que la page se fige après le clic sur Payer, impossible de reproduire.

Ce week-end-là, j’ai fouillé les logs Vercel, redéployé une version de test, et découvert un timeout intermittent du SDK de paiement tiers en production. Comme chercher une aiguille dans le noir.

Votre app Next.js est fluide en dev, mais en prod c’est la boîte de Pandore — SSR qui renvoie parfois 500, fonctions edge qui plantent sans raison apparente, API qui ralentissent sans savoir où.

La cause est simple : il vous manque une stack de surveillance production complète. Cet article montre pas à pas comment la monter — Sentry, logs structurés, performance, alertes. Pas de théorie creuse : du code copiable. La prochaine fois, vous saurez avant l’utilisateur.

Pourquoi Next.js nécessite une surveillance dédiée

La triple exécution de Next.js

Une app frontend classique ne tourne que dans le navigateur ; les erreurs apparaissent dans DevTools. Next.js est différent — une même application s’exécute à trois endroits distincts :

  • Client (navigateur) : composants React
  • Serveur (Node.js) : SSR, API Routes, Server Actions
  • Edge (Edge Runtime) : middleware, fonctions edge

Un flux de paiement peut enchaîner : validation formulaire client → auth middleware → Server Action → API Route → base de données → rendu client. Une erreur à n’importe quelle étape échappe à la surveillance navigateur classique.

L’an dernier, un bug étrange : « la page charge lentement puis affiche 500 ». Le panneau Network montre la lenteur, pas la cause. Le tracing distribué Sentry a révélé un appel API tiers côté SSR passé de 200 ms à 8 s. Impossible à voir côté client.

L’effet boîte noire du SSR

En cas d’erreur SSR, l’utilisateur ne voit souvent qu’une page 500 nue. Pas de stack trace, pas de contexte.

Pire encore : les erreurs d’hydratation. Vous avez peut-être vu :

Warning: Expected server HTML to contain a matching <div> in <div>

Peu visible en dev, en prod cela peut casser toute l’interactivité. Sans surveillance, vous dépendez du retour « la page ne répond plus ».

Selon Vercel, les erreurs liées au SSR représentent environ 35 % des problèmes Next.js en production. Et ce ne sont que les erreurs — pas les perfs, comme un composant SSR qui ralentit soudainement.

Les quatre piliers d’une surveillance complète

Suivi d’erreurs
Pas seulement capturer l’exception : qui (utilisateur), où (device, navigateur, réseau), quoi (breadcrumbs), avec quels paramètres.

Monitoring performance
LCP > 2,5 s ? API qui ralentit ? Quelle requête DB bloque tout ?

Gestion des logs
Logs structurés, recherche par heure, utilisateur, request ID. Dev : sortie lisible ; prod : plateforme centralisée.

Alertes
Taux d’erreur au-dessus du seuil → notification immédiate ; nouvelle erreur → Slack ; régression perf → alerte auto.

Avec ces quatre piliers, fini de tâtonner en aveugle. On attaque un par un.

Intégration Sentry en pratique — de l’installation à la config avancée

Intégration en 5 minutes

Sentry supporte Next.js depuis longtemps, avec un wizard officiel. Vraiment 5 minutes :

# Installer le SDK
npm install @sentry/nextjs

# Lancer le wizard
npx @sentry/wizard@latest -i nextjs

Le wizard pose quelques questions (DSN, Source Maps, etc.) et crée trois fichiers :

  • sentry.client.config.ts — navigateur
  • sentry.server.config.ts — Node.js serveur
  • sentry.edge.config.ts — Edge Runtime

Il modifie aussi next.config.js avec le plugin webpack Sentry. Un passage du wizard, la base est en place.

Mais ce n’est que le début. La prod demande une config plus fine.

Points clés App Router pour la capture d’erreurs

Avec App Router, quelques zones à ne pas oublier :

Gestion globale des erreurs

Créez app/global-error.tsx, dernier filet de sécurité :

'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    Sentry.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h2>Un problème est survenu</h2>
          <p>Nous avons enregistré cette erreur et la corrigerons au plus vite</p>
          <button onClick={() => reset()}>Réessayer</button>
        </div>
      </body>
    </html>
  );
}

Capture d’erreurs Server Actions

Les Server Actions sont puissantes, mais la gestion d’erreurs est souvent négligée :

'use server';

import * as Sentry from '@sentry/nextjs';

export async function createOrder(formData: FormData) {
  return await Sentry.withServerActionInstrumentation(
    'createOrder',
    {
      recordResponse: true,
    },
    async () => {
      const productId = formData.get('productId');
      const order = await db.order.create({
        data: { productId, userId: getCurrentUserId() },
      });
      return order;
    }
  );
}

Ainsi, toute erreur Server Action remonte automatiquement, avec le temps d’exécution.

Config optimisée pour la production

Après intégration, la facture du premier mois peut surprendre — la config par défaut envoie tout. Ajustez l’échantillonnage :

// sentry.client.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,

  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,

  environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',

  ignoreErrors: [
    'ResizeObserver loop limit exceeded',
    /chrome-extension/,
    /^Non-Error promise rejection/,
  ],
});

Guide tracesSampleRate :

  • UV/jour < 10 000 : 0,2 - 0,5
  • UV/jour 10 000-100 000 : 0,1 - 0,2
  • UV/jour > 100 000 : 0,05 - 0,1

Notre projet (~30 000 UV/jour) est à 0,15 — ~60 % du quota Sentry, bon compromis.

Source Maps : débogable sans exposer le code

Le JS de prod est minifié ; la stack ressemble à :

at r.render (app.js:1:23456)

Illisible. Les Source Maps remappent vers le code source, mais les exposer publiquement fuit le code.

Sentry les reçoit en upload ; le navigateur utilisateur ne les voit pas.

Dans le CI/CD (ex. GitHub Actions) :

# .github/workflows/deploy.yml
- name: Upload Source Maps to Sentry
  env:
    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
    SENTRY_ORG: your-org
    SENTRY_PROJECT: your-project
  run: npm run build

Le plugin Sentry dans next.config.js gère l’upload. Mettez SENTRY_AUTH_TOKEN dans GitHub Secrets, jamais dans le repo.

Fonctions avancées : Session Replay et tracing distribué

Session Replay — rejouer les actions utilisateur comme une vidéo.

Un retour « le bouton Payer ne répond pas » : le replay montrait un iPad paysage, bouton masqué par le clavier virtuel. Invisible dans les logs seuls.

Activation :

import * as Sentry from '@sentry/nextjs';
import { Replay } from '@sentry/nextjs';

Sentry.init({
  integrations: [
    new Replay({
      maskAllText: false,
      blockAllMedia: true,
      maskAllInputs: true,
    }),
  ],
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
});

Tracing distribué — cycle de vie complet d’une requête : clic → API → DB → rendu, durée à chaque étape.

Même config Sentry.init côté client et serveur ; le SDK propage sentry-trace dans les en-têtes.

Contexte personnalisé : des erreurs plus utiles

Par défaut, Sentry sait seulement qu’une erreur s’est produite. Enrichissez :

import * as Sentry from '@sentry/nextjs';

Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username,
});

Sentry.setContext('purchase', {
  orderId: '12345',
  amount: 99.99,
  paymentMethod: 'credit_card',
});

Sentry.setTag('feature', 'checkout');
Sentry.setTag('ab_test', 'variant_b');

Cas réel : une erreur paiement plus fréquente que prévu — contexte Sentry montrait uniquement ab_test: variant_b. Bug dans le nouveau flux A/B ; variante coupée immédiatement.

Gestion des logs — faire travailler vos logs

console.log ne suffit plus

Au début, j’avais des console.log partout. Pratique en debug, inutilisable en prod :

  • Pas de filtrage : retrouver une requête parmi 100 000 lignes ?
  • Pas d’agrégation : combien de requêtes DB > 1 s cette heure ?
  • Pas d’alerte : « Payment failed » dans les logs, personne au courant.

Les logs structurés résolvent ça : JSON avec timestamp, niveau, request ID, user ID. Recherche et agrégation par champ.

Pino vs Winston : lequel choisir

CaractéristiquePinoWinston
PerformanceTrès rapide, async quasi sans coûtUn peu plus lent, suffisant
SimplicitéConfig minimalisteRiche, bon écosystème
ExtensibilitéVia TransportTransports intégrés
CommunautéRecommandé Next.jsBibliothèque historique

Mon avis :

  • Fort trafic (QPS > 1000) : Pino
  • Traitement complexe (multi-format, multi-cible) : Winston
  • Indécis : Pino, la doc Next.js l’utilise

Config Pino en pratique

npm install pino
npm install pino-pretty --save-dev
// lib/logger.ts
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',

  formatters: {
    level: (label) => ({ level: label.toUpperCase() }),
  },

  transport: process.env.NODE_ENV === 'development'
    ? {
        target: 'pino-pretty',
        options: {
          colorize: true,
          translateTime: 'HH:MM:ss',
          ignore: 'pid,hostname',
        },
      }
    : undefined,
});

export { logger };

Dev : sortie colorée ; prod : JSON pour la plateforme de logs.

Utilisation dans une API Route

Attribuez un correlationId à chaque requête :

// app/api/products/route.ts
import { logger } from '@/lib/logger';
import { randomUUID } from 'crypto';

export async function GET(request: Request) {
  const correlationId = request.headers.get('x-correlation-id') || randomUUID();
  const log = logger.child({ correlationId });

  try {
    log.info({ url: request.url }, 'Processing product request');

    const products = await db.product.findMany();

    log.info({ count: products.length }, 'Products fetched successfully');

    return Response.json(products);
  } catch (error) {
    log.error({ error: error.message, stack: error.stack }, 'Failed to fetch products');
    throw error;
  }
}

Même correlationId = toute la chaîne de la requête visible d’un coup.

Bonnes pratiques des niveaux de log

ERROR — action immédiate

  • Échec connexion DB, paiement, logique métier critique
log.error({ error, userId, orderId }, 'Payment processing failed');

WARN — anomalie récupérable

  • Retry API réussi, fallback, quota proche
log.warn({ retryCount: 3 }, 'External API retry succeeded');

INFO — nœuds métier clés

  • Login, commande, changement de config
log.info({ userId, ip }, 'User logged in');

DEBUG — détail de debug

  • Paramètres, états intermédiaires, timings
log.debug({ params }, 'Calling external API');

Prod : INFO par défaut ; DEBUG temporaire en incident.

Agrégation et analyse

Vercel Logs — zéro config si déployé sur Vercel ; 7 jours, recherche limitée.

Datadog — APM + logs + monitoring entreprise.

import { datadogLogs } from '@datadog/browser-logs';

datadogLogs.init({
  clientToken: process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN,
  site: 'datadoghq.com',
  forwardErrorsToLogs: true,
  sampleRate: 100,
});

Logtail/BetterStack — bon rapport qualité-prix, recherche temps réel, alertes.

Perso : Logtail (1 Go/mois gratuit). Équipe : Datadog.

Champs de log essentiels

{
  "timestamp": "2025-12-20T15:00:06.123Z",
  "level": "INFO",
  "correlationId": "abc-123-def",
  "userId": "user_456",
  "action": "create_order",
  "duration": 234,
  "status": "success",
  "metadata": {
    "orderId": "order_789",
    "amount": 99.99
  }
}

Répond à : qui, quand, quoi, résultat.

Monitoring performance — optimiser avec les données

Core Web Vitals : ce que Google regarde

  • LCP : < 2,5 s idéal
  • FID / INP : < 100 ms / < 200 ms
  • CLS : < 0,1

Next.js expose Web Vitals ; dans app/layout.tsx :

'use client';

import { useReportWebVitals } from 'next/web-vitals';

export function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    if (window.Sentry) {
      window.Sentry.captureMessage(`Web Vital: ${metric.name}`, {
        level: 'info',
        tags: {
          web_vital: metric.name,
        },
        contexts: {
          web_vitals: {
            value: metric.value,
            rating: metric.rating,
          },
        },
      });
    }

    fetch('/api/analytics/web-vitals', {
      method: 'POST',
      body: JSON.stringify(metric),
    });
  });

  return null;
}
// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <WebVitalsReporter />
        {children}
      </body>
    </html>
  );
}

Suivi performance API

// app/api/products/[id]/route.ts
import * as Sentry from '@sentry/nextjs';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  return await Sentry.startSpan(
    {
      op: 'api.request',
      name: 'GET /api/products/[id]',
    },
    async () => {
      const product = await Sentry.startSpan(
        {
          op: 'db.query',
          name: 'Fetch product from database',
        },
        async () => {
          return await db.product.findUnique({
            where: { id: params.id },
            include: { reviews: true },
          });
        }
      );

      if (!product) {
        return Response.json({ error: 'Not found' }, { status: 404 });
      }

      const pricing = await Sentry.startSpan(
        {
          op: 'http.client',
          name: 'Fetch pricing from external API',
        },
        async () => {
          const res = await fetch(`https://pricing-api.com/product/${params.id}`);
          return res.json();
        }
      );

      return Response.json({ ...product, pricing });
    }
  );
}

Dans Sentry : requête 450 ms — DB 120 ms, API externe 300 ms, reste 30 ms. Goulot : API externe.

Alertes requêtes lentes

Middleware Prisma :

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
import { logger } from './logger';

const prisma = new PrismaClient();

prisma.$use(async (params, next) => {
  const before = Date.now();
  const result = await next(params);
  const after = Date.now();
  const duration = after - before;

  if (duration > 1000) {
    logger.warn({
      model: params.model,
      action: params.action,
      duration,
      args: params.args,
    }, 'Slow database query detected');

    Sentry.captureMessage('Slow database query', {
      level: 'warning',
      tags: { model: params.model, action: params.action },
      extra: { duration, args: params.args },
    });
  }

  return result;
});

export { prisma };

RUM vs monitoring synthétique

RUM (Real User Monitoring) — données réelles (réseau, devices) ; passif.

Monitoring synthétique — Checkly, Pingdom, accès simulé planifié ; proactif.

Combiné : RUM pour l’UX, synthétique pour disponibilité (login, paiement).

Checkly toutes les 5 min depuis 5 régions sur accueil et login — timeout ou échec → alerte.

Configuration des alertes — détecter en premier

Intégration Slack

Settings → Integrations → Slack dans Sentry. Sans règles, tout part dans le canal = bruit.

  1. Alerts → Create Alert Rule
  2. Conditions :
    • Taux d’erreur : « > 50 erreurs en 10 min »
    • Nouvelle erreur : notification immédiate
    • Régression perf : « P95 API > 1 s »
  3. Action : Slack

Format message :

🚨 Production Error Spike

Project: my-nextjs-app
Environment: production
Error: TypeError: Cannot read property 'id' of undefined
Events: 127 events in 10 minutes

View in Sentry: https://sentry.io/...

Niveaux d’alerte : éviter la fatigue

P0 — critique (immédiat)

  • Service down, paiement, DB déconnectée
  • PagerDuty + Slack @channel

P1 — important (< 1 h)

  • Fonction core, > 100 erreurs/10 min, P95 > 3 s
  • Slack dev

P2 — normal (heures ouvrées)

  • Erreurs mineures, scripts tiers
  • E-mail quotidien

Exemples Sentry :

// P0 : échec paiement
{
  conditions: [
    { type: 'event.tag', key: 'feature', value: 'payment' },
    { type: 'event.level', value: 'error' }
  ],
  frequency: 'every event',
  actions: [
    { type: 'slack', channel: '#critical-alerts', mention: '@channel' },
    { type: 'pagerduty', service: 'payments' }
  ]
}

// P1 : pic d'erreurs
{
  conditions: [
    { type: 'event.count', value: 100, interval: '10m' }
  ],
  frequency: 'once per issue',
  actions: [
    { type: 'slack', channel: '#alerts-dev' }
  ]
}

Réduction du bruit

1. Ignorer le bruit connu

// sentry.client.config.ts
Sentry.init({
  ignoreErrors: [
    /chrome-extension/,
    /moz-extension/,
    /google-analytics/,
    /HMR/,
  ],
  denyUrls: [
    /extensions\//i,
    /^chrome:\/\//i,
  ],
});

2. Fusionner — Issue Grouping Sentry, une notif par issue/10 min.

3. Silence déploiement — « Mute for 10 minutes » pendant le deploy.

4. Fingerprint

Sentry.captureException(error, {
  fingerprint: ['database-connection-error', databaseName],
});

Cas pratique — déploiement d’une stack complète

Architecture e-commerce

L’an dernier, refonte surveillance d’un site e-commerce :

Contexte :

  • ~80 000 UV/jour
  • Pic QPS 3000+
  • Problèmes : paiements intermittents, accueil lent

Architecture :

┌─────────────┐
│   Next.js   │
│   front/SSR │
└──────┬──────┘

       ├─ Sentry (erreurs + perf)
       ├─ Pino (logs structurés) → Datadog
       ├─ Web Vitals → Sentry
       └─ Checkly (synthétique)

Config clé :

  1. Suivi parcours utilisateur
// lib/tracking.ts
import * as Sentry from '@sentry/nextjs';

export function trackCheckoutStep(step: string, data: any) {
  Sentry.addBreadcrumb({
    category: 'checkout',
    message: `Checkout step: ${step}`,
    data,
    level: 'info',
  });
}

trackCheckoutStep('add_to_cart', { productId, price });
trackCheckoutStep('proceed_to_payment', { cartTotal });
trackCheckoutStep('payment_submitted', { method: 'credit_card' });
  1. Monitoring paiement
// app/api/payment/route.ts
export async function POST(request: Request) {
  const log = logger.child({ action: 'payment' });

  try {
    const result = await processPayment(data);

    log.info({ orderId, amount, method }, 'Payment succeeded');

    return Response.json({ success: true, orderId });
  } catch (error) {
    log.error({ error, orderId, userId }, 'Payment failed');

    Sentry.captureException(error, {
      tags: { feature: 'payment', severity: 'critical' },
      level: 'fatal',
    });

    return Response.json({ error: 'Payment failed' }, { status: 500 });
  }
}
  1. Baseline performance
  • LCP accueil < 2 s
  • LCP fiche produit < 2,5 s
  • API /api/products P95 < 500 ms

Dépassement → alerte auto.

Résultats :

  • Détection incident : 40 min → 3 min
  • Échecs paiement : 0,8 % → 0,2 %
  • LCP accueil : 3,2 s → 1,8 s

Checklist surveillance

**Suivi d'erreurs**
- [ ] Sentry configuré et testé
- [ ] Source Maps uploadés
- [ ] global-error.tsx créé (App Router)
- [ ] Server Actions avec gestion d'erreurs
- [ ] Règles d'ignore (bruit filtré)

**Logs**
- [ ] Pino/Winston intégré
- [ ] JSON en production
- [ ] correlationId présent
- [ ] Niveau INFO en prod
- [ ] Plateforme d'agrégation connectée

**Performance**
- [ ] Web Vitals activés
- [ ] Core Web Vitals OK (LCP<2,5s, INP<200ms, CLS<0,1)
- [ ] Tracing sur APIs clés
- [ ] Monitoring requêtes lentes
- [ ] Monitoring synthétique (optionnel)

**Alertes**
- [ ] Slack/e-mail testés
- [ ] Règles par priorité
- [ ] Réduction du bruit
- [ ] Équipe informée du processus
- [ ] Responsable P0 défini

**Amélioration continue**
- [ ] Revue hebdomadaire des données
- [ ] Tendances d'erreurs
- [ ] Détection régressions perf
- [ ] Optimisation régulière des règles

Conclusion

De la réaction passive à la détection proactive : la surveillance vous redonne le contrôle.

Récap :

  • Sentry — erreurs, perf, replay utilisateur
  • Pino — logs structurés, chaîne correlationId
  • Web Vitals — UX et SEO
  • Slack — alertes rapides, niveaux pour éviter la fatigue

Changement de mindset : la surveillance n’est pas un plus, c’est l’airbag de la prod. Vous n’attendez pas l’accident pour l’installer.

Agissez aujourd’hui. Sans aucune surveillance :

  1. Ce week-end : 2 h pour Sentry de base
  2. Semaine suivante : logs structurés + correlationId
  3. Semaine d’après : alertes Slack + baseline perf

Commencez par le minimum, enrichissez ensuite. Après chaque incident : « la surveillance aurait-elle détecté plus tôt ? » Elle deviendra votre meilleur allié.

Partagez avec l’équipe — la surveillance se fait à plusieurs.

Que votre Next.js tienne bon en prod. (La réalité dit autrement — d’où l’importance de surveiller 😄)

Configuration complète de la surveillance Next.js en production

Étapes complètes de l'intégration Sentry aux logs, au monitoring performance et aux alertes

⏱️ Estimated time: 3 hr

  1. 1

    Step 1: Intégrer le suivi d'erreurs Sentry

    Installation :
    ```bash
    npm install @sentry/nextjs
    ```

    Initialisation :
    ```bash
    npx @sentry/wizard@latest -i nextjs
    ```

    Config client :
    ```ts
    // sentry.client.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    Config serveur :
    ```ts
    // sentry.server.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    Points clés :
    • Config client et serveur séparées
    • tracesSampleRate pour contrôler l'échantillonnage
    • Variables d'environnement configurées
  2. 2

    Step 2: Configurer des logs structurés

    Utiliser correlationId pour relier les requêtes :
    ```ts
    // middleware.ts
    import { v4 as uuidv4 } from 'uuid'

    export function middleware(request: NextRequest) {
    const correlationId = request.headers.get('x-correlation-id') || uuidv4()

    const response = NextResponse.next()
    response.headers.set('x-correlation-id', correlationId)

    return response
    }
    ```

    Dans les logs :
    ```ts
    import { headers } from 'next/headers'

    export async function handler() {
    const headersList = headers()
    const correlationId = headersList.get('x-correlation-id')

    console.log({
    correlationId,
    message: 'User action',
    timestamp: new Date().toISOString(),
    })
    }
    ```

    Points clés :
    • ID unique par requête
    • correlationId sur tous les logs
    • Traçabilité de bout en bout
  3. 3

    Step 3: Configurer le monitoring performance

    Sentry APM :
    ```ts
    Sentry.init({
    tracesSampleRate: 1.0,
    integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    ],
    })
    ```

    Monitoring personnalisé :
    ```ts
    const transaction = Sentry.startTransaction({
    op: 'http.server',
    name: 'API Route',
    })

    try {
    await processRequest()
    } finally {
    transaction.finish()
    }
    ```

    Points clés :
    • tracesSampleRate pour l'échantillonnage
    • Surveiller le temps de réponse API
    • Identifier les goulots d'étranglement
  4. 4

    Step 4: Configurer les alertes

    Alertes Sentry :
    • Règles d'alerte dans le Dashboard Sentry
    • Seuils d'erreur
    • Canaux de notification (Slack, e-mail, etc.)

    Intégration Slack :
    ```ts
    // Configurer dans le Dashboard Sentry
    // Webhook URL: https://hooks.slack.com/services/...
    ```

    Alertes e-mail :
    • Configurer dans le Dashboard Sentry
    • Définir les destinataires
    • Conditions d'alerte

    Points clés :
    • Seuils raisonnables
    • Éviter la fatigue d'alerte
    • Répondre rapidement

FAQ

Pourquoi Next.js nécessite-t-il une surveillance dédiée ?
Raison : la triple exécution de Next.js.

Une même application tourne à trois endroits :
• Client (navigateur) : composants React
• Serveur (Node.js) : SSR, API Routes, Server Actions
• Edge (Edge Runtime) : middleware, fonctions edge

La surveillance frontend classique ne voit que les erreurs client.

Effet boîte noire SSR :
• Erreur SSR → page 500 pour l'utilisateur
• Pas de stack trace, pas de contexte
• Le tracing distribué Sentry est nécessaire

Cas réel :
• Retour utilisateur : page lente puis 500
• Network du navigateur montre la lenteur, pas la cause
• Sentry révèle un appel API tiers passé de 200 ms à 8 s côté serveur

Solution : une stack complète couvrant client, serveur et edge.
Comment intégrer Sentry ?
Installation :
```bash
npm install @sentry/nextjs
```

Initialisation :
```bash
npx @sentry/wizard@latest -i nextjs
```

Config client :
```ts
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

Config serveur :
```ts
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

Points clés :
• Config client et serveur séparées
• tracesSampleRate pour l'échantillonnage
• Variables NEXT_PUBLIC_SENTRY_DSN et SENTRY_DSN
Comment configurer des logs structurés ?
Utiliser correlationId pour relier les requêtes :

Génération dans le middleware :
```ts
import { v4 as uuidv4 } from 'uuid'

export function middleware(request: NextRequest) {
const correlationId = request.headers.get('x-correlation-id') || uuidv4()

const response = NextResponse.next()
response.headers.set('x-correlation-id', correlationId)

return response
}
```

Dans les logs :
```ts
import { headers } from 'next/headers'

export async function handler() {
const headersList = headers()
const correlationId = headersList.get('x-correlation-id')

console.log({
correlationId,
message: 'User action',
timestamp: new Date().toISOString(),
})
}
```

Avantages :
• ID unique par requête
• correlationId sur tous les logs
• Traçabilité de bout en bout
• Diagnostic rapide

Point clé : générer dans le middleware, utiliser dans les logs.
Comment configurer le monitoring performance ?
Sentry APM :
```ts
Sentry.init({
tracesSampleRate: 1.0, // 100 % en dev (0.1 recommandé en prod)
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
],
})
```

Monitoring personnalisé :
```ts
const transaction = Sentry.startTransaction({
op: 'http.server',
name: 'API Route',
})

try {
await processRequest()
} finally {
transaction.finish()
}
```

Métriques :
• Temps de réponse API
• Temps de requête base de données
• Appels API tiers
• Temps de chargement page

Points clés :
• tracesSampleRate (0.1 en prod recommandé)
• Surveiller les chemins critiques
• Identifier les goulots
Comment configurer les alertes ?
Alertes Sentry :
• Règles dans le Dashboard Sentry
• Seuils (ex. : >10 erreurs en 5 min)
• Canaux Slack, e-mail, etc.

Slack :
• Webhook URL dans le Dashboard
• Conditions d'alerte
• Test des alertes

E-mail :
• Destinataires et conditions

Règles suggérées :
• Taux d'erreur au-dessus du seuil
• Temps de réponse au-dessus du seuil
• Types d'erreur spécifiques
• Nouvelles erreurs

Points clés :
• Seuils raisonnables, éviter la fatigue
• Réponse rapide

Conseil : démarrer par le suivi d'erreurs de base, enrichir ensuite.
Quelles sont les bonnes pratiques de surveillance ?
Approche progressive :
1. Ce week-end : 2 h pour Sentry et le suivi d'erreurs de base
2. Semaine suivante : logs structurés et correlationId
3. Semaine d'après : alertes Slack et baseline performance

Ne visez pas la perfection d'un coup — commencez par le minimum viable.

Amélioration continue :
• Après chaque incident : la surveillance aurait-elle détecté plus tôt ?
• Ajuster les seuils
• Revoir la config régulièrement

Indicateurs clés :
• Taux d'erreur
• Temps de réponse
• Portée impact utilisateur
• Temps de récupération

Conseils :
• La surveillance est une affaire d'équipe
• Partager les données régulièrement
• Améliorer en continu

Rappel : la surveillance n'est pas un one-shot, c'est un processus continu.

12 min de lecture · Publié le: 20 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog