
Développeur Next.js - Freelance
UI Engineering : créer des interfaces haut de gamme avec Next.js
Dans le développement front moderne, on croise souvent des interfaces fonctionnelles, mais qui manquent de qualité perçue.
La qualité d'une interface ne vient pas de la couleur des boutons, mais de la réponse de l'interface aux actions de l'utilisateur.
C'est ici que l'UI Engineering brille.
L’UI Engineer n’optimise pas seulement des mesures objectives comme Lighthouse. Il optimise la perception : ce qui semble instantané, ce qui paraît fluide, ce qui inspire confiance.
L’objectif de cet article : comprendre l’UI Engineering et appliquer ses principes pour créer des interfaces haut de gamme avec Next.js.
Qu'est-ce que l'UI Engineering ?
Définition et positionnement
L'UI Engineering est un rôle qui se situe à l'intersection du design et du développement.
Contrairement au développeur front-end classique qui développe des features, l'UI Engineer se concentre sur la qualité perçue de l'interface utilisateur.
Différences clés :
- Designer UI/UX : Crée les maquettes et définit l'expérience utilisateur. L'UI Engineer traduit ces maquettes en code avec une attention particulière aux détails.
- Développeur front-end : Développe des fonctionnalités et gère la logique métier. L'UI Engineer optimise ce que l'utilisateur ressent lors de l'interaction.
- Développeur full-stack : Travaille sur l'ensemble de la stack. L'UI Engineer se concentre uniquement sur l'interface utilisateur.
Le niveau "senior UI Engineer"
Un senior UI Engineer sait :
- Expliquer pourquoi une UI est bonne ou mauvaise
- Anticiper les problèmes UI avant qu'ils arrivent
- Construire un design system qui tient dans le temps
- Livrer une UI accessible, fluide, cohérente
Parler les deux langues : design & code
L'UI Engineer doit comprendre le design autant que le code. Il doit :
- Comprendre Figma : typographie, hiérarchie visuelle, contrastes
- Identifier les défis Figma : spacing irréalistes, animations impossibles, incohérences
- Aider à structurer le design system côté design
- Créer le pont entre design et code via les design tokens
Cette capacité à parler les deux langues permet de créer des interfaces qui respectent l'intention du design.
Tout en étant techniquement solides.
Feedback optimiste et réactivité de l'interface
La qualité perçue vient de la réponse immédiate de l'interface. L'utilisateur doit sentir que l'application réagit instantanément à ses actions.
Feedback Optimiste (Optimistic UI)
Le feedback optimiste consiste à mettre à jour l'interface avant même que le serveur n'ait répondu. Cela crée une sensation d'instantanéité.
Avec React et Next.js, on peut utiliser le hook useOptimistic pour implémenter ce pattern :
Cette approche améliore drastiquement la perception de la réactivité de l'application.
Le combo CVA + Framer Motion
Pour créer des interactions fluides et cohérentes, on peut combiner CVA (Class Variance Authority) pour définir les états d'interaction et Framer Motion pour les animations subtiles.
Comme le souligne Emil Kowalski :
[...], Another purposeful animation is this subtle scale down effect when pressing a button. It's a small thing, but it helps the interface feel more alive and responsive.
Voici comment combiner les deux :
Cet exemple montre la puissance de CVA : on définit clairement les états d'interaction (intent, size) avec des classes cohérentes, tout en gardant le code maintenable.
Cette combinaison permet de créer des composants avec des états d'interaction clairement définis.
Les animations renforcent le feedback visuel.
Pour approfondir la création d'un design system avec CVA, consultez mon article sur la création d'un Design System.
Skeletons
Au lieu d'afficher un spinner générique, les skeletons imitent exactement la structure finale de la donnée. Cela réduit l'anxiété de l'attente et améliore la perception du temps de chargement.
Évidemment, il convient de créer un composant Skeleton pour encapsuler cette logique.
Pour une transition encore plus fluide, on peut ajouter une animation de transition entre le skeleton et le contenu réel avec Framer Motion :
Cette transition subtile évite le "flash" de contenu et crée une expérience plus fluide.
Performance perçue : optimiser ce que l'utilisateur ressent
L'UI Engineering optimise ce que l'utilisateur ressent, pas juste les métriques Lighthouse.
Les métriques techniques sont importantes pour plein de raisons, mais la perception de l'utilisateur est ce qui compte vraiment.
Image Priority & LCP Optimization (Next.js)
Le LCP (Largest Contentful Paint) mesure le temps nécessaire pour que le plus grand élément visible de la page (image, vidéo, bloc de texte) soit rendu. C'est une métrique cruciale pour la performance perçue.
Ce qui dégrade le LCP :
- Images non optimisées qui se chargent lentement
- Images sans priorité de chargement
- Polices web qui bloquent le rendu
- JavaScript qui bloque le rendu initial
Comment l'améliorer, exemple avec Next.js :
La prop priority indique à Next.js que cette image est critique et doit être chargée en priorité.
Le décodage d'image asynchrone de Next.js évite le "flash" de contenu qui dégrade la qualité perçue. L'image se charge de manière optimisée sans bloquer le rendu.
Layout Instability (CLS) - Next.js
Le CLS (Cumulative Layout Shift) mesure la stabilité visuelle. Il quantifie les décalages inattendus d'éléments pendant le chargement de la page.
Exemple de CLS : Une image sans dimensions définies se charge et "pousse" le contenu en dessous vers le bas. L'utilisateur voit la page "sauter", ce qui est un marqueur fort de non-professionnalisme.
Comment éviter le CLS avec Next.js :
- Réserver l'espace avec
next/imageen spécifiantwidthetheight - Utiliser
aspect-ratiopour les conteneurs dynamiques
Cette approche garantit que l'espace est réservé avant le chargement de l'image, évitant tout décalage visuel.
Hydration Impact (Next.js)
L'hydratation React peut créer un "flash" de contenu qui dégrade la qualité perçue. Ce phénomène survient quand le HTML statique rendu côté serveur diffère du rendu initial côté client, forçant React à "réparer" l'interface.
Le problème : Un composant qui utilise useState ou useEffect pour modifier le rendu initial crée une différence entre le HTML serveur et le HTML client. L'utilisateur voit brièvement le contenu serveur, puis un "saut" quand React prend le contrôle.
Next.js offre plusieurs solutions pour minimiser cet impact :
Server Components : éviter l'hydratation
Les Server Components s'exécutent uniquement côté serveur et ne sont jamais hydratés. Ils réduisent drastiquement la quantité de JavaScript envoyée au client :
Quand utiliser Server Components :
- Contenu statique ou basé sur des données serveur
- Composants qui n'ont pas besoin d'interactivité
- Fetching de données directement dans le composant
Quand utiliser Client Components :
- Composants avec état local (
useState,useReducer) - Composants avec effets (
useEffect) - Composants avec événements utilisateur (
onClick,onChange)
Streaming SSR : affichage progressif
Le Streaming SSR permet d'afficher le contenu progressivement, améliorant la perception du temps de chargement :
Le contenu critique (Header) s'affiche immédiatement, tandis que SlowContent se charge en arrière-plan.
Impact sur le bundle JavaScript
En privilégiant les Server Components, on réduit significativement la taille du bundle JavaScript :
- Avant : Tous les composants sont hydratés → bundle important
- Après : Seuls les Client Components sont hydratés → bundle réduit
Cette réduction améliore non seulement la performance perçue, mais aussi les métriques réelles (Time to Interactive, First Input Delay).
En utilisant correctement ces techniques, on réduit l'impact de l'hydratation sur la qualité perçue et la quantité de JavaScript à charger côté client.
Accessibilité "invisible" (A11y Engineering)
Une interface qui réagit parfaitement au clavier est une interface "haut de gamme". L'accessibilité ne doit pas être une contrainte, mais une opportunité d'améliorer l'expérience pour tous.
Focus Management
Un focus trap empêche le focus de sortir de la modale : quand l'utilisateur appuie sur Tab, le focus reste à l'intérieur de la modale au lieu de revenir à la page en arrière-plan.
Exemple concret : Sans focus trap, un utilisateur qui navigue au clavier peut "sortir" de la modale et interagir avec des éléments inaccessibles visuellement mais toujours présents dans le DOM.
Avec des librairies comme Radix UI ou Headless UI, cette gestion est automatique :
Cette attention aux détails d'accessibilité améliore l'expérience pour tous les utilisateurs, pas seulement ceux qui utilisent le clavier.
Reduced Motion
Respecter la préférence système prefers-reduced-motion est crucial. Certaines animations peuvent rendre les utilisateurs malades. Framer Motion respecte automatiquement cette préférence :
Si l'utilisateur a activé prefers-reduced-motion, Framer Motion réduit ou désactive automatiquement les animations.
Accessibilité visuelle
L'accessibilité passe aussi par les détails visuels : contraste suffisant, focus states visibles, navigation au clavier fluide. Pour approfondir ce sujet, je vous invite à consulter mon guide complet sur l'accessibilité Next.js.
Typographie : éviter les "flashs" de texte
La typographie est souvent négligée par les développeurs, mais elle est cruciale pour les UI Engineers. Un texte qui "clignote" ou qui change d'apparence au chargement dégrade immédiatement la qualité perçue.
FOIT et FOUT : les ennemis de la qualité perçue
Quand une page charge, deux problèmes peuvent survenir avec les polices web :
- FOIT (Flash of Invisible Text) : Le navigateur cache le texte pendant le chargement de la police. L'utilisateur voit une page vide, puis le texte apparaît brutalement.
- FOUT (Flash of Unstyled Text) : Le navigateur affiche d'abord le texte avec une police système (fallback), puis bascule vers la police web une fois chargée. L'utilisateur voit le texte "sauter".
Ces flashs créent une sensation de non-professionnalisme. L'UI Engineer doit les éviter.
Optimiser le chargement avec next/font
Avec Next.js, next/font optimise automatiquement le chargement des polices. Il peut charger des polices depuis Google Fonts ou depuis des fichiers locaux.
Exemple avec Google Fonts :
Les options importantes :
subsets: ["latin"]: Charge uniquement les caractères latins, réduisant la taille du fichier. On peut ajouter["latin", "latin-ext"]si besoin.display: "swap": Affiche immédiatement le texte avec la police fallback, puis bascule vers la police web une fois chargée. Évite le FOIT.preload: true: Précharge la police en priorité, réduisant le délai avant l'affichage, à utiliser avec parcimonie.
Exemple avec une police locale :
next/font optimise automatiquement les polices : il génère les formats nécessaires (woff2, woff), ajoute les preloads, et évite les requêtes externes inutiles.
Antialiasing : lisser le rendu du texte
L'antialiasing est une technique qui lisse les bords des caractères pour éviter l'effet de "pixelisation" sur écran. Sur certains navigateurs (notamment macOS), le texte peut paraître trop fin ou trop épais selon le background.
La propriété CSS font-smoothing permet de contrôler ce rendu :
Cette propriété est souvent ajoutée globalement dans le CSS global de Next.js pour garantir un rendu cohérent. C'est un détail qui améliore la qualité perçue du texte, surtout sur les écrans haute résolution.
Animations
Au-delà du simple hover, les animations peuvent raconter une histoire et guider l'utilisateur. L'UI Engineer sait quand et comment animer pour améliorer l'expérience.
Mais attention : toutes les animations ne sont pas bénéfiques. Une animation mal choisie peut dégrader la performance, distraire l'utilisateur, ou même provoquer des nausées chez certaines personnes.
Quand ne pas animer
Évitez les animations dans ces cas :
- Contenu critique : Les animations ne doivent jamais retarder l'affichage d'informations importantes
- Listes longues : Animer chaque élément d'une liste de 100+ items peut créer des problèmes de performance
- Éléments fréquents : Une animation sur chaque clic de bouton peut devenir agaçante
- Contexte professionnel : Certains environnements (outils médicaux, financiers) nécessitent une interface sobre
Règle d'or : Si l'animation n'apporte pas de valeur claire à l'expérience utilisateur, ne l'ajoutez pas.
Alternatives à Framer Motion
Framer Motion est puissant, mais parfois une simple transition CSS suffit :
Quand utiliser CSS vs Framer Motion :
- CSS transitions : Animations simples (hover, focus, opacity, transform). Plus performant, pas de JavaScript.
- Framer Motion : Animations complexes (layout transitions, stagger, gestes). Plus de contrôle, mais plus de JavaScript.
Pour des animations simples, privilégiez CSS. Pour des interactions complexes, Framer Motion est justifié.
Impact sur les performances
Les animations peuvent impacter les performances, surtout sur mobile :
Bonnes pratiques :
- Utilisez
transformetopacityplutôt quewidth,height,top,left(ces propriétés déclenchent des reflows) - Limitez le nombre d'éléments animés simultanément
- Utilisez
will-changeavec parcimonie (uniquement pour les éléments qui vont vraiment animer) - Testez sur des appareils moins puissants
Framer Motion optimise automatiquement en utilisant transform et opacity quand c'est possible, mais il est important de comprendre ces principes.
Shared Layout Transitions (Framer Motion)
Les shared layout transitions créent une continuité mentale pour l'utilisateur. Avec Framer Motion et la propriété layoutId, on peut créer des transitions fluides entre différentes vues :
Quand l'utilisateur clique sur un produit dans la liste, Framer Motion détecte que les deux images partagent le même layoutId.
Il crée automatiquement une transition fluide entre les deux positions. L'image semble "se déplacer" de la liste vers la page de détail, créant une expérience mémorable.
Cf. la documentation de Framer motion.
Stagger Effects
Les stagger effects (effets en cascade) font apparaître les éléments d'une liste avec un léger décalage, guidant l'œil de l'utilisateur :
Cette approche crée un rythme visuel qui rend l'interface plus agréable à parcourir. Cf. Framer motion.
State-Driven Animations
Les animations peuvent également refléter l'état de l'application. Au lieu d'afficher brutalement un changement d'état, une transition animée guide l'utilisateur et améliore la compréhension du système.
Exemple concret : Un système de filtres où les résultats changent selon les critères sélectionnés. Sans animation, les éléments disparaissent et réapparaissent brutalement. Avec une animation, l'utilisateur comprend visuellement quels éléments correspondent aux filtres :
La propriété layout de Framer Motion détecte automatiquement les changements de position et crée une transition fluide. Les éléments qui disparaissent s'estompent, ceux qui apparaissent s'animent, et ceux qui changent de position se déplacent en douceur.
L'animation transforme un changement d'état technique en feedback visuel compréhensible qui guide l'attention de l'utilisateur.
Design tokens et cohérence visuelle
Les design tokens sont le pont entre le design et le code. Ils garantissent la cohérence visuelle à travers toute l'application.
Design tokens : pont entre design et code
Les design tokens définissent les valeurs de base du design system : couleurs, espacements, typographie, ombres, etc.
Ils permettent de maintenir la cohérence même quand le design évolue.
Il existe des outils pour exporter automatiquement les design tokens depuis Figma, comme Figma Tokens ou Style Dictionary.
Ces outils permettent de synchroniser le design et le code, réduisant les erreurs et les incohérences.
Dans Next.js avec Tailwind, on peut définir les tokens dans tailwind.config.js :
Synchroniser design et code
Les design tokens sont le pont entre le design et le code. Mais si les tokens du code ne correspondent pas exactement à ceux de la maquette, les maquettes ne servent pratiquement à rien.
Le problème classique : Le designer définit primary: #3B82F6 dans Figma, mais le développeur utilise bg-blue-500 (qui vaut #3B82F6... ou pas ?). Résultat : des incohérences subtiles qui s'accumulent.
Les tokens essentiels à synchroniser :
L'UI Engineer s'assure que :
- Les couleurs du code correspondent exactement à celles de Figma
- Les border radius utilisent les mêmes valeurs que le design
- Les espacements respectent la grille définie
Sans cette synchronisation, chaque développeur interprète différemment le design, et l'interface dérive progressivement de l'intention initiale.
Il existe des outils pour exporter automatiquement les tokens depuis Figma (comme Figma Tokens) vers le code, garantissant cette synchronisation.
Conclusion
L'UI Engineering optimise ce que l'utilisateur ressent, pas juste les métriques Lighthouse. C'est un rôle qui se concentre sur les détails qui font la différence : feedback immédiat, animations subtiles, performance perçue, accessibilité invisible.
Un UI Engineer doit parler les deux langues : design et code. Il doit comprendre l'intention du designer tout en étant capable de l'implémenter de manière technique et durable.
Avec Next.js, nous avons accès à des outils puissants pour l'UI Engineering : Server Components pour réduire l'hydratation, next/image pour optimiser le LCP, Streaming SSR pour améliorer la performance perçue, et bien plus encore.
Les techniques présentées dans cet article (feedback optimiste, skeleton screens, fluid typography, shared layout transitions, etc.) sont autant de moyens d'améliorer la qualité perçue d'une interface.
Mais le plus important reste de comprendre pourquoi on fait ces choix et comment ils impactent l'expérience utilisateur.
Pour aller plus loin, je recommande d'explorer les travaux d'Emil Kowalski qui illustrent parfaitement l'art de l'UI Engineering.