Accueil / Articles

État de l’URL, état du serveur et un BFF : une pile Vite pour les applications React internes

Pourquoi une plateforme React interne authentifiée peut être mieux servie par Vite, TanStack Router, TanStack Query et un BFF distinct que par Next.js, et quand cela cesse d’être vrai.

Éditeur : REACTAPP.TOP10 min de lecture2310 mots

Sources: Next.js is excellent. That’s exactly why you **shouldn’t** use it everywhere.

Lorsqu’on commence un nouveau projet React, la question « Devrions-nous utiliser Next.js ? » reçoit généralement une réponse affirmative avant même que quiconque ait décrit l’application. Pour des sites publics à forte teneur en contenu, cette réaction est souvent justifiée. En revanche, pour une plateforme de gestion nécessitant une authentification, où les utilisateurs passent toute la journée à filtrer des tableaux et à modifier des enregistrements derrière un compte connecté, de nombreuses fonctionnalités puissantes du framework résolvent des problèmes qui n’existent pas dans l’application. Cet article décrit en détail l’architecture réellement nécessaire à une telle plateforme, montre comment un ensemble composable de React, Vite, TanStack Router, TanStack Query ainsi qu’un backend-for-frontend (BFF) s’y adapte, et se termine par une liste de contrôle pour déterminer quand Next.js est finalement le choix le plus adapté.

Rien ici ne prouve que Next.js est faible. C’est l’un des meilleurs frameworks React disponibles. L’argument porte plutôt sur le fait de ne pas l’adopter par défaut, sans vérifier si ses capacités correspondent aux besoins.

Un scénario concret

Imaginons une petite équipe de cinq ingénieurs qui développent une plateforme interne comptant plus de 100 routes, plus de 300 points d’entrée API et environ un million d’appels API par mois. Ils pourraient la construire avec Next.js. Au lieu de cela, ils choisissent React avec Vite, TanStack Router pour le routage, TanStack Query pour la gestion des données, ainsi qu’un BFF distinct entre l’interface utilisateur et les services backend. Six mois après le début du développement, ce choix s’avère judicieux, non pas parce que Next.js aurait échoué, mais parce que la plateforme ne sollicite que très peu les domaines où Next.js excelle.

Pour une comparaison plus approfondie des deux approches selon les types de projets, consultez Next.js ou React pur : choisir le framework adapté à votre projet. L’objectif ici est plus ciblé : déterminer ce dont a besoin une application authentifiée et gourmande en données de chaque couche.

Décrivez l’application avant de choisir le framework

Next.js est devenu la solution standard car il regroupe de nombreuses fonctionnalités : routage et maquettes basés sur des fichiers, SSR et composants serveur, une couche de mise en cache, du middleware, du code backend dans le même projet, des images optimisées ainsi qu’un écosystème très développé. Pour de nombreux produits, cette intégration est idéale. Cependant, la première question plus pertinente concerne le type d’application que vous souhaitez créer. Deux applications React peuvent ne pas avoir grand-chose en commun sur le plan architectural.

Une vitrine en ligne

Un site e-commerce public dispose généralement de :

  • mille de pages que n’importe qui peut ouvrir
  • un trafic provenant des moteurs de recherche
  • des exigences réelles en matière de SEO
  • des pages de produits dont le contenu bénéficie du rendu côté serveur
  • des URL publiques partageables et des aperçus sociaux
  • beaucoup de contenu statique ou peu changeant

Une plateforme d’opérations internes

Un outil interne comporte généralement :

  • presque tous les écrans après authentification
  • une recherche de clients, des filtres et des tableaux paginés
  • des formulaires, des tableaux de bord et des vérifications d’accès
  • des mutations fréquentes et des données quasi en temps réel
  • une grande quantité d’état serveur
  • une interface très interactive
  • un niveau API ou BFF déjà existant

Tous deux sont des applications React, mais ils résolvent des problèmes différents, et l’architecture doit le refléter.

Les utilisateurs manipulent l’application plutôt que de la lire

Sur une plateforme interne, un utilisateur se connecte et travaille à l’intérieur de l’application toute la journée. Il n’arrive pas à partir d’un résultat de recherche, ne lit pas d’article et ne parcourt pas de liste de produits. Il effectue des recherches, filtre, classe, ouvre des dialogues, soumet des formulaires, modifie des enregistrements, change de onglet, feuillette des tableaux, met à jour les données et passe d’une entité à une autre liée. Il exécute une application, il ne visite pas un site web, et ce changement modifie les fonctionnalités du framework qui sont importantes.

Le SEO cesse d’être une exigence

Prenez une route comme /customers/12345 qui affiche le profil d’un client authentifié contenant des données sensibles. Vous ne souhaitez absolument pas que les moteurs de recherche l’indexent. Il n’est pas nécessaire que des crawlers le découvrent, ni que la première réponse HTML soit adaptée aux crawlers. La page a de la valeur parce qu’un utilisateur autorisé interagit avec elle, et non parce qu’elle peut être trouvée. L’un des principaux arguments en faveur de Next.js contribue donc peu à cette situation. Il s’agit d’un manque de correspondance entre les exigences et les capacités du framework, et non d’une faille intrinsèque de celui-ci.

Où les composants serveur conviennent, et où ils ne conviennent pas

Les composants serveur constituent un modèle véritablement utile, ce qui rend la question d’autant plus importante. Imaginez une page de gestion des clients composée de plusieurs sections :

Customer
├── Profile
├── Loans
├── Payments
├── Transactions
├── Documents
└── Activity

Dès que la page se charge, l’utilisateur commence à la modifier. Il filtre par exemple la liste des paiements :

Status: Failed
Type: ACH
Date: Last 30 days

Ensuite, ils effectuent de la pagination, du tri, des recherches, ouvrent une transaction et modifient des éléments. L’affichage est constamment réorganisé en fonction des entrées de l’utilisateur et des données fraîches provenant du serveur. Le flux dominant n’est pas ce cycle de type document :

Request → Render document → Read document

Il ressemble beaucoup plus à un cycle d’interaction :

User interaction
       ↓
Application state
       ↓
Server state
       ↓
API
       ↓
Updated UI

Plus vos écrans ressemblent au deuxième diagramme, plus l’architecture côté client détermine la qualité : la manière dont l’état est stocké, comment les données sont mémorisées en cache et la façon dont l’interface réagit aux changements. Les composants serveur peuvent toujours afficher des parties de cette page, mais ils ne suppriment pas la nécessité d’une couche de données solide côté client.

Considérer l’URL comme un état d’application

Supposons qu’un utilisateur chargé des opérations se trouve sur l’onglet paiements d’un client :

/customer/12345/payments

Ils appliquent un ensemble de filtres :

status=failed
type=ach
amountMin=500
page=2

Si ces filtres sont codés dans l’adresse, l’URL devient :

/customer/12345/payments
  ?status=failed
  &type=ach
  &amountMin=500
  &page=2

À ce stade, l’URL n’est plus simplement un moyen de navigation ; elle fait partie de l’état de l’application. Un ingénieur peut la copier à un collègue, qui se retrouve sur le même client, dans la même rubrique, avec les mêmes filtres et la même page de résultats. Elle survit également aux rechargements et fait en sorte que le bouton « Retour » fonctionne comme les utilisateurs l’attendent. C’est précisément dans ce contexte, à travers de nombreuses interfaces, qu’un routeur sécurisé par des types trouve son utilité.

Ce que TanStack Router apporte

TanStack Router considère le routage comme un élément essentiel de la conception. Les routes, les paramètres de chemin et les paramètres de recherche peuvent être définis dans le système de types, ce qui est crucial lorsque l’application compte plus d’une centaine de routes. À cette échelle, le routage n’est plus simplement « quel composant s’affiche ici » ; c’est une partie de l’architecture. Pour une route telle que :

/customer/$customerId/payments

vous souhaitez :

  • des paramètres de chemin clairement définis et typés
  • des paramètres de recherche validés et ayant des types prévisibles
  • des appels de navigation vérifiés par le compilateur, de sorte qu’un paramètre renommé empêche la compilation plutôt que la génération du lien
  • des états de chargement et d’erreur déclarés pour chaque route
  • des mécanismes d’authentification au niveau de la route
  • des liens profonds qui fonctionnent à chaque fois
  • Il est essentiel de noter que rien de tout cela ne dépend du fait qu’une page soit rendue sur un serveur. Cela convient parfaitement à une application interne qui s’exécute entièrement dans le navigateur.

    L’état du serveur est le véritable point central

    Une grande partie d’une application interne importante concerne moins les pages que plutôt des données distantes. Les entités qu’elle lit et écrit constamment ressemblent à ceci :

    Customers
    Loans
    Payments
    Transactions
    Documents
    Users
    Accounts
    Reports
    

    Pour chacune d’entre elles, le frontend doit répondre aux mêmes questions :

    • Ces données sont-elles déjà en mémoire ?
    • Sont-elles devenues obsolètes ?
    • Faut-il les récupérer à nouveau, et quand ?
    • Que montre l’interface utilisateur pendant le chargement ?
    • Que se passe-t-il lorsque une requête échoue ?
    • Un autre composant pourrait-il partager ce résultat ?
    • Quels changements ont lieu après une mutation, et quelles requêtes doivent être invalidées ?
    • Qu’est-ce qui doit apparaître immédiatement lorsque l’utilisateur revient en arrière ?
    • L’interface utilisateur peut-elle être mise à jour de manière optimiste ?

    C’est là la gestion de l’état serveur, et c’est précisément le problème pour lequel TanStack Query a été conçu : un cache indexé par requête, un rechargement en arrière-plan, la suppression des doublons de requêtes identiques, des hooks de mutation et une invalidation ciblée. Avec le routeur qui gère l’état des URL et Query qui gère l’état serveur, chaque couche a une responsabilité claire, et l’état local aux composants se limite à des aspects purement liés à l’interface utilisateur, tels que le fait qu’un dialogue soit ouvert.

    Garder le BFF comme limite de l’API

    Dans ce scénario, le frontend n’appelle pas directement des dizaines de microservices. Il communique avec un BFF, qui à son tour s’adresse aux services concernés. Cette séparation permet au frontend de ne pas connaître les détails d’implémentation du backend, y compris le fait qu’une réponse provienne d’un service ou de cinq. Le BFF gère le contrat API.

    Cela permet également une séparation claire des déploiements : le frontend est constitué d’assets statiques servis depuis un CDN ou un hébergeur statique :

    Frontend
       ↓
    CDN / static hosting
    

    alors que le BFF fonctionne en tant que service indépendant devant les microservices :

    BFF
       ↓
    Microservices
    

    Chaque partie peut être développée et déployée indépendamment. Next.js peut tout à fait être mis en œuvre aux côtés d’un BFF. La vraie question est de savoir si votre framework frontend doit également servir de limite serveur. Lorsque l’organisation dispose déjà d’une couche backend qui assure cette fonction, ajouter une seconde couche au sein du framework frontend double la responsabilité.

    L’authentification reste du côté serveur

    Le flux d’authentification respecte la même séparation. L’utilisateur clique sur « Se connecter » et est redirigé vers Okta. Après une connexion réussie, la fonction de rappel est envoyée au backend, qui conserve le token d’accès du côté serveur, tandis que Redis gère le stockage des sessions. Le navigateur ne contient qu’un identifiant de session dans un cookie sécurisé. Conceptuellement :

    Browser
       │
       │ Sign in
       ▼
     Okta
       │
       │ Callback
       ▼
     BFF / Backend
       │
       ▼
     Redis
    

    L’application React n’est pas l’endroit où réside l’architecture d’authentification ; son rôle est de gérer le produit, tandis que le BFF assure la sécurité et le contrat API. Un avantage supplémentaire est que les tokens d’accès n’atteignent jamais le JavaScript du navigateur, ce qui diminue l’impact d’une faille XSS. Le fait de le faire avec Next.js n’est pas intrinsèquement pire, mais lorsque cette architecture existe déjà au sein de l’organisation, il est simple et clair de la conserver en dehors du frontend.

    Oui, Next.js peut gérer tout cela

    Il le peut. Vous pouvez développer l’application avec Next.js en utilisant des composants clients, TanStack Query à l’intérieur, configurer l’authentification, la connecter au BFF et la déployer comme vous le souhaitez. Les capacités ne sont pas le problème. La meilleure question est de savoir quelle partie du framework vous utiliserez réellement. Si la majeure partie de l’application finit par ressembler à ceci :

    'use client'
            ↓
    Client-side interactions
            ↓
    API calls
            ↓
    TanStack Query
    

    Il convient alors de s’arrêter un instant pour se demander si les fonctionnalités côté serveur justifient de faire de Next.js la limite de votre application, avec ses conventions, son environnement d’exécution et sa voie de mise à jour.

    Chaque composant de la pile composable a une fonction précise

    Pour ce type d’application, des choix par défaut sensés sont React, Vite, TanStack Router et TanStack Query, ainsi que le BFF. Il ne s’agit pas de rassembler des bibliothèques jusqu’à créer accidentellement son propre framework. Chaque élément a un rôle bien défini :

    • Vite fournit les outils de compilation et le serveur de développement.
    • React rend l’interface utilisateur.
    • TanStack Router gère le routage et l’état des URL.
    • TanStack Query gère l’état côté serveur.
    • Le BFF constitue la frontière API et de sécurité.

    Puisque les responsabilités sont claires, vous pouvez remplacer une partie sans devoir repenser toute l’application.

    Les inconvénients de ne pas utiliser Next.js

    La route composable a ses coûts. Vous renoncez à l’expérience « tout inclus » et devez prendre vous-même les décisions que Next.js prendrait à votre place : la structure du projet, les conventions de code-splitting, la manière dont l’hébergeur statique gère les routes côté client, ainsi que le maintien à jour simultané de plusieurs bibliothèques. Les exigences peuvent également évoluer. Si la moitié de l’application doit ensuite devenir publique et que le SEO devient soudainement important, Next.js paraît bien plus attrayant. Il en va de même si vous avez besoin d’un rendu serveur important ou d’une génération statique, si les composants serveur apportent une véritable valeur à votre conception, ou si vous souhaitez réellement associer délibérément le code frontend et serveur dans une seule version déployable. Ainsi, la décision n’est pas « Next.js ou React ». C’est plutôt de déterminer quelle architecture convient à l’application.

    Une liste de contrôle pour choisir

    Au préalable, réfléchissez aux questions suivantes :

    • L’application dispose-t-elle d’une interface publique importante ? C’est un atout majeur pour Next.js.
    • L’SEO est-il important ? Next.js devient alors plus attrayant.
    • Avez-vous besoin du rendu serveur ou de la génération statique ? Next.js gagne également ce point.
    • Les composants serveur ou les actions serveur amélioreraient-ils significativement la conception ? Alors prenez Next.js au sérieux.
    • L’application est-elle principalement authentifiée et très interactive ? Préférez React avec Vite.
    • L’organisation utilise-t-elle déjà un BFF ou une couche API ? Une application client indépendante déployée séparément mérite d’être envisagée.
    • L’état serveur est-il une préoccupation majeure ? TanStack Query devient alors très attrayant.
    • L’état des URL et le routage complexe sont-ils centraux ? TanStack Router devient alors très attrayant.

    Une règle pratique découle de ces réponses. Lorsqu’une application présente une interface publique importante, envisagez sérieusement Next.js. Lorsqu’elle est principalement destinée à des utilisateurs authentifiés, en particulier une plateforme interne ou d’administration, commencez par React et Vite et n’ajoutez que les éléments nécessaires. Aucun de ces stacks n’est globalement supérieur ; l’un convient mieux à un problème donné, une distinction qui disparaît facilement dès qu’un framework devient la solution par défaut.

    En résumé

    La meilleure architecture n’est pas celle qui dispose de la liste la plus longue de fonctionnalités, mais celle dont les fonctionnalités résolvent les problèmes réels que vous rencontrez. Pour un outil d’opérations protégé par un système d’authentification, doté de sa propre couche backend, nécessitant beaucoup d’état serveur, des interactions fréquentes et très peu de contenu public, React avec Vite, TanStack Router et TanStack Query n’est pas un compromis ; c’est un choix délibéré.

    • Classifiez d’abord l’application : site de contenu public ou outil nécessitant une authentification.
    • Considérez l’URL et les données du serveur comme les deux principaux stockages d’état, et attribuez un outil dédié à chacun.
    • Maintenez la frontière entre la sécurité et l’API là où votre organisation en a déjà une.
    • Revoyez cette décision si les pages publiques, le SEO ou l’affichage côté serveur deviennent des exigences réelles.