Noqta
  • Accueil
  • Services
  • À propos
  • Écrits
  • Se connecter
écrits/tutorial/2026/05
● Tutorial15 mai 2026·28 min

Construire un système de notifications in-app complet avec Novu et Next.js 15

Apprenez à construire un système de notifications in-app prêt pour la production avec Novu v2 et Next.js 15. Ce tutoriel couvre les workflows-as-code, le composant React Inbox, les canaux email, les mises à jour en temps réel via WebSocket et le déploiement.

Noqta Team
Noqta Team
Author
·EN · FR · AR

Les applications SaaS modernes ont besoin de notifications fiables et multi-canaux — alertes in-app, emails, messages push, le tout coordonné et tracé. Construire cela de zéro signifie jongler avec des serveurs WebSocket, des fournisseurs email, des schémas de notifications et des compteurs de non-lus. Novu résout tout cela avec une seule plateforme open source.

Avec Novu v2, vous définissez les workflows de notifications directement en TypeScript, au même endroit que le code de votre application. Il n'y a pas d'interface drag-and-drop à parcourir — vos workflows sont versionnés, type-safe et testables comme n'importe quelle logique métier. Novu gère l'infrastructure : livraison en temps réel via WebSocket, fan-out multi-canaux, logique de retry et un composant React Inbox prêt à l'emploi qui s'intègre dans n'importe quelle application Next.js.

Dans ce tutoriel, vous allez construire une application de gestion des tâches avec un système de notifications complet : les utilisateurs reçoivent des alertes in-app instantanées quand des tâches leur sont assignées, avec des emails de secours s'ils sont hors ligne. Vous utiliserez le moteur workflow-as-code de Novu, un endpoint bridge et le composant React Inbox.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ installé
  • Un projet Next.js 15 fonctionnel avec App Router et TypeScript
  • Une connaissance de base des React Server Components et des API Routes
  • Un compte Novu — inscription gratuite sur novu.co (aucune carte bancaire requise)

Ce que vous allez construire

À la fin de ce tutoriel, votre application aura :

  • Une cloche de notifications dans la barre de navigation avec un badge de comptage non-lus
  • Un panneau Inbox Novu qui s'ouvre au clic sur la cloche, listant toutes les notifications avec support marquer-comme-lu
  • Un workflow task-assigned qui envoie une alerte in-app instantanée puis un email après 10 minutes
  • Une Server Action qui déclenche la notification lors de la création ou l'assignation d'une tâche

Étape 1 : Créer votre compte Novu

Rendez-vous sur novu.co et créez un compte gratuit. Après connexion :

  1. Créez une nouvelle Organisation (ou utilisez celle par défaut)
  2. Ouvrez Settings puis API Keys
  3. Copiez votre Secret Key (commence par novu_secret_)
  4. Copiez votre App Identifier (commence par app_)

Gardez ces valeurs à portée — vous les ajouterez dans les variables d'environnement à l'étape suivante.

Étape 2 : Installer les dépendances Novu

Dans le répertoire de votre projet Next.js, installez les trois packages Novu :

npm install @novu/framework @novu/react @novu/api
PackageRôle
@novu/frameworkMoteur workflow-as-code et helper pour le bridge endpoint
@novu/reactComposant Inbox prêt à l'emploi et hook useNotifications
@novu/apiClient API côté serveur pour déclencher les notifications

Étape 3 : Configurer les variables d'environnement

Créez ou mettez à jour votre fichier .env.local :

# Côté serveur uniquement — ne jamais exposer au navigateur
NOVU_SECRET_KEY=novu_secret_your_key_here
 
# Sûr à exposer au navigateur (préfixe NEXT_PUBLIC_)
NEXT_PUBLIC_NOVU_APP_ID=app_your_identifier_here
 
# URL publique de votre application
NEXT_PUBLIC_APP_URL=http://localhost:3000

Ajoutez les variables sans préfixe dans les secrets de votre hébergeur avant de déployer en production.

Étape 4 : Définir votre workflow de notifications

Le workflow-as-code de Novu v2 vous permet de définir la logique de notifications comme des fonctions TypeScript. Créez un fichier dédié :

// lib/novu/workflows.ts
import { workflow } from '@novu/framework';
import { z } from 'zod';
 
