Checklist de performance para UIs admin em Vue 3 — listas pesadas mais rápidas
Use este checklist de performance para UIs admin em Vue 3 e acelere listas pesadas com virtualização, busca com debounce, componentes memoizados e estados de carregamento melhores.

Por que listas pesadas em admin parecem lentas
Os usuários raramente dizem “este componente é ineficiente.” Eles dizem que a tela está pegajosa: a rolagem trava, digitar demora, e cliques chegam com atraso. Mesmo quando os dados estão corretos, esse atraso faz as pessoas hesitarem. Elas deixam de confiar na ferramenta.
UIs admin ficam pesadas rápido porque listas não são “apenas listas.” Uma única tabela pode incluir milhares de linhas, muitas colunas e células personalizadas com badges, menus, avatares, tooltips e editores inline. Some ordenação, múltiplos filtros e busca em tempo real, e a página começa a fazer trabalho real a cada pequena mudança.
O que as pessoas notam primeiro é simples: a rolagem perde frames, a busca parece atrás dos dedos, menus de linha abrem devagar, seleção em massa congela e estados de carregamento piscam ou reiniciam a página.
Por baixo, o padrão também é simples: muitas coisas re-renderizam com muita frequência. Uma tecla dispara filtragem, a filtragem atualiza a tabela, e cada linha reconstrói suas células. Se cada linha for barata, você tolera. Se cada linha for basicamente um mini-app, você paga por isso toda vez.
Um checklist de performance para UIs admin em Vue 3 não é sobre ganhar benchmarks. É sobre manter a digitação suave, a rolagem estável, cliques responsivos e progresso visível sem interromper o usuário.
A boa notícia: pequenas mudanças normalmente vencem grandes reescritas. Renderize menos linhas (virtualização), reduza trabalho por tecla (debounce), impeça células caras de rodarem de novo (memoização) e projete estados de carregamento que não façam a página pular.
Meça antes de mudar qualquer coisa
Se você afinar sem uma linha de base, é fácil “consertar” a coisa errada. Escolha uma tela admin lenta (uma tabela de usuários, fila de tickets, lista de pedidos) e defina um alvo perceptível: rolagem rápida e campo de busca que nunca trave.
Comece reproduzindo a lentidão e depois profile.
Grave uma pequena sessão no painel Performance do navegador: carregue a lista, role rápido por alguns segundos e então digite na busca. Procure por long tasks no main thread e trabalho repetido de layout/paint quando nada novo deveria acontecer.
Depois abra o Vue Devtools e verifique o que realmente re-renderiza. Se uma tecla causa que toda a tabela, filtros e cabeçalho da página re-renderizem, isso geralmente explica o atraso na digitação.
Acompanhe alguns números para confirmar melhorias depois:
- Tempo até a primeira lista utilizável (não apenas um spinner)
- Sensação da rolagem (suave vs truncada)
- Atraso de input ao digitar (o texto aparece instantaneamente?)
- Duração de render do componente de tabela
- Tempo de rede para a chamada da API da lista
Por fim, confirme onde está o gargalo. Um teste rápido é reduzir ruído de rede. Se a UI ainda trava com dados em cache, é principalmente renderização. Se a UI fica suave mas os resultados chegam tarde, foque em tempo de rede, tamanho da query e filtragem no servidor.
Virtualize listas e tabelas grandes
Virtualização costuma ser o maior ganho quando uma tela admin renderiza centenas ou milhares de linhas de uma vez. Em vez de colocar cada linha no DOM, você renderiza apenas o que está visível no viewport (mais um pequeno buffer). Isso corta o tempo de render, reduz uso de memória e deixa a rolagem mais estável.
Escolha a abordagem certa
Virtual scrolling (windowing) é melhor quando usuários precisam rolar longos datasets suavemente. Paginação é melhor quando as pessoas pulam por páginas e você quer queries servidor-simples. Um padrão “Carregar mais” pode funcionar quando quer menos controles mas ainda evitar árvores DOM enormes.
Como regra aproximada:
- 0–200 linhas: renderização normal costuma bastar
- 200–2.000 linhas: virtualização ou paginação dependendo do UX
- 2.000+ linhas: virtualização mais filtragem/ordenacão no servidor
Torne a virtualização estável
Listas virtuais funcionam melhor quando cada linha tem altura previsível. Se a altura da linha muda depois do render (imagens carregando, quebra de texto, seções expansíveis), o scroller precisa re-medir. Isso leva a rolagem saltando e thrash de layout.
Mantenha estável:
- Use altura fixa por linha quando possível, ou um pequeno conjunto de alturas conhecidas
- Tranque conteúdo variável (tags, notas) e revele com uma view de detalhes
- Use uma key forte e única por linha (nunca o índice do array)
- Para cabeçalhos sticky, mantenha o cabeçalho fora do corpo virtualizado
- Se precisar suportar alturas variáveis, habilite medição e mantenha células simples
Exemplo: se uma tabela de tickets mostra 10.000 linhas, virtualize o corpo da tabela e mantenha a altura da linha consistente (status, assunto, responsável). Coloque mensagens longas num drawer de detalhes para que a rolagem continue suave.
Busca com debounce e filtragem mais inteligente
Uma caixa de busca pode fazer uma tabela rápida parecer lenta. O problema geralmente não é o filtro em si. É a reação em cadeia: cada tecla dispara renders, watchers e, muitas vezes, uma requisição.
Debounce significa “espere um momento depois que o usuário parar de digitar, então aja uma vez.” Para a maioria das telas admin, 200 a 400 ms parece responsivo sem ficar instável. Considere também aparar espaços e ignorar buscas com menos de 2 a 3 caracteres se isso fizer sentido para seus dados.
A estratégia de filtragem deve casar com o tamanho do dataset e as regras ao redor dele:
- Se são algumas centenas de linhas já carregadas, filtragem cliente é ok.
- Se são milhares de linhas ou permissões são estritas, consulte o servidor.
- Se filtros são caros (intervalos de datas, lógica de status), empurre para o servidor.
- Se precisa dos dois, faça abordagem mista: estreite cliente rapidamente, depois query no servidor para o resultado final.
Quando você chama o servidor, trate resultados stale. Se o usuário digita “inv” e rapidamente completa “invoice”, a requisição anterior pode retornar por último e sobrescrever a UI com dados errados. Cancele a requisição anterior (AbortController com fetch, ou o recurso de cancelamento do seu cliente HTTP), ou rastreie um id de requisição e ignore qualquer coisa que não seja a mais recente.
Estados de carregamento importam tanto quanto velocidade. Evite um spinner de tela inteira a cada tecla. Um fluxo mais calmo fica assim: enquanto o usuário digita, não pisque nada. Quando o app está buscando, mostre um pequeno indicador inline perto do input. Quando os resultados atualizarem, mostre algo sutil e claro como “Mostrando 42 resultados”. Se não houver resultados, diga “Nenhuma correspondência” em vez de deixar uma grade em branco.
Componentes memoizados e renderização estável
Muitas tabelas admin lentas não são lentas por “dados demais.” São lentas porque as mesmas células re-renderizam várias vezes.
Encontre o que causa re-renders
Atualizações repetidas frequentemente vêm de alguns hábitos comuns:
- Passar grandes objetos reativos como props quando só alguns campos são necessários
- Criar funções inline em templates (novas a cada render)
- Usar watchers deep em arrays grandes ou objetos de linha
- Construir novos arrays ou objetos dentro dos templates para cada célula
- Fazer formatação dentro de cada célula (datas, moeda, parsing) a cada atualização
Quando props e handlers mudam identidade, o Vue assume que o filho pode precisar atualizar, mesmo que nada visível mude.
Torne props estáveis, depois memoize
Comece passando props menores e estáveis. Em vez de passar o row inteiro para cada célula, passe row.id mais os campos específicos que a célula mostra. Mova valores derivados para computed para que só recalculam quando suas entradas mudam.
Se parte de uma linha muda raramente, v-memo pode ajudar. Memoize partes estáticas com base em inputs estáveis (por exemplo, row.id e row.status) para que digitar ou pairar a linha não force cada célula a reexecutar o template.
Também mantenha trabalho caro fora do caminho de render. Pré-formate datas uma vez (por exemplo, num mapa computed indexado por id), ou formate no servidor quando fizer sentido. Um ganho comum é parar com uma coluna “Última atualização” chamando new Date() para centenas de linhas a cada pequena atualização da UI.
O objetivo é simples: mantenha identidades estáveis, tire trabalho dos templates e atualize somente o que realmente mudou.
Estados de carregamento inteligentes que parecem rápidos
Uma lista costuma parecer mais lenta do que é porque a UI fica pulando. Bons estados de carregamento tornam a espera previsível.
Skeleton rows ajudam quando a forma dos dados é conhecida (tabelas, cards, timelines). Um spinner não diz o que você está esperando. Skeletons definem expectativa: quantas linhas, onde ações aparecerão e como o layout ficará.
Quando você atualiza dados (paginação, ordenação, filtros), mantenha os resultados anteriores na tela enquanto a nova requisição está em voo. Adicione uma dica sutil de “atualizando” em vez de limpar a tabela. Usuários podem continuar lendo ou checando algo enquanto a atualização acontece.
Carregamento parcial vence bloqueio total
Nem tudo precisa congelar. Se a tabela está carregando, mantenha a barra de filtros visível, mas temporariamente desabilitada. Se ações de linha precisam de dados extras, mostre um estado pendente na linha clicada, não na página inteira.
Um padrão estável fica assim:
- Primeiro load: linhas esqueléticas
- Refresh: mantenha linhas antigas visíveis, mostre uma pequena indicação de “atualizando”
- Filtros: desabilite durante o fetch, mas não os mova
- Ações por linha: estado pendente por linha
- Erros: inline, sem colapsar o layout
Previna shifts de layout
Reserve espaço para toolbars, estados vazios e paginação para que controles não se movam quando os resultados mudam. Uma min-height fixa para a área da tabela ajuda, e manter o cabeçalho/barra de filtro sempre renderizados evita saltos de página.
Exemplo concreto: em uma tela de tickets, trocar de “Open” para “Solved” não deveria limpar a lista. Mantenha as linhas atuais, desabilite o filtro de status brevemente e mostre estado pendente apenas no ticket atualizado.
Passo a passo: conserte uma lista lenta em uma tarde
Escolha uma tela lenta e trate-a como um pequeno reparo. O objetivo não é perfeição. É uma melhoria clara que você sente na rolagem e na digitação.
Um plano rápido para a tarde
Nomeie a dor exata primeiro. Abra a página e faça três coisas: role rápido, digite na busca e mude páginas ou filtros. Muitas vezes só uma dessas está realmente quebrada, e isso diz o que consertar primeiro.
Então execute uma sequência simples:
- Identifique o gargalo: rolagem travada, digitação lenta, respostas de rede lentas ou uma mistura.
- Corte o tamanho do DOM: virtualização, ou reduza o tamanho padrão da página até a UI ficar estável.
- Acalme a busca: debounce no input e cancele requisições antigas para que resultados não cheguem fora de ordem.
- Mantenha linhas estáveis: chaves consistentes, nada de objetos novos nos templates, memoize a renderização das linhas quando os dados não mudarem.
- Melhore a percepção de velocidade: skeletons por linha ou um pequeno spinner inline em vez de bloquear a página inteira.
Depois de cada passo, teste de novo a mesma ação que estava ruim. Se virtualizar deixou a rolagem suave, siga adiante. Se digitar ainda estiver lento, debounce e cancelamento de requisições são normalmente os próximos maiores ganhos.
Exemplo pequeno que você pode copiar
Imagine uma tabela “Usuários” com 10.000 linhas. A rolagem está travada porque o navegador pinta muitas linhas. Virtualize para que apenas as linhas visíveis sejam renderizadas.
Em seguida, a busca parece atrasada porque cada tecla dispara uma requisição. Adicione debounce de 250 a 400 ms e cancele a requisição anterior com AbortController (ou o cancelamento do seu cliente HTTP) para que só a query mais recente atualize a lista.
Por fim, torne cada linha barata de re-renderizar. Mantenha props simples (ids e primitivos quando possível), memoize a saída da linha para que linhas não afetadas não redesenhem e mostre carregamento dentro do corpo da tabela em vez de um overlay em tela cheia, assim a página continua responsiva.
Erros comuns que mantêm a UI lenta
Times frequentemente aplicam alguns consertos, veem um pequeno ganho e então travam. A razão comum: a parte cara não é “a lista.” É tudo o que cada linha faz ao renderizar, atualizar e buscar dados.
Virtualização ajuda, mas é fácil anular o benefício. Se cada linha visível ainda monta um gráfico pesado, decodifica imagens, executa muitos watchers ou faz formatação cara, a rolagem continuará ruim. Virtualização limita quantas linhas existem, não quão pesadas elas são.
Keys são outro assassino de performance silencioso. Se você usa o índice do array como key, o Vue não consegue rastrear linhas corretamente ao inserir, deletar ou ordenar. Isso frequentemente força remounts e pode resetar foco de inputs. Use um id estável para que Vue reaproveite DOM e instâncias de componente.
Debounce também pode atrapalhar. Se o delay for longo demais, a UI parece quebrada: pessoas digitam, nada acontece e então os resultados pulam. Um delay curto normalmente funciona melhor, e você ainda pode mostrar feedback imediato como “Buscando...” para que o usuário saiba que o app ouviu.
Cinco erros que aparecem na maioria das auditorias de listas lentas:
- Virtualizar a lista, mas manter células pesadas (imagens, gráficos, componentes complexos) em cada linha visível.
- Usar keys baseadas em índice, fazendo com que linhas remountem ao ordenar ou atualizar.
- Debounce da busca tão longo que parece lento em vez de calmo.
- Disparar requisições a partir de mudanças reativas amplas (watching no objeto inteiro de filtros, sincronizar estado de URL com muita frequência).
- Usar um loader global que limpa posição de rolagem e rouba foco.
Se você usa um checklist de performance Vue 3 para admin, trate “o que re-renderiza” e “o que refetcha” como problemas de primeira classe.
Checklist rápido de performance
Use este checklist quando uma tabela ou lista começar a ficar pegajosa. O alvo é rolagem suave, busca previsível e menos re-renders surpresa.
Render e rolagem
A maioria dos problemas de “lista lenta” vem de renderizar demais, com muita frequência.
- Se a tela pode mostrar centenas de linhas, use virtualização para que o DOM só contenha o que está na tela (mais um pequeno buffer).
- Mantenha altura de linha estável. Alturas variáveis podem quebrar virtualização e causar jank.
- Evite passar novos objetos e arrays como props inline (por exemplo
:style="{...}"). Crie-os uma vez e reutilize. - Cuidado com watchers deep nos dados de linha. Prefira valores
computede watches direcionados aos poucos campos que realmente mudam. - Use chaves estáveis que correspondam a ids reais de registro, não o índice do array.
Busca, carregamento e requisições
Faça a lista parecer rápida mesmo quando a rede não estiver.
- Debounce a busca entre 250 e 400 ms, mantenha o foco no input e cancele requisições obsoletas para que respostas antigas não sobrescrevam as mais novas.
- Mantenha resultados existentes visíveis durante o carregamento de novos. Use um estado sutil de “atualizando” em vez de limpar a tabela.
- Mantenha paginação previsível (tamanho de página fixo, comportamento claro de próximo/anterior, sem resets-surpresa).
- Agrupe chamadas relacionadas (por exemplo counts + dados da lista) ou busque em paralelo e renderize só quando tudo estiver pronto.
- Cache a última resposta bem-sucedida para um conjunto de filtros para que voltar a uma visão comum pareça instantâneo.
Exemplo: tela de tickets sob carga
Um time de suporte mantém uma tela de tickets aberta o dia todo. Eles buscam por nome do cliente, tag ou número do pedido enquanto um feed ao vivo atualiza status (novas respostas, mudança de prioridade, timers de SLA). A tabela pode facilmente chegar a 10.000 linhas.
A primeira versão funciona tecnicamente, mas a sensação é péssima. Enquanto digitam, os caracteres aparecem atrasados. A tabela pula para o topo, a posição de rolagem reseta e o app envia uma requisição a cada tecla. Resultados piscam entre velho e novo.
O que mudou:
- Debounce no input de busca (250 a 400 ms) e consulta só após o usuário pausar.
- Manter resultados anteriores visíveis enquanto a nova requisição está em voo.
- Virtualizar linhas para que o DOM só renderize o que está visível.
- Memoizar a linha de ticket para que ela não re-renderize para updates ao vivo não relacionados.
- Lazy-load de conteúdo pesado da célula (avatares, snippets ricos, tooltips) apenas quando a linha estiver visível.
Depois do debounce, o lag na digitação desapareceu e requisições desperdiçadas caíram. Manter resultados antigos evitou flicker, deixando a tela estável mesmo quando a rede era lenta.
A virtualização foi o maior ganho visual: a rolagem ficou suave porque o navegador não precisava mais gerenciar milhares de linhas simultaneamente. Memoizar a linha impediu atualizações de “tabela inteira” quando um único ticket mudava.
Um ajuste a mais ajudou no feed ao vivo: as atualizações foram agrupadas e aplicadas a cada poucos centenas de milissegundos para que a UI não reflowasse constantemente.
Resultado: rolagem estável, digitação rápida e menos surpresas.
Próximos passos: torne performance um padrão
Uma UI admin rápida é mais fácil de manter rápida do que resgatar depois. Trate este checklist como um padrão para toda nova tela, não apenas um cleanup pontual.
Priorize correções que os usuários sintam mais. Grandes ganhos geralmente vêm de reduzir o que o navegador precisa desenhar e o quão rápido ele reage à digitação.
Comece pelo básico: reduza o tamanho do DOM (virtualize listas longas, não renderize linhas ocultas), depois reduza o atraso de input (debounce na busca, mova filtragem pesada para fora de cada tecla), e então estabilize a renderização (memoize componentes de linha, mantenha props estáveis). Guarde pequenos refactors para o final.
Depois, adicione guardrails para que novas telas não regrassem. Por exemplo, qualquer lista com mais de 200 linhas usa virtualização, qualquer input de busca vem com debounce, e cada linha usa uma chave id estável.
Blocos reutilizáveis facilitam isso. Um componente de tabela virtual com defaults sensatos, uma barra de busca com debounce embutido e estados skeleton/empty que batem com o layout da tabela valem mais do que um wiki.
Um hábito prático: antes de dar merge numa nova tela admin, teste com 10x dados e um preset de rede lenta. Se ainda estiver boa, vai ser ótima em uso real.
Se você está construindo ferramentas internas rápido e quer esses padrões consistentes entre telas, AppMaster (appmaster.io) pode ser um bom ajuste. Ele gera apps Vue 3 reais, então a mesma abordagem de profiling e otimização se aplica quando uma lista fica pesada.
FAQ
Comece pela virtualização se você renderiza mais de algumas centenas de linhas de uma vez. Normalmente é o que mais melhora a “sensação” porque o navegador para de gerenciar milhares de nós DOM durante a rolagem.
Quando a rolagem perde frames, geralmente é problema de renderização/DOM. Quando a interface fica suave mas os resultados chegam atrasados, é rede ou filtragem no servidor; confirme testando com dados em cache ou uma resposta local rápida.
A virtualização renderiza apenas as linhas visíveis (mais um pequeno buffer) em vez de todas as linhas do conjunto de dados. Isso reduz o tamanho do DOM, o uso de memória e o trabalho que Vue e o navegador fazem enquanto você rola.
Visar alturas de linha consistentes e evitar conteúdo que mude de tamanho depois do render. Se linhas expandem, quebram ou carregam imagens que alteram a altura, o scroller precisa re-medir e pode ficar instável.
Um bom padrão é usar entre 250–400 ms. É curto o bastante para parecer responsivo, mas longo o suficiente para evitar re-filtragens e re-renderizações a cada tecla.
Cancele a requisição anterior ou ignore respostas fora de ordem. A ideia é simples: somente a consulta mais recente pode atualizar a tabela, assim respostas antigas não sobrescrevem resultados novos.
Evite passar grandes objetos reativos quando só alguns campos são necessários e evite criar funções/objetos inline nos templates. Quando identidades de props e handlers se mantêm estáveis, use memoização como v-memo para partes de linha que não mudam.
Tire trabalho custoso do caminho de render: pré-calculando ou cacheando valores formatados (datas, moeda) e reaproveitando-os até que os dados mudem, em vez de recalcular para cada linha visível a cada atualização.
Mantenha os resultados anteriores na tela durante um refresh e mostre um pequeno indicador “atualizando” em vez de limpar a tabela. Isso evita flicker, previne saltos de layout e mantém a página com sensação de responsiva mesmo em redes lentas.
Sim — as mesmas técnicas se aplicam porque AppMaster gera apps Vue 3 reais. Você ainda vai perfilar re-renders, virtualizar listas longas, debouncer buscas e estabilizar a renderização de linhas; a diferença é que você pode padronizar esses padrões como blocos reutilizáveis.


