Accueil / Articles

Transformer les gestionnaires de routes de Next.js en une couche BFF intentionnelle

Découvrez ce que résout le pattern Backend for Frontend, pourquoi il refait surface dans les applications Next.js, et comment éviter de transformer les gestionnaires de routes en objets surdimensionnés.

6 min de lecture1435 mots

BFF n’est pas du tout une idée nouvelle. De nombreux ingénieurs le développaient déjà sous un autre nom avant que « BFF » ne devienne le terme standard. Ce qui est nouveau, c’est qu’il est réintroduit dans le contexte spécifique des applications Next.js qui se sont progressivement transformées en couches d’agrégation d’API, souvent sans que personne ne le décide intentionnellement. Compte tenu de cette tendance, il vaut la peine d’adopter ce modèle délibérément plutôt que de le rencontrer par hasard.

La nature du problème

Ce scénario se répète constamment sous des formes légèrement différentes. Vous disposez de quelques services backend, certains développés en interne, d’autres étant des API fournies par des tiers ajoutées ultérieurement. Vous avez également plusieurs clients : une application web, une application mobile, et éventuellement un outil d’administration interne. Chacun de ces clients a des exigences différentes. Le client mobile souhaite un envoi de données léger, car il fonctionne souvent sur un réseau peu fiable. Le tableau de bord web exige une réponse entièrement dénormalisée, avec toutes les données déjà combinées, afin d’éviter une série de requêtes successives. L’outil d’administration nécessite des données brutes, non traitées, car les personnes qui l’utilisent cherchent généralement à diagnostiquer un problème.

En sautant la couche BFF, on se retrouve face à l’un de deux résultats, tous deux défavorables. Soit vos services principaux deviennent des points d’accès encombrés et polyvalents qui tentent de satisfaire tous les utilisateurs possibles en même temps, ce qui revient essentiellement à un cimetière de solutions du type « ajoutez simplement un autre paramètre de requête optionnel », soit chaque client est contraint de réimplémenter sa propre logique d’agrégation sur les mêmes services backend. C’est ainsi que l’on se retrouve avec trois définitions légèrement différentes et partiellement erronées du terme « utilisateur actif » dispersées dans trois bases de code distinctes.

Qu’est-ce qu’un BFF vraiment ?

Cette idée revient constamment dans les discussions sur Next.js, car le App Router vous fournit essentiellement une couche BFF gratuitement, à condition de l’utiliser de cette manière. Les gestionnaires de route et les composants serveur s’exécutent déjà sur le serveur, se situent entre l’client et le backend, et disposent déjà de la capacité d’envoyer plusieurs requêtes en parallèle ainsi que de remodeler le résultat combiné avant même qu’il n’atteigne le navigateur. De nombreuses équipes ayant adopté le App Router effectuent déjà des tâches de type BFF sans pour autant les nommer officiellement ou les appliquer de manière cohérente.

// app/api/checkout-summary/route.ts
// A web-specific BFF endpoint that aggregates three backend calls
// into the one shape the checkout screen actually needs
export async function GET(req: Request) {
  const userId = getUserIdFromSession(req);  const [cart, pricing, shippingOptions] = await Promise.all([
    fetch(`${CART_SERVICE}/users/${userId}/cart`).then(r => r.json()),
    fetch(`${PRICING_SERVICE}/quote?userId=${userId}`).then(r => r.json()),
    fetch(`${SHIPPING_SERVICE}/options?userId=${userId}`).then(r => r.json()),
  ]);  // Reshape into exactly what the checkout component renders,
  // nothing more. This is the whole point.
  return Response.json({
    items: cart.items.map((i: CartItem) => ({ id: i.id, name: i.name, qty: i.qty })),
    total: pricing.total,
    currency: pricing.currency,
    shipping: shippingOptions.filter((o: ShippingOption) => o.available),
  });
}

Imaginez maintenant que l’application mobile se connecte directement à ces mêmes trois services et gère elle-même l’agrégation des données en Swift ou Kotlin, reproduisant de manière indépendante la logique déterminant quelles options d’expédition sont disponibles. C’est précisément ainsi que les écarts apparaissent progressivement : deux équipes, deux langages, deux interprétations distinctes de la même règle métier, et tôt ou tard ces interprétations entrent en conflit sans que personne ne s’en aperçoive, jusqu’à ce qu’un ticket de support arrive sur le bureau de quelqu’un.