export const taskAssignedWorkflow = workflow(
  'task-assigned',
  async ({ step, payload }) => {
    // Notification in-app instantanée
    await step.inApp('in-app-alert', async () => ({
      subject: `Nouvelle tâche : ${payload.taskTitle}`,
      body: `${payload.assignerName} vous a assigné une tâche avec priorité ${payload.priority}.`,
    }));
 
    // Attendre 10 minutes avant d'envoyer un email
    await step.delay('wait-before-email', async () => ({
      amount: 10,
      unit: 'minutes',
    }));
 
    await step.email('email-fallback', async () => ({
      subject: `Action requise : ${payload.taskTitle}`,
      body: `
        <h2>Vous avez une nouvelle tâche</h2>
        <p><strong>${payload.assignerName}</strong> vous a assigné :</p>
        <h3>${payload.taskTitle}</h3>
        <p>Priorité : ${payload.priority}</p>
        <p>${payload.taskDescription}</p>
        <a href="${payload.taskUrl}" style="color:#7C3AED">Voir la tâche</a>
      `,
    }));
  },
  {
    payloadSchema: z.object({
      taskTitle: z.string(),
      taskDescription: z.string().optional().default(''),
      assignerName: z.string(),
      priority: z.enum(['low', 'medium', 'high']),
      taskUrl: z.string().url(),
    }),
  }
);

Points clés sur ce workflow :

  • L'identifiant 'task-assigned' est ce que vous référencez lors du déclenchement
  • step.inApp() livre une notification instantanée dans la Inbox de l'utilisateur
  • step.delay() met en pause l'exécution 10 minutes — Novu persiste l'état pendant la pause
  • step.email() s'exécute après le délai pour envoyer un email formaté
  • Le payloadSchema avec Zod valide les données de déclenchement au moment de l'exécution

Étape 5 : Créer le bridge endpoint

Novu utilise un bridge endpoint dans votre application pour découvrir et exécuter vos workflows. C'est une route API Next.js standard :

// app/api/novu/route.ts
import { serve } from '@novu/framework/next';
import { taskAssignedWorkflow } from '@/lib/novu/workflows';
 
export const { GET, POST, PUT } = serve({
  workflows: [taskAssignedWorkflow],
});

Après avoir créé ce fichier, synchronisez vos workflows avec Novu Cloud. En développement local, utilisez un tunnel pour exposer votre serveur :

# Exposer le serveur local avec ngrok
ngrok http 3000
 
# Synchroniser avec l'URL du tunnel
npx novu@latest sync --bridge-url https://your-ngrok-url.ngrok.io/api/novu

Après une synchronisation réussie, votre workflow task-assigned apparaît dans le tableau de bord Novu Cloud. Relancez cette commande à chaque ajout ou modification de workflows, ou automatisez-la dans votre pipeline CI/CD.

Étape 6 : Ajouter le composant Inbox

Le composant Inbox de Novu est un composant React prêt à l'emploi qui affiche une cloche avec badge et un panneau déroulant listant toutes les notifications. Ajoutez-le à votre barre de navigation :

// components/NotificationInbox.tsx
'use client';
 
import { NovuProvider, Inbox } from '@novu/react';
 
interface NotificationInboxProps {
  subscriberId: string;
}
 
export function NotificationInbox({ subscriberId }: NotificationInboxProps) {
  return (
    <NovuProvider
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId={subscriberId}
    >
      <Inbox
        appearance={{
          variables: {
            colorPrimary: '#7C3AED',
            colorBackground: '#1F2937',
            colorForeground: '#F9FAFB',
            borderRadius: '0.5rem',
          },
        }}
      />
    </NovuProvider>
  );
}

Le subscriberId est l'identifiant unique de votre utilisateur — généralement son ID en base de données. Novu crée automatiquement un enregistrement subscriber lors de la première notification, sans préinscription nécessaire.

Intégrez-le dans votre composant de navigation :

// components/Navbar.tsx
import { auth } from '@/lib/auth';
import { NotificationInbox } from './NotificationInbox';
 
export async function Navbar() {
  const session = await auth();
 
  return (
    <nav className="flex items-center justify-between px-6 py-4 border-b">
      <span className="text-xl font-bold">TaskFlow</span>
      <div className="flex items-center gap-4">
        {session?.user && (
          <NotificationInbox subscriberId={session.user.id} />
        )}
      </div>
    </nav>
  );
}

Étape 7 : Déclencher les notifications depuis les Server Actions

Créez un utilitaire client Novu pour usage côté serveur :

// lib/novu/client.ts
import { Novu } from '@novu/api';
 
export const novu = new Novu({ secretKey: process.env.NOVU_SECRET_KEY! });

Déclenchez ensuite le workflow depuis une Server Action lors de l'assignation d'une tâche :

// app/actions/tasks.ts
'use server';
 
import { novu } from '@/lib/novu/client';
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
 
export async function assignTask(taskId: string, assigneeId: string) {
  const session = await auth();
  if (!session?.user) throw new Error('Non autorisé');
 
  const task = await db.task.update({
    where: { id: taskId },
    data: { assigneeId },
    include: { assignee: true },
  });
 
  // Ne pas notifier si l'utilisateur s'assigne la tâche à lui-même
  if (assigneeId !== session.user.id) {
    await novu.trigger({
      name: 'task-assigned',
      to: {
        subscriberId: assigneeId,
        email: task.assignee.email,
        firstName: task.assignee.name ?? undefined,
      },
      payload: {
        taskTitle: task.title,
        taskDescription: task.description ?? '',
        assignerName: session.user.name ?? 'Un collègue',
        priority: task.priority,
        taskUrl: `${process.env.NEXT_PUBLIC_APP_URL}/tasks/${taskId}`,
      },
    });
  }
 
  revalidatePath('/tasks');
  return task;
}

Étape 8 : Interface personnalisée avec le hook useNotifications

Pour un contrôle total sur l'interface des notifications, utilisez le hook useNotifications :

// components/CustomNotificationPanel.tsx
'use client';
 
import { useNotifications } from '@novu/react';
import { BellIcon } from 'lucide-react';
import { useState } from 'react';
 
export function CustomNotificationPanel() {
  const [open, setOpen] = useState(false);
  const {
    notifications,
    unreadCount,
    markAllAsRead,
    markAsRead,
    isLoading,
  } = useNotifications();
 
  return (
    <div className="relative">
      <button
        onClick={() => setOpen(!open)}
        className="relative p-2 rounded-full hover:bg-gray-100"
      >
        <BellIcon className="w-5 h-5" />
        {unreadCount > 0 && (
          <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full w-4 h-4 flex items-center justify-center">
            {unreadCount}
          </span>
        )}
      </button>
 
      {open && (
        <div className="absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-xl border z-50">
          <div className="flex justify-between items-center p-4 border-b">
            <span className="font-semibold">Notifications</span>
            <button onClick={markAllAsRead} className="text-sm text-purple-600">
              Tout marquer lu
            </button>
          </div>
 
          {isLoading ? (
            <div className="p-4 text-center text-gray-500">Chargement...</div>
          ) : notifications.length === 0 ? (
            <div className="p-4 text-center text-gray-500">Aucune notification</div>
          ) : (
            <ul className="max-h-96 overflow-y-auto">
              {notifications.map((n) => (
                <li
                  key={n.id}
                  onClick={() => markAsRead(n.id)}
                  className={`p-4 border-b cursor-pointer hover:bg-gray-50 ${
                    !n.isRead ? 'bg-purple-50' : ''
                  }`}
                >
                  <p className="font-medium text-sm">{n.subject}</p>
                  <p className="text-xs text-gray-500 mt-1">{n.body}</p>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}
    </div>
  );
}

Enveloppez ce composant dans NovuProvider exactement comme avec le composant Inbox prêt à l'emploi.

Étape 9 : Préférences de notifications

Permettez aux utilisateurs de contrôler les canaux de notifications avec le composant Preferences intégré :

// components/NotificationSettings.tsx
'use client';
 
import { NovuProvider, Preferences } from '@novu/react';
 
export function NotificationSettings({ subscriberId }: { subscriberId: string }) {
  return (
    <NovuProvider
      applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
      subscriberId={subscriberId}
    >
      <Preferences />
    </NovuProvider>
  );
}

Affichez ce composant sur une page de paramètres utilisateur. Novu expose des bascules par workflow et par canal. Quand un utilisateur désactive les emails pour un workflow spécifique, l'étape email est silencieusement ignorée lors du prochain déclenchement — sans modification de code.

Étape 10 : Tester votre système de notifications

Test local avec Novu Local Studio

Lancez le Local Studio en parallèle de votre serveur de développement :

# Terminal 1
npm run dev
 
# Terminal 2
npx novu@latest dev

Le Local Studio s'ouvre sur http://localhost:2022 et permet de :

  • Déclencher des notifications de test avec des payloads personnalisés
  • Inspecter la sortie de chaque étape du workflow
  • Prévisualiser les templates email avant de connecter un fournisseur
  • Surveiller les logs d'exécution en temps réel

Script de test d'intégration

// scripts/test-notification.ts
import { novu } from '../lib/novu/client';
 
async function main() {
  const result = await novu.trigger({
    name: 'task-assigned',
    to: {
      subscriberId: 'test-user-123',
      email: 'test@yourapp.com',
    },
    payload: {
      taskTitle: "Réviser la page d'accueil",
      taskDescription: "Vérifier la formulation de la section hero.",
      assignerName: 'Script de test',
      priority: 'medium',
      taskUrl: 'http://localhost:3000/tasks/test-001',
    },
  });
 
  console.log('Déclenché :', result);
}
 
main().catch(console.error);
npx tsx scripts/test-notification.ts

Ouvrez votre application — la notification doit apparaître en temps réel sans rechargement de page.

Résolution des problèmes courants

La Inbox ne montre pas les notifications après déclenchement

Vérifiez que le subscriberId passé à NovuProvider correspond exactement au to.subscriberId dans l'appel trigger. Même une différence de casse crée un subscriber différent.

novu sync échoue avec une erreur de connexion

Votre URL bridge doit être accessible depuis Novu Cloud. Utilisez ngrok ou Cloudflare Tunnel pour exposer votre serveur local, puis passez l'URL publique à --bridge-url.

Les mises à jour en temps réel s'arrêtent après un rechargement

La connexion WebSocket est rétablie automatiquement. Si vous constatez un délai important, vérifiez que NEXT_PUBLIC_NOVU_APP_ID est correctement défini dans vos variables d'environnement de production.

L'étape email ne s'exécute pas en production

Connectez un fournisseur email dans le tableau de bord Novu sous Settings → Integrations (Resend, SendGrid, Postmark et d'autres sont supportés). Sans intégration email active, les étapes email sont silencieusement ignorées.

Prochaines étapes

  • Notifications digest — utilisez step.digest() pour regrouper plusieurs mises à jour en un seul email quotidien, réduisant la fatigue des notifications
  • Routage conditionnel — ignorez l'étape email si l'abonné a lu la notification in-app dans la fenêtre de délai
  • Workflows multiples — ajoutez des workflows pour les commentaires, les rappels de délais et les mentions
  • Notifications push — connectez Firebase Cloud Messaging pour étendre votre système aux appareils mobiles
  • Auto-hébergement — Novu est entièrement open source et déployable avec Docker pour un contrôle total des données

Conclusion

Vous avez construit un système de notifications de niveau production avec Novu v2 et Next.js 15. Vos utilisateurs reçoivent des alertes in-app en temps réel alimentées par WebSocket, une interface Inbox soignée sans CSS à écrire, et des emails de secours pour les utilisateurs hors ligne — le tout défini en quelques dizaines de lignes de TypeScript vivant dans votre dépôt. L'approche workflow-as-code de Novu fait des notifications un citoyen de première classe de votre codebase : versionné, testable et facile à étendre à mesure que votre application grandit.

● Tags
#novu#nextjs#typescript#notifications#react#realtime#websocket#intermediate#28 min de lecture
● Partage
● Une question ?

Discutez de cet article avec un agent Noqta.

Noqta Team
Noqta Team
Author · noqta
Suivre ↗

● À lire ensuite

Construire un Agent IA Autonome avec Agentic RAG et Next.js
● Tutorial

Construire un Agent IA Autonome avec Agentic RAG et Next.js

11 févr. 2026
Construire des agents IA from scratch avec TypeScript : maîtriser le pattern ReAct avec le Vercel AI SDK
● Tutorial

Construire des agents IA from scratch avec TypeScript : maîtriser le pattern ReAct avec le Vercel AI SDK

12 févr. 2026
Guide d'Integration de Chatbot IA : Construire des Interfaces Conversationnelles Intelligentes
● Tutorial

Guide d'Integration de Chatbot IA : Construire des Interfaces Conversationnelles Intelligentes

25 janv. 2026
Noqta
Conditions générales · Politique de Confidentialité
Services
  • Automatisation IA
  • Agents IA
  • Automatisation CX
  • Vibe Coding
  • Gestion de Projet
  • Assurance Qualité
  • Développement Web
  • Intégration API
  • Applications Métier
  • Maintenance
  • Low-Code/No-Code
Liens
  • À propos de nous
  • Comment ça marche?
  • Actualités
  • Tutoriels
  • Blog
  • Contact
  • FAQ
  • Ressources
Régions
  • Arabie Saoudite
  • Émirats Arabes Unis
  • Qatar
  • Bahreïn
  • Oman
  • Libye
  • Tunisie
  • Algérie
  • Maroc
Entreprise
  • Noqta, Tunisie, Tunis, téléphone +216 40 385 594
© Noqta. Tous droits réservés.