Checklist de performance pour UI admin Vue 3 : accélérer les listes volumineuses
Utilisez cette checklist de performance Vue 3 pour accélérer les listes volumineuses : virtualisation, recherche debounce, composants mémoïsés et états de chargement plus intelligents.

Pourquoi les listes lourdes d'un admin semblent lentes
Les utilisateurs ne disent presque jamais « ce composant est inefficace ». Ils disent que l’écran colle : le défilement saccade, la frappe lag, et les clics arrivent avec un temps de retard. Même quand les données sont correctes, ce délai fait hésiter. Ils cessent de faire confiance à l’outil.
Les UIs d’administration deviennent lourdes rapidement parce que les listes ne sont pas « juste des listes ». Une seule table peut contenir des milliers de lignes, beaucoup de colonnes et des cellules personnalisées avec badges, menus, avatars, infobulles et éditeurs inline. Ajoutez le tri, de multiples filtres et une recherche en direct, et la page réalise beaucoup de travail à chaque petite modification.
Ce que les gens remarquent d’abord est simple : le défilement perd des images, la recherche semble en retard sur vos doigts, les menus de ligne s’ouvrent lentement, la sélection en masse gèle et les états de chargement scintillent ou réinitialisent la page.
Sous le capot, le schéma est aussi simple : trop de choses se re-rendent trop souvent. Une frappe déclenche le filtrage, le filtrage déclenche la table, et chaque ligne reconstruit ses cellules. Si chaque ligne est peu coûteuse, on s’en sort. Si chaque ligne est en fait une mini-application, vous payez à chaque fois.
Une checklist de performance pour une UI admin Vue 3 ne vise pas à gagner des benchmarks. Elle vise à garder la saisie fluide, le défilement régulier, les clics réactifs et le progrès visible sans interrompre l’utilisateur.
La bonne nouvelle : de petits changements battent souvent de grandes réécritures. Rendez moins de lignes (virtualisation), réduisez le travail par frappe (debounce), empêchez les cellules chères de se relancer (mémoïsation), et concevez des états de chargement qui n’envoient pas la page en saut.
Mesurez avant de changer quoi que ce soit
Si vous optimisez sans point de référence, il est facile de « réparer » la mauvaise chose. Choisissez un écran admin lent (une table utilisateurs, file de tickets, liste de commandes) et définissez un objectif que vous sentez : défilement fluide et saisie qui ne lag jamais.
Commencez par reproduire le ralentissement, puis profilez.
Enregistrez une courte session dans le panneau Performance du navigateur : chargez la liste, scrollez fort pendant quelques secondes, puis tapez dans la recherche. Cherchez les longues tâches sur le thread principal et le travail répété de layout/paint quand rien de nouveau ne devrait se produire.
Ensuite, ouvrez Vue Devtools et vérifiez ce qui se re-render vraiment. Si une frappe provoque le rendu de toute la table, des filtres et de l’en-tête de page, cela explique généralement le délai d’entrée.
Suivez quelques chiffres pour confirmer les améliorations plus tard :
- Temps avant que la liste soit utilisable (pas juste un spinner)
- Sensation du défilement (fluide vs saccadé)
- Délai d’entrée lors de la frappe (le texte apparaît-il instantanément ?)
- Durée de rendu du composant table
- Temps réseau pour l’appel API de la liste
Enfin, confirmez où est le goulot. Un test rapide consiste à réduire le bruit réseau. Si l’UI bourdonne encore avec des données en cache, c’est surtout le rendu. Si l’UI est fluide mais les résultats arrivent tard, concentrez-vous sur le temps réseau, la taille des requêtes et le filtrage côté serveur.
Virtualisez les grandes listes et tables
La virtualisation est souvent le plus gros gain quand un écran admin rend des centaines ou milliers de lignes. Au lieu de placer chaque ligne dans le DOM, vous ne rendez que ce qui est visible dans la fenêtre (plus un petit buffer). Cela réduit le temps de rendu, la mémoire utilisée et rend le défilement nettement plus stable.
Choisissez la bonne approche
Le scroll virtuel (windowing) est idéal quand les utilisateurs ont besoin de défiler un grand dataset. La pagination est meilleure quand les gens sautent par pages et que vous voulez des requêtes serveur simples. Un pattern « Charger plus » peut convenir quand vous voulez moins de contrôles mais éviter de gros arbres DOM.
Règle générale :
- 0–200 lignes : le rendu normal suffit souvent
- 200–2 000 lignes : virtualisation ou pagination selon l’UX
- 2 000+ lignes : virtualisation plus filtrage/tri côté serveur
Rendre la virtualisation stable
Les listes virtuelles fonctionnent mieux quand chaque ligne a une hauteur prévisible. Si la hauteur change après le rendu (images qui chargent, retour à la ligne, sections expansibles), le scroller doit re-mesurer. Cela provoque un défilement sautillant et des thrashs de layout.
Gardez la stabilité :
- Utilisez une hauteur fixe quand vous le pouvez, ou un petit ensemble de hauteurs connues
- Limitez le contenu variable (tags, notes) et révélez-le via une vue détail
- Utilisez une clé forte et unique par ligne (jamais l’index du tableau)
- Pour les en-têtes collants, gardez l’en-tête en dehors du corps virtualisé
- Si vous devez supporter des hauteurs variables, activez la mesure et simplifiez les cellules
Exemple : si une table de tickets affiche 10 000 lignes, virtualisez le corps de la table et gardez la hauteur des lignes cohérente (statut, sujet, assigné). Mettez les messages longs derrière un tiroir de détails pour que le scroll reste fluide.
Recherche debounce et filtrage plus intelligent
Une boîte de recherche peut faire paraître une table rapide comme lente. Le problème n’est généralement pas le filtre lui-même, mais la réaction en chaîne : chaque frappe déclenche des rendus, des watchers et souvent une requête.
Debounce signifie « attendre un instant après que l’utilisateur a arrêté de taper, puis agir une seule fois ». Pour la plupart des écrans admin, 200 à 400 ms paraît réactif sans être nerveux. Pensez aussi à trim les espaces et à ignorer les recherches de moins de 2–3 caractères si cela convient à vos données.
La stratégie de filtrage doit correspondre à la taille du dataset et aux règles autour :
- Si c’est moins de quelques centaines de lignes et déjà chargé, le filtrage côté client suffit.
- Si c’est des milliers de lignes ou que les permissions sont strictes, interrogez le serveur.
- Si les filtres sont coûteux (plages de dates, logique d’état), poussez-les côté serveur.
- Si vous avez besoin des deux, faites une approche mixte : un premier rétrécissement client, puis une requête serveur pour les résultats finaux.
Quand vous appelez le serveur, gérez les résultats obsolètes. Si l’utilisateur tape « inv » puis termine vite sur « invoice », la requête antérieure peut revenir après et écraser l’UI avec de mauvaises données. Annulez la requête précédente (AbortController avec fetch, ou la fonctionnalité d’annulation de votre client) ou suivez un id de requête et ignorez tout ce qui n’est pas le plus récent.
Les états de chargement comptent autant que la vitesse. Évitez un spinner pleine page pour chaque frappe. Un flux plus calme ressemble à ceci : pendant la frappe, ne rien flasher. Quand l’app cherche, montrez un petit indicateur inline près de l’input. Quand les résultats se mettent à jour, affichez quelque chose de subtil et clair comme « Affichage de 42 résultats ». S’il n’y a pas de résultat, dites « Aucune correspondance » au lieu de laisser une grille vide.
Composants mémoïsés et rendu stable
Beaucoup de tables admin lentes ne le sont pas à cause de « trop de données ». Elles le sont parce que les mêmes cellules se re-rendent encore et encore.
Trouvez ce qui provoque les re-rendus
Les mises à jour répétées proviennent souvent de quelques habitudes :
- Passer de gros objets réactifs en props alors que seules quelques propriétés sont nécessaires
- Créer des fonctions inline dans les templates (nouvelle identité à chaque rendu)
- Utiliser des watchers profonds sur de grands tableaux ou objets de ligne
- Construire de nouveaux tableaux ou objets dans les templates pour chaque cellule
- Faire du formatage à l’intérieur de chaque cellule (dates, monnaies, parsing) à chaque mise à jour
Quand les props et handlers changent d’identité, Vue suppose que l’enfant doit peut‑être se mettre à jour, même si rien de visible n’a changé.
Stabilisez les props, puis mémoïsez
Commencez par passer des props plus petites et stables. Au lieu de passer tout l’objet row dans chaque cellule, passez row.id plus les champs spécifiques affichés. Déplacez les valeurs dérivées dans des computed pour qu’elles ne se recalculent que quand leurs entrées changent.
Si une partie d’une ligne change rarement, v-memo peut aider. Mémoïsez les parties statiques en fonction d’entrées stables (par exemple row.id et row.status) pour que taper dans la recherche ou survoler une ligne n’oblige pas chaque cellule à relancer son template.
Éloignez aussi le travail cher du chemin de rendu. Pré-formattez les dates une fois (par exemple dans une map computed indexée par id), ou formatez côté serveur quand cela a du sens. Un gain fréquent est d’arrêter une colonne « Dernière mise à jour » d’appeler new Date() pour des centaines de lignes à chaque petite mise à jour UI.
L’objectif est simple : garder les identités stables, sortir le travail des templates et n’actualiser que ce qui a réellement changé.
États de chargement intelligents qui paraissent rapides
Une liste paraît souvent plus lente qu’elle ne l’est parce que l’UI saute constamment. De bons états de chargement rendent l’attente prévisible.
Les lignes squelette aident quand la forme des données est connue (tables, cartes, timelines). Un spinner n’indique pas ce qu’on attend. Les squelettes fixent les attentes : combien de lignes, où apparaîtront les actions et à quoi ressemblera la mise en page.
Quand vous rafraîchissez les données (pagination, tri, filtres), gardez les résultats précédents à l’écran pendant que la nouvelle requête est en vol. Ajoutez un indice subtil « actualisation » au lieu de vider la table. Les utilisateurs peuvent continuer à lire ou vérifier quelque chose pendant que la mise à jour a lieu.
Un chargement partiel vaut mieux qu’un blocage total
Tout n’a pas besoin de geler. Si la table charge, gardez la barre de filtres visible mais temporairement désactivée. Si des actions de ligne nécessitent des données supplémentaires, affichez un état en attente sur la ligne cliquée, pas sur toute la page.
Un pattern stable ressemble à :
- Premier chargement : lignes squelette
- Rafraîchissement : gardez les anciennes lignes visibles, affichez un petit hint « updating »
- Filtres : désactivez pendant la fetch, mais ne les déplacez pas
- Actions par ligne : état pending au niveau de la ligne
- Erreurs : inline, sans effondrer la mise en page
Prévenez les changements de mise en page
Réservez de l’espace pour les toolbars, états vides et la pagination pour que les contrôles ne bougent pas quand les résultats changent. Une hauteur min fixe pour la zone de la table aide, et garder l’en-tête/barre de filtres toujours rendue évite les sauts de page.
Exemple concret : sur un écran de tickets, passer de « Open » à « Solved » ne devrait pas vider la liste. Gardez les lignes actuelles, désactivez brièvement le filtre de statut et montrez l’état pending uniquement sur le ticket mis à jour.
Étape par étape : réparer une liste lente en une après‑midi
Choisissez un écran lent et traitez-le comme une petite réparation. L’objectif n’est pas la perfection mais une amélioration claire que l’on ressent en défilement et en saisie.
Plan rapide pour une après‑midi
Nommez d’abord la douleur exacte. Ouvrez la page et faites trois choses : scrollez vite, tapez dans la recherche et changez de page ou de filtres. Souvent une seule de ces actions est vraiment cassée, et cela vous dit quoi réparer en premier.
Ensuite, suivez une séquence simple :
- Identifiez le goulet : défilement saccadé, frappe lente, réponses réseau lentes, ou un mix.
- Réduisez la taille du DOM : virtualisation, ou réduisez la taille de page par défaut jusqu’à stabilité.
- Calmez la recherche : debounce de l’input et annulez les requêtes anciennes pour que les résultats ne soient pas hors ordre.
- Stabilisez les lignes : clés cohérentes, pas de nouveaux objets dans les templates, mémoïsez le rendu des lignes quand les données n’ont pas changé.
- Améliorez la vitesse perçue : squelettes par ligne ou petit spinner inline au lieu de bloquer toute la page.
Après chaque étape, retestez la même action qui posait problème. Si la virtualisation rend le scroll fluide, passez à la suite. Si la frappe lag encore, debounce et annulation de requêtes sont généralement le prochain gros gain.
Petit exemple réutilisable
Imaginez une table « Utilisateurs » avec 10 000 lignes. Le scroll est saccadé parce que le navigateur peint trop de lignes. Virtualisez pour ne rendre que les lignes visibles.
Ensuite, la recherche semble retardée car chaque frappe lance une requête. Ajoutez un debounce de 250 à 400 ms et annulez la requête précédente avec AbortController (ou l’annulation de votre client HTTP) pour que seule la dernière requête mette à jour la liste.
Enfin, rendez chaque ligne peu coûteuse à rerendre. Gardez les props simples (ids et primitives quand possible), mémoïsez la sortie des lignes pour que les lignes non affectées ne se redessinent pas, et affichez le chargement dans le corps du tableau plutôt qu’une superposition plein écran pour que la page reste réactive.
Erreurs fréquentes qui maintiennent l’UI lente
Les équipes appliquent souvent quelques correctifs, voient un petit gain, puis stagnent. La raison habituelle : la partie coûteuse n’est pas « la liste », c’est tout ce que chaque ligne fait en se rendant, en se mettant à jour et en récupérant des données.
La virtualisation aide, mais il est facile d’annuler son effet. Si chaque ligne visible monte un graphique lourd, décode des images, a trop de watchers ou réalise un formatage coûteux, le scroll restera rude. La virtualisation limite combien de lignes existent, pas combien de travail chaque ligne fait.
Les clés sont un autre coupable discret. Si vous utilisez l’index du tableau comme key, Vue ne peut pas suivre correctement les lignes lors d’insertion, suppression ou tri. Cela force souvent des remounts et peut réinitialiser le focus des inputs. Utilisez un id stable pour que Vue réutilise le DOM et les instances de composants.
Le debounce peut aussi se retourner contre vous. Si le délai est trop long, l’UI paraît cassée : les gens tapent, rien ne se passe, puis les résultats sautent. Un délai court fonctionne souvent mieux, et vous pouvez afficher un feedback immédiat comme « Searching... » pour montrer que l’app a bien reçu la saisie.
Cinq erreurs qui apparaissent dans la plupart des audits de listes lentes :
- Virtualiser la liste, mais garder des cellules coûteuses (images, graphiques, composants complexes) dans chaque ligne visible.
- Utiliser des keys basées sur l'index, provoquant des remounts lors du tri et des mises à jour.
- Définir un debounce trop long, rendant l’interface lente plutôt que calme.
- Déclencher des requêtes depuis des changements réactifs trop larges (watch sur l’objet filtre entier, synchronisation d’état URL trop fréquente).
- Utiliser un loader global qui efface la position de scroll et vole le focus.
Si vous utilisez une checklist de performance Vue 3, traitez « ce qui re-render » et « ce qui refetch » comme des problèmes primordiaux.
Checklist rapide de performance
Utilisez cette checklist quand une table ou une liste commence à coller. L’objectif : défilement fluide, recherche prévisible et moins de re-rendus surprises.
Rendu et défilement
La plupart des problèmes de « liste lente » viennent de trop de rendu, trop souvent.
- Si l’écran peut montrer des centaines de lignes, virtualisez pour que le DOM ne contienne que ce qui est à l’écran (plus un petit buffer).
- Gardez la hauteur des lignes stable. Les hauteurs variables peuvent casser la virtualisation et provoquer du jank.
- Évitez de passer de nouveaux objets et tableaux comme props inline (par exemple
:style="{...}"). Créez-les une fois et réutilisez-les. - Méfiez-vous des watchers profonds sur les données de lignes. Préférez les computed et des watchers ciblés sur les quelques champs qui changent réellement.
- Utilisez des clés stables correspondant aux vrais ids des enregistrements, pas l’index du tableau.
Recherche, chargement et requêtes
Faites paraître la liste rapide même quand le réseau ne l’est pas.
- Debouncez la recherche autour de 250 à 400 ms, gardez le focus dans l’input et annulez les requêtes obsolètes pour que d’anciennes réponses ne puissent pas écraser les plus récentes.
- Gardez les résultats existants visibles pendant le chargement des nouveaux. Utilisez un état subtil « updating » au lieu de vider la table.
- Gardez la pagination prévisible (taille de page fixe, comportement clair next/prev, pas de réinitialisations surprises).
- Batcher les appels liés (par exemple comptes + données de liste) ou récupérez-les en parallèle, puis rendez une fois.
- Mettez en cache la dernière réponse réussie pour un jeu de filtres donné afin que revenir à une vue courante paraisse instantané.
Exemple : un écran de tickets sous charge
Une équipe support garde un écran de tickets ouvert toute la journée. Ils cherchent par nom client, tag ou numéro de commande pendant qu’un flux en direct met à jour le statut des tickets (nouveaux replies, changements de priorité, timers SLA). La table peut facilement atteindre 10 000 lignes.
La première version fonctionnait techniquement mais était horrible à utiliser. En tapant, les caractères apparaissaient en retard. La table sautait en haut, la position de scroll se réinitialisait, et l’app envoyait une requête à chaque frappe. Les résultats scintillaient entre l’ancien et le nouveau.
Ce qui a changé :
- Debounce de l’input de recherche (250–400 ms) et requête lancée seulement quand l’utilisateur fait une pause.
- Conservation des résultats précédents visibles pendant que la nouvelle requête était en vol.
- Virtualisation des lignes pour que le DOM ne rende que ce qui est visible.
- Mémoïsation de la ligne de ticket pour qu’elle ne se re-render pas lors de mises à jour live non pertinentes.
- Chargement paresseux du contenu lourd des cellules (avatars, extraits enrichis, tooltips) uniquement quand la ligne était visible.
Après le debounce, le lag de frappe a disparu et les requêtes gaspillées ont chuté. Garder les résultats précédents a arrêté le scintillement, rendant l’écran stable même quand le réseau était lent.
La virtualisation a été le plus grand gain visuel : le défilement est resté fluide parce que le navigateur n’avait plus à gérer des milliers de lignes simultanément. La mémoïsation des lignes a empêché les « re-rendus de toute la table » quand un seul ticket changeait.
Un dernier ajustement a aidé le flux live : les mises à jour ont été groupées et appliquées toutes les quelques centaines de millisecondes pour éviter des reflows constants.
Résultat : défilement stable, saisie rapide et moins de surprises.
Étapes suivantes : rendre la performance par défaut
Une UI admin rapide est plus facile à garder rapide qu’à réparer ensuite. Considérez cette checklist comme une norme pour chaque nouvel écran, pas comme un nettoyage ponctuel.
Priorisez les correctifs que les utilisateurs ressentent le plus. Les gros gains viennent généralement de la réduction de ce que le navigateur doit dessiner et de la rapidité de réaction à la saisie.
Commencez par les basiques : réduisez la taille du DOM (virtualisez les longues listes, ne rendez pas les lignes cachées), réduisez le délai d’entrée (debounce de la recherche, déplacez le filtrage lourd hors de chaque frappe), puis stabilisez le rendu (mémoïsation des composants de ligne, props stables). Gardez les petits refactors pour la fin.
Après cela, ajoutez quelques garde-fous pour éviter les régressions sur de nouveaux écrans. Par exemple : toute liste de plus de 200 lignes utilise la virtualisation, tout champ de recherche est debounce, et chaque ligne utilise une clé id stable.
Les composants réutilisables facilitent cela. Un composant table virtualisée avec des defaults sensés, une barre de recherche avec debounce intégré, et des états squelette/vides qui correspondent à votre layout font plus qu’une page wiki.
Une bonne habitude pratique : avant de merger un nouvel écran admin, testez-le avec 10x de données et un preset réseau lent une fois. S’il reste agréable, il sera excellent en production.
Si vous construisez rapidement des outils internes et voulez que ces patterns restent cohérents entre les écrans, AppMaster (appmaster.io) peut convenir. Il génère de vraies apps Vue 3, donc la même approche de profilage et d’optimisation s’applique quand une liste devient lourde.
FAQ
Commencez par la virtualisation si vous rendez plus de quelques centaines de lignes à la fois. C’est généralement la plus grosse amélioration perçue : le navigateur n’a plus à gérer des milliers de nœuds DOM pendant le défilement.
Quand le défilement perd des images (frames), c’est souvent un problème de rendu/DOM. Si l’interface reste fluide mais que les résultats arrivent en retard, c’est plutôt le réseau ou le filtrage serveur ; confirmez en testant avec des données en cache ou une réponse locale rapide.
La virtualisation ne rend que les lignes visibles (plus un petit buffer) au lieu de toutes les lignes du dataset. Cela réduit la taille du DOM, la consommation mémoire et le travail que Vue et le navigateur effectuent pendant le scroll.
Visez une hauteur de ligne cohérente et évitez le contenu qui change de taille après le rendu. Si les lignes s’étendent, se replient ou chargent des images modifiant la hauteur, le scroller doit re-mesurer et peut devenir saccadé.
Un bon par défaut se situe autour de 250–400 ms. C’est assez court pour paraître réactif, mais suffisant pour éviter de re-filtrer et re-rendre à chaque frappe.
Annulez la requête précédente ou ignorez les réponses obsolètes. L’idée est simple : seule la requête la plus récente peut mettre à jour la table, pour éviter que des réponses plus anciennes n’écrasent des résultats plus récents.
Évitez de passer de gros objets réactifs quand seules quelques propriétés sont nécessaires, et évitez de créer des fonctions ou objets inline dans les templates. Une fois que les identités des props et handlers restent stables, utilisez la mémoïsation comme v-memo pour les parties de ligne qui ne changent pas.
Sortez le travail coûteux du chemin de rendu pour qu’il ne s’exécute pas pour chaque ligne visible à chaque mise à jour. Pré-calculer ou mettre en cache les valeurs formatées (dates, monnaies) et les réutiliser tant que les données sous-jacentes n’ont pas changé.
Conservez les résultats précédents à l’écran pendant le rafraîchissement et affichez un petit indicateur « updating » au lieu de vider la table. Cela évite le scintillement, les sauts de mise en page et donne l’impression d’une interface réactive même sur des réseaux lents.
Oui, les mêmes techniques s’appliquent car AppMaster génère de vraies apps Vue 3. Vous profilez toujours les re-rendus, virtualisez les longues listes, debouncez la recherche et stabilisez le rendu des lignes ; la différence est que vous pouvez standardiser ces patterns comme composants réutilisables à l’échelle.