Là où cela devient politique, et pas seulement technique

La partie difficile de l’adoption correcte d’un BFF a peu à voir avec l’écriture du code. Il s’agit de savoir qui en est le responsable. Un BFF conçu uniquement pour l’équipe web ne fonctionnera que si quelqu’un ayant l’autorité de le modifier rapidement en prend la charge, idéalement l’équipe web elle-même, et non un groupe travaillant sur une plateforme partagée soumis à un calendrier de publication toutes les deux semaines. Si votre couche BFF finit entre les mains d’une équipe backend distincte qui le traite comme un simple autre microservice soumis au même processus d’approbation que tout le reste, vous n’avez pas résolu le goulot d’étranglement initial ; vous l’avez simplement déplacé un peu plus près du client.

C’est aussi pour cette raison que ce schéma s’intègre si naturellement dans une base de code Next.js : les développeurs qui écrivent les gestionneurs de route sont généralement ceux-là mêmes qui créent les composants qui les appellent. Il n’y a pas de ticket à soumettre, pas de transfert entre équipes, et aucune chaîne de déploiement distincte à coordonner. Si vous devez modifier la structure de la réponse du récapitulatif de paiement, vous effectuez cette modification dans la même demande de fusion que le composant qui l’affiche.

Le piège : transformer chaque gestionnaire de route en objet divin

Il existe un mode de défaillance à l’extrémité opposée du spectre qui est rarement mentionné. Une fois qu’une équipe découvre que les gestionnaires de routes peuvent agréger tout ce qu’ils souhaitent, l’envie de créer un seul point d’entrée géant qui restitue l’état initial complet de l’application en une seule requête devient difficile à résister, car moins d’allers-retours semble être un avantage évident. C’est parfois le cas. Mais cela signifie aussi qu’un seul gestionnaire de route finit par gérer six fonctionnalités non liées en même temps, et un petit ajustement au module de livraison vous oblige alors à placer ce code dans le même fichier que celui du widget des points de fidélité, car tous deux ont été regroupés dans une fonction telle que getInitialAppState.

Il existe également un signe d’alerte connexe à surveiller : les points de terminaison BFF qui passent du simple reformatage des données à la réimplémentation de la logique métier. Un gestionnaire de route qui décide seul si un client est éligible à la livraison gratuite, au lieu de se contenter de demander l’information au service de tarification et d’envoyer la réponse, devient ainsi silencieusement une deuxième source de vérité pour une règle déjà présente ailleurs dans le système. La fonction d’un BFF est de traduire et de combiner des données, pas de prendre des décisions. Dès qu’il commence à décider, on revient au même problème de dérive évoqué précédemment, seulement situé un niveau plus près du client, ce qui rend son identification beaucoup plus difficile lors d’une revue de code.

Ma position à ce sujet

Un BFF n’a pas nécessairement besoin d’être traité comme un engagement architectural formel accompagné d’un diagramme et d’une page wiki, bien que ce ne soit pas un problème de le faire si cela correspond au fonctionnement de votre organisation. Pour la plupart des équipes qui travaillent déjà avec Next.js, l’adoption d’un BFF consiste moins à introduire quelque chose de nouveau qu’à donner un nom à ce qui existe déjà : reconnaître que vos gestionnaires de routes fonctionnent comme des BFF, abandonner l’idée qu’il s’agit de routes API génériques, et les structurer en fonction des besoins réels du client plutôt que selon les limites des services backend. Le modèle en lui-même n’a jamais été la partie difficile. Ce qui est difficile, et ce que la plupart des équipes reportent, c’est d’admettre que la logique d’agrégation a besoin d’un véritable cadre, puis de le confier à l’équipe frontend qui en est réellement responsable, au lieu d’attendre que l’écran de paiement nécessite six appels distincts pour s’afficher avant que quelqu’un ne se demande pourquoi.

Lectures complémentaires

  • Création d’interfaces UI pour des agents IA à plusieurs étapes avec Next.js et l’AI SDK — Apprenez comment concevoir une interface d’agent IA prête pour la production en utilisant des outils typés, des boucles à plusieurs étapes et des composants UI génératifs en flux dans Next.js.
  • Génération automatique d’un client API Next.js sécurisé par des types à partir de NestJS Swagger — Apprenez comment éliminer les doublons de types API en utilisant NestJS Swagger et Orval pour générer automatiquement des hooks React Query sécurisés par des types pour Next.js.