- Projet
- REX
Comment j'ai développé une application de réservation en ligne sur-mesure pour une plateforme de téléconsultation

Une agence m'a contacté pour le compte d'un client dans le domaine de la téléconsultation pour les couples m'a contacté avec un besoin clair : remplacer son agenda basique par un vrai flow de réservation en ligne sur-mesure, capable de qualifier les besoins, collecter des informations et gérer le paiement en un seul parcours. Deux développeurs, une stack volontairement légère, et un parti pris fort : ne pas sur-architecturer. Voici le retour d'expérience complet sur ce projet en marque blanche.
Un simple agenda ne suffisait plus
Le client proposait déjà de la téléconsultation à destination des couples confrontés à des problématiques variées : communication, crise de couple, sexualité, questions familiales. Le service existait, les professionnels étaient en place, la demande était là. Mais le système de prise de rendez-vous ? Un agenda de type cal.com. Point.
Concrètement, ça voulait dire : pas de personnalisation du parcours, pas de collecte d'informations en amont, et surtout, les professionnels démarraient chaque consultation à froid. Aucune donnée sur le couple, aucune idée de la problématique avant de se retrouver face à eux en visio. Pour un service qui traite de sujets aussi sensibles, c'est un vrai problème.
Quand cal.com atteint ses limites
Ne vous méprenez pas, cal.com fait très bien ce qu'il fait. Mais dès qu'on sort du cas d'usage "je choisis un créneau et c'est réglé", on touche vite aux murs. Ici, le besoin allait bien plus loin : il fallait orienter le couple vers la bonne spécialité, lui poser les bonnes questions, encaisser le paiement, puis trouver un professionnel disponible qui correspond. Tout ça dans un parcours fluide, sans friction.
Le cahier des charges se résumait en une phrase : construire un développement d'application de réservation en ligne sur-mesure qui guide l'utilisateur de A à Z, tout en fournissant un maximum de contexte au professionnel avant la séance.
Le vrai enjeu n'était pas technique, il était humain : comment faire en sorte qu'un couple qui traverse une période difficile arrive en consultation avec le bon professionnel, qui a déjà les bonnes informations ?
React, Supabase et pas de framework fullstack - le choix d'une stack légère
Première décision structurante : la stack. Et c'est là que le réflexe classique aurait été de dégainer un framework fullstack. Next.js, Nuxt, Remix, AdonisJS avec Inertia... les options ne manquent pas. Mais est-ce qu'on en avait besoin ? Non.
L'application est un flow linéaire. Pas de SEO à gérer (ce n'est pas un site vitrine), pas de rendu côté serveur nécessaire, pas de routing complexe. On a choisi React + Vite pour le frontend : léger, rapide à mettre en place, parfaitement adapté à une application standalone. Ajoutez à ça des contraintes de budget et de délai serrées, et le choix devenait évident.
Pour le backend, Supabase cochait toutes les cases. On avait besoin d'une base PostgreSQL, d'une API REST auto-générée, et d'un connecteur natif vers n8n pour la partie orchestration. Pas besoin d'authentification côté Supabase, pas de storage, pas d'edge functions, pas de realtime. On a utilisé Supabase pour ce qu'il fait de mieux : fournir une base de données avec une API prête à l'emploi, sans avoir à coder et maintenir un backend custom.
Est-ce que cette stack aurait tenu sur un projet infiniment plus complexe ? Pas sans effort. Avec du travail elle peut scaler, mais si vous savez dès le départ que la complexité sera élevée, d'autres options seront plus adaptées dès le jour 1. Pour ce cas d'usage, elle était suffisante. Et "suffisant" ne veut pas dire "juste assez". Ça veut dire qu'il reste de la marge si le projet se complexifie. Dans un contexte projet, c'est souvent le meilleur choix.
Avant d'ajouter une brique technique, demandez-vous : est-ce que le volume et le cas d'usage le justifient ? Si la réponse est "pas encore", n'ajoutez rien.
Construire un wizard form dynamique qui ne perd personne en route
Le cœur de l'application, c'est le wizard form. Un formulaire multi-étapes qui guide l'utilisateur à travers : sélection de catégorie, choix de spécialité, questionnaire adapté, paiement, sélection de créneau, confirmation. Six étapes, un seul objectif : que l'utilisateur aille au bout sans décrocher.
L'état de ce wizard est géré avec Zustand. Pourquoi Zustand plutôt que le Context API natif de React ou une autre solution ? Parce qu'il est minimal, sans boilerplate, et qu'il gère parfaitement le cas d'un state partagé entre plusieurs étapes d'un parcours. On stocke la progression, les réponses, les données utilisateur, tout dans un store unique et lisible.
Un questionnaire piloté par la base de données
C'est ici que le projet devient intéressant techniquement. Les questions du questionnaire dynamique ne sont pas hardcodées dans le frontend. Elles sont stockées dans Supabase : les questions, les choix de réponses, leur configuration, tout vit en base de données.
Quand un utilisateur sélectionne une spécialité, le questionnaire correspondant est chargé dynamiquement. Les questions s'affichent une par une, format wizard, avec des réponses à choix unique ou multiple. En termes de volumétrie, on parle de 4 catégories, 5 à 7 spécialités par catégorie, et environ 10 questions par questionnaire. Rien de massif, mais suffisamment varié pour que le hardcoding ne soit pas une option viable.
L'avantage de cette approche est double. D'abord, le client peut faire évoluer ses questionnaires sans qu'on touche au code. Ensuite, ça ouvre la porte à des optimisations basées sur les données, ce qui m'amène au point suivant.
Tracker chaque abandon pour optimiser le flow
Voici un choix qui a l'air anodin mais qui change tout : à chaque réponse du questionnaire, la donnée est envoyée en base de données. Pas à la fin du formulaire. À chaque question.
Pourquoi ? Parce que ça permet de tracker l'avancement de chaque utilisateur dans le wizard et d'identifier précisément les points d'abandon. Si beaucoup d'utilisateurs s'arrêtent à la même question, c'est un signal. Peut-être que la question est mal formulée, trop intrusive, ou qu'il manque un choix de réponse. Sans ce tracking d'abandon, on navigue à l'aveugle.
C'est le genre de fonctionnalité qui ne coûte presque rien à implémenter (une sauvegarde par question au lieu d'une sauvegarde globale en fin de parcours), mais qui apporte une valeur analytique énorme au client. On transforme un simple formulaire en outil d'optimisation continue.
Si vous construisez un wizard multi-étapes, sauvegardez à chaque étape, pas à la fin. Le coût technique est négligeable, mais la visibilité que ça offre sur le comportement utilisateur est inestimable.
Intégrer Stripe Elements sans casser le parcours
Le paiement était un point critique du flow. Beaucoup de parcours de réservation perdent leurs utilisateurs au moment du paiement, souvent parce qu'on les redirige vers une page externe. Le couple est dans un processus sensible, il a répondu à un questionnaire personnel, et là, paf, on le sort du contexte pour l'envoyer sur une page Stripe hébergée.
On a fait le choix d'intégrer Stripe Elements directement dans le wizard. Le formulaire de paiement est embarqué dans le flow, visuellement cohérent avec le reste de l'application, sans aucune redirection. L'utilisateur ne quitte jamais le parcours. C'est transparent, fluide, et ça élimine une source majeure d'abandon.
L'intégration de Stripe Elements dans React est bien documentée et relativement simple. La vraie valeur ici n'est pas technique, elle est UX : en gardant le paiement à l'intérieur du flow, on maintient la confiance et la fluidité du parcours.
Attribution automatique des créneaux et agrégation des disponibilités
Une fois le questionnaire complété et le paiement validé, l'utilisateur arrive sur un calendrier de disponibilités. Ici, les choses se compliquent un peu, parce qu'on ne montre pas les créneaux d'un seul professionnel, on agrège les disponibilités de tous les professionnels correspondant à la spécialité et à la catégorie sélectionnées.
C'est n8n qui gère cette agrégation. Le second développeur de l'équipe a mis en place les workflows d'orchestration : récupération des plannings, synchronisation avec les sources externes, et exposition des créneaux disponibles. L'affichage côté frontend reprend un format familier, un calendrier avec des slots, similaire à ce que propose cal.com.
L'attribution automatique du professionnel se fait au moment de la sauvegarde de la réservation en base. Si plusieurs professionnels sont disponibles sur le même créneau, l'un d'eux est attribué aléatoirement. Simple, efficace, et suffisant pour le volume actuel.
Le casse-tête de la synchronisation des plannings
Le défi principal sur cette partie ? Les créneaux des professionnels étaient gérés par un plugin WordPress sur le site vitrine du client. Le format de données n'était pas optimal, c'est un euphémisme. C'est le second développeur qui a absorbé cette complexité côté n8n, en mettant en place des workflows de synchronisation et d'agrégation qui transforment ces données brutes en créneaux exploitables.
C'est un bon rappel : dans un projet, le travail le plus ingrat est souvent celui qui consiste à faire dialoguer des systèmes existants qui n'ont pas été pensés pour communiquer entre eux.
Et la concurrence sur les créneaux ?
Question légitime : que se passe-t-il si deux utilisateurs sélectionnent le même créneau en même temps ? Pour l'instant, rien de spécial. Le volume d'utilisation est suffisamment faible pour que le problème ne se pose pas concrètement.
Mais on a anticipé. L'évolution identifiée serait d'implémenter un mécanisme de lock temporaire : dès qu'un utilisateur sélectionne un créneau, celui-ci est verrouillé pendant un délai défini (15 minutes, par exemple). Passé ce délai ou en cas d'inactivité, le créneau est libéré et l'utilisateur est redirigé vers l'étape de sélection.
On ne l'a pas implémenté. Et c'est un choix assumé. Construire un système de lock pour un problème qui n'existe pas encore, c'est de l'over-engineering. Le jour où le volume le justifiera, la solution est prête conceptuellement, il n'y aura qu'à la développer.
N'implémentez pas une solution à un problème qui n'existe pas encore. Documentez-la, gardez-la en tête, et construisez-la quand le besoin sera réel.
Un CMS léger sans CMS - les textes modifiables en base
Dernier point technique qui mérite qu'on s'y attarde : la gestion des contenus textuels. Le client avait besoin de pouvoir modifier les textes affichés dans l'application sans passer par un redéploiement. La solution classique serait d'intégrer un CMS headless type Strapi ou Contentful. Mais encore une fois, est-ce que le besoin justifie la complexité ?
On a opté pour un système de CMS léger maison. Tous les textes sont stockés dans Supabase, associés à une clé de page et une clé de texte. Chaque page pré-charge ses textes par clé de page et les affiche selon leur clé de texte. C'est tout. Pas de framework CMS, pas de dépendance supplémentaire, pas d'interface d'administration complexe. Le client modifie ses textes directement en base, et les changements sont visibles immédiatement, sans redéploiement.
Est-ce que ça scale sur un projet avec 200 pages et des workflows éditoriaux ? Non. Est-ce que c'est parfait pour une application avec quelques pages et des textes qui changent occasionnellement ? Absolument.
Ce que ce projet en marque blanche m'a appris
L'application est en production, le client est satisfait. Mais au-delà du livrable, ce projet m'a confirmé quelque chose que je savais intuitivement sans toujours l'appliquer : on n'a pas toujours besoin de sortir une architecture complexe pour répondre à un besoin.
Une application React avec Supabase peut couvrir un cas d'usage complet de manière efficace et maintenable. Pas besoin d'un backend custom, pas besoin d'un framework fullstack, pas besoin d'un CMS headless. Ce qui compte, c'est de choisir ses outils en fonction du problème réel, pas du problème qu'on imagine avoir dans six mois.
Le travail en binôme a aussi été un facteur clé. Deux développeurs avec des périmètres clairement définis : l'un sur l'application React et la co-construction de la base de données, l'autre sur la partie n8n pour l'orchestration et la synchronisation. La base de données a été co-conçue en fonction des besoins de chaque partie, ce qui a évité les allers-retours et les incompréhensions.
Le meilleur outil pour un projet, c'est celui qui est suffisant. Pas celui qui impressionne sur un CV.
Si ce type de retour d'expérience vous parle, que vous soyez développeur curieux, CTO en quête de pragmatisme, ou porteur de projet qui cherche à comprendre comment concevoir un flow de réservation sur-mesure adapté à votre métier, n'hésitez pas à me contacter pour en discuter. J'ai d'autres projets en portfolio qui illustrent cette même approche : comprendre le besoin, choisir la bonne stack, livrer quelque chose qui marche.
Ce projet a été réalisé en marque blanche. Le nom du client et de la plateforme ne sont volontairement pas mentionnés, par respect des engagements de confidentialité. Les choix techniques et l'architecture décrits dans cet article sont fidèles au projet réel.
![2 ans et demi comme [[développeur en startup]] : retour d'expérience chez ncScale](/_next/image?url=https%3A%2F%2Fsupabase.clementmenczynski.fr%2Fstorage%2Fv1%2Fobject%2Fpublic%2Fimages%2Fncscale.webp&w=2048&q=75)