Um olhar prático sobre a abordagem por compilador para desempenho web, contrastando frameworks com muita lógica em runtime e saída gerada em tempo de compilação, junto com um quadro simples de decisão.

Os usuários raramente descrevem desempenho em termos técnicos. Eles dizem que o app parece pesado. As páginas demoram um pouco demais para mostrar algo, botões respondem tarde, e ações simples como abrir um menu, digitar numa busca ou trocar de aba ficam travadas.
Os sintomas são familiares: primeiro carregamento lento (UI em branco ou meio construída), interações lentas (cliques que só acontecem depois de uma pausa, rolagem com tremores) e longos indicadores de carregamento depois de ações que deveriam ser instantâneas, como salvar um formulário ou filtrar uma lista.
Muito disso é custo de runtime. Em termos simples, é o trabalho que o navegador precisa fazer depois que a página carrega para tornar o app utilizável: baixar mais JavaScript, analisá-lo, executá-lo, construir a UI, anexar handlers e depois continuar fazendo trabalho a cada atualização. Mesmo em dispositivos rápidos, há um limite do quanto de JavaScript você pode empurrar pelo navegador antes que a experiência comece a arrastar.
Problemas de performance também aparecem com o tempo. No início, o app é pequeno: poucas telas, dados leves, UI simples. Depois o produto cresce. Marketing adiciona trackers, design adiciona componentes mais ricos, times adicionam estado, features, dependências e personalização. Cada mudança parece inofensiva por si só, mas o trabalho total se acumula.
Por isso equipes começam a olhar para ideias de performance que priorizam compilação. O objetivo geralmente não é pontuações perfeitas. É continuar entregando sem que o app fique mais lento todo mês.
A maioria dos frameworks frontend te ajuda em duas coisas: construir um app e manter a UI sincronizada com os dados. A diferença chave é quando a segunda parte acontece.
Com um framework pesado no runtime, grande parte do trabalho acontece no navegador depois que a página carrega. Você envia um runtime de uso geral que pode lidar com muitos casos: rastrear mudanças, decidir o que atualizar e aplicar essas atualizações. Essa flexibilidade pode ser ótima para desenvolvimento, mas muitas vezes significa mais JavaScript para baixar, analisar e executar antes que a UI pareça pronta.
Com otimização em tempo de compilação, mais desse trabalho é movido para a etapa de build. Em vez de enviar ao navegador um grande conjunto de regras, a ferramenta de build analisa seus componentes e gera código mais direto e específico para o app.
Um modelo mental útil:
A maioria dos produtos reais fica em algum lugar no meio. Abordagens que priorizam o compilador ainda enviam algum código de runtime (roteamento, fetch de dados, animações, tratamento de erro). Frameworks pesados no runtime também contam com técnicas de build (minificação, code splitting, server rendering) para reduzir o trabalho no cliente. A questão prática não é qual campo é “certo”, mas qual mistura se encaixa no seu produto.
Rich Harris é uma das vozes mais claras por trás do pensamento que prioriza compilador no frontend. O argumento é direto: faça mais trabalho antecipadamente para que os usuários baixem menos código e o navegador faça menos trabalho.
A motivação é prática. Muitos frameworks pesados enviam um motor de uso geral: lógica de componentes, reatividade, diffing, agendamento e helpers que precisam servir a qualquer app. Essa flexibilidade custa bytes e CPU. Mesmo quando sua UI é pequena, você pode pagar por um runtime grande.
Uma abordagem por compilador inverte o modelo. Durante a build, o compilador olha seus componentes reais e gera o código específico de atualização do DOM que eles precisam. Se um rótulo nunca muda, vira HTML simples. Se só um valor muda, apenas o caminho de atualização para esse valor é emitido. Em vez de enviar uma máquina de UI genérica, você envia a saída feita sob medida para o seu produto.
Isso costuma levar a um resultado simples: menos código de framework enviado aos usuários e menos trabalho feito a cada interação. Também tende a aparecer mais em dispositivos de baixo custo, onde overheads extras de runtime ficam visíveis rapidamente.
Os trade-offs ainda importam:
Uma regra prática: se sua UI é em grande parte previsível na build, um compilador pode gerar saída enxuta. Se sua UI é altamente dinâmica ou baseada em plugins, um runtime mais pesado pode ser mais fácil.
A otimização em build muda onde o trabalho acontece. Mais decisões são tomadas durante a build, e menos trabalho fica para o navegador.
Um resultado visível é menos JavaScript enviado. Bundles menores reduzem tempo de rede, tempo de parsing e o atraso antes da página poder responder a um toque ou clique. Em telefones de média gama, isso importa mais do que muitas equipes esperam.
Compiladores também podem gerar atualizações DOM mais diretas. Quando a etapa de build enxerga a estrutura de um componente, ela pode produzir código de atualização que mexe apenas nos nós do DOM que realmente mudam, sem tantas camadas de abstração a cada interação. Isso pode tornar atualizações frequentes mais ágeis, especialmente em listas, tabelas e formulários.
Análise em tempo de build também fortalece tree-shaking e remoção de código morto. O ganho não é só arquivos menores. São menos caminhos de código que o navegador precisa carregar e executar.
Hidratação é outra área onde escolhas de build podem ajudar. Hidratação é o passo em que uma página renderizada no servidor se torna interativa ao anexar handlers e reconstruir estado no navegador. Se a build consegue marcar o que precisa ser interativo e o que não precisa, você reduz o trabalho do primeiro carregamento.
Como efeito colateral, a compilação frequentemente melhora o escopo do CSS. A build pode reescrever nomes de classes, remover estilos não usados e reduzir vazamentos de estilo entre componentes. Isso diminui custos surpresa conforme a UI cresce.
Imagine um dashboard com filtros e uma grande tabela de dados. Uma abordagem que prioriza compilador pode manter o carregamento inicial mais leve, atualizar apenas as células que mudaram após um clique de filtro e evitar hidratar partes da página que nunca ficam interativas.
Um runtime maior não é automaticamente ruim. Muitas vezes ele compra flexibilidade: padrões decididos em tempo de execução, muitos componentes de terceiros e fluxos testados ao longo dos anos.
Frameworks pesados brilham quando as regras da UI mudam com frequência. Se você precisa de roteamento complexo, layouts aninhados, formulários ricos e um modelo de estado profundo, um runtime maduro pode parecer uma rede de segurança.
Um runtime ajuda quando você quer que o framework cuide de muita coisa enquanto o app está rodando, não apenas enquanto está sendo construído. Isso pode tornar as equipes mais rápidas no dia a dia, mesmo adicionando overhead.
Ganhas comuns incluem um ecossistema amplo, padrões familiares para estado e fetch de dados, ferramentas de desenvolvedor robustas, extensão por plugins mais fácil e onboarding mais suave ao contratar de um pool de talentos comum.
Familiaridade da equipe é um custo e um benefício real. Um framework um pouco mais lento que sua equipe domina pode vencer uma abordagem mais rápida que exige requalificação, disciplina maior ou ferramentas customizadas para evitar armadilhas.
Muitas reclamações de “app lento” não são causadas pelo runtime do framework. Se sua página está esperando por uma API lenta, imagens pesadas, muitas fontes ou scripts de terceiros, trocar de framework não conserta o problema central.
Um dashboard administrativo interno atrás de login costuma se comportar bem mesmo com um runtime maior, porque os usuários estão em dispositivos potentes e o trabalho é dominado por tabelas, permissões e consultas ao backend.
“Rápido o suficiente” pode ser o alvo certo no começo. Se você ainda está provando valor do produto, mantenha alta velocidade de iteração, defina orçamentos básicos e só adote complexidade orientada por compilador quando houver evidência de que isso importa.
Velocidade de iteração é tempo-para-feedback: com que rapidez alguém pode mudar uma tela, rodá-la, ver o que quebrou e consertar. Equipes que mantêm esse loop curto entregam mais e aprendem mais rápido. Por isso frameworks pesados no runtime podem parecer produtivos no começo: padrões conhecidos, resultados rápidos, muito comportamento embutido.
Trabalhos de performance desaceleram esse loop quando feitos cedo demais ou de forma ampla. Se cada pull request vira uma discussão sobre micro-otimizações, a equipe para de arriscar. Se você monta um pipeline complexo antes de saber o produto, as pessoas gastam tempo brigando com tooling em vez de falar com usuários.
O truque é concordar sobre o que é “bom o suficiente” e iterar dentro dessa caixa. Um orçamento de performance dá essa caixa. Não se trata de correr atrás de pontuações perfeitas. Trata-se de limites que protegem a experiência mantendo o desenvolvimento em movimento.
Um orçamento prático pode incluir:
Se você ignora performance, geralmente paga depois. Quando o produto cresce, lentidão fica amarrada a decisões de arquitetura, não só a pequenos ajustes. Uma reescrita tardia pode significar congelar features, requalificar a equipe e quebrar fluxos que antes funcionavam.
Ferramentas que priorizam compilação podem deslocar esse trade-off. Você pode aceitar builds um pouco mais longos, mas reduzir o trabalho feito em cada dispositivo, a cada visita.
Reavalie orçamentos conforme o produto se consolida. No começo, proteja o básico. Conforme tráfego e receita crescem, aperte os orçamentos e invista onde as mudanças movem métricas reais, não orgulho.
Discussões sobre performance ficam confusas quando ninguém concorda sobre o que é “rápido”. Escolha um pequeno conjunto de métricas, escreva-as e trate-as como placar compartilhado.
Um conjunto inicial simples:
Meça em dispositivos representativos, não só no laptop de desenvolvimento. CPU rápida, cache quente e servidor local podem ocultar atrasos que aparecem num celular de média gama em rede móvel.
Mantenha o processo simples: escolha dois ou três dispositivos que combinem com usuários reais e rode o mesmo fluxo cada vez (tela inicial, login, uma tarefa comum). Faça isso consistentemente.
Antes de trocar de framework, capture uma linha de base. Pegue a build de hoje, registre os números para os mesmos fluxos e mantenha esses dados visíveis. Essa linha de base é sua foto “antes”.
Não julgue performance por uma única pontuação de laboratório. Ferramentas de laboratório ajudam, mas podem favorecer a coisa errada (bom carregamento inicial) enquanto perdem o que os usuários reclamam (menus travados, digitação lenta, atrasos após a primeira tela).
Quando os números pioram, não chute. Verifique o que foi enviado, o que bloqueou a renderização e onde o tempo foi gasto: rede, JavaScript ou API.
Para fazer uma escolha calma e repetível, trate decisões de framework e renderização como decisões de produto. O objetivo não é a melhor tecnologia. É o equilíbrio certo entre performance e o ritmo que sua equipe precisa.
Um thin slice deve incluir as partes complicadas: dados reais, autenticação e sua tela mais lenta.
Se quiser uma maneira rápida de prototipar esse thin slice, a Koder.ai permite construir fluxos web, backend e mobile via chat e depois exportar o código-fonte. Isso ajuda a testar uma rota real cedo e manter experimentos reversíveis com snapshots e rollback.
Documente a decisão em linguagem simples, incluindo o que faria você revisitar ela (crescimento de tráfego, participação mobile, metas de SEO). Isso torna a escolha durável quando a equipe mudar.
Decisões de performance costumam dar errado quando as equipes otimizam o que veem hoje, não o que os usuários vão sentir em três meses.
Um erro é otimizar demais na primeira semana. A equipe gasta dias cortando milissegundos numa página que ainda muda diariamente, enquanto o problema real é que os usuários ainda não têm as features certas. No começo, acelere o aprendizado. Trave trabalho de performance mais profundo quando rotas e componentes estabilizarem.
Outro é ignorar o crescimento do bundle até que passe a incomodar. As coisas parecem bem em 200 KB, depois algumas adições “pequenas” e você está enviando megabytes. Um hábito simples ajuda: monitore o tamanho do bundle ao longo do tempo e trate picos súbitos como bugs.
Times também costumam optar por renderização só no cliente para tudo, mesmo quando algumas rotas são majoritariamente estáticas (páginas de preços, conteúdo estilo docs, etapas de onboarding). Essas páginas muitas vezes podem ser entregues com bem menos trabalho no dispositivo.
Um assassino silencioso é adicionar uma grande biblioteca de UI por conveniência sem medir seu custo nas builds de produção. Conveniência é válida. Só seja claro sobre o que você está pagando em JavaScript extra, CSS extra e interações mais lentas em celulares de média gama.
Por fim, misturar abordagens sem limites claros cria apps difíceis de depurar. Se metade do app assume atualizações geradas por compilador e a outra metade depende de mágica de runtime, você acaba com regras obscuras e falhas confusas.
Alguns guardrails que funcionam em times reais:
Imagine uma equipe de 3 pessoas construindo um SaaS de agendamento e faturamento. Tem duas faces: site de marketing público (landing, preços, docs) e um dashboard autenticado (calendário, faturas, relatórios, configurações).
Com um caminho runtime-first, eles escolhem uma configuração com runtime pesado porque facilita mudanças na UI. O dashboard vira um grande app client-side com componentes reutilizáveis, uma biblioteca de estado e interações ricas. A iteração é rápida. Com o tempo, o primeiro carregamento começa a ficar pesado em celulares de média gama.
Com um caminho compiler-first, eles escolhem um framework que empurra mais trabalho para o tempo de build para reduzir JavaScript no cliente. Fluxos comuns como abrir o dashboard, trocar abas e buscar ficam mais ágeis. O trade-off é que a equipe precisa ser mais deliberada sobre padrões e ferramentas, e alguns truques fáceis de runtime não são tão plug-and-play.
O gatilho para mudança raramente é gosto. Geralmente é pressão da realidade: páginas mais lentas prejudicam inscrições, mais usuários chegam em dispositivos de baixo custo, compradores corporativos pedem orçamentos previsíveis, o dashboard fica aberto o tempo todo e memória importa, ou tickets de suporte mencionam lentidão em redes reais.
Uma opção híbrida costuma vencer. Mantenha páginas de marketing leves (renderizadas no servidor ou quase estáticas, com código cliente mínimo) e aceite runtime maior no dashboard onde interatividade vale o custo.
Usando os passos de decisão: eles nomeiam as jornadas críticas (inscrição, primeira fatura, relatório semanal), medem em um celular de média gama, definem um orçamento e escolhem híbrido. Compiler-first por padrão para páginas públicas e componentes compartilhados; runtime-heavy apenas onde acelera a experimentação.
A maneira mais fácil de tornar essas ideias reais é um loop semanal curto.
Comece com uma varredura de 15 minutos: o tamanho do bundle está crescendo? Quais rotas parecem lentas? Quais são os maiores blocos de UI nessas rotas (tabelas, gráficos, editores, mapas)? Quais dependências pesam mais? Depois escolha um gargalo que você possa consertar sem reescrever a stack.
Para esta semana, mantenha pequeno:
Para manter escolhas reversíveis, desenhe limites claros entre rotas e features. Prefira módulos que você possa trocar depois (gráficos, editores ricos, SDKs de analytics) sem tocar o app inteiro.
Na maior parte das vezes não é só a rede — é o custo em tempo de execução: o navegador baixando, analisando e executando JavaScript, construindo a interface e fazendo trabalho extra a cada atualização.
Por isso um app pode parecer “pesado” mesmo em um laptop rápido, quando a carga de JavaScript aumenta.
Eles têm o mesmo objetivo (fazer menos no cliente), mas o mecanismo difere.
Significa que o framework pode analisar seus componentes na hora da build e produzir código adaptado ao seu app, em vez de enviar uma grande máquina de UI genérica.
O benefício prático costuma ser bundles menores e menos trabalho de CPU durante interações (cliques, digitação, rolagem).
Comece com:
Meça o mesmo fluxo de usuário sempre, para poder comparar builds de forma confiável.
Sim e não. Se o problema for APIs lentas, imagens pesadas, muitas fontes ou scripts de terceiros, mudar de framework não resolve esses gargalos.
Trate a escolha do framework como uma alavanca entre outras. Primeiro confirme onde o tempo está sendo gasto: rede, CPU do JavaScript, renderização ou backend.
Escolha runtime-heavy quando precisar de flexibilidade e velocidade de iteração:
Se o runtime não for o gargalo, a conveniência pode valer os bytes extras.
Uma regra simples:
Híbrido costuma funcionar bem quando você documenta limites claros para evitar mistura confusa de pressupostos.
Use um orçamento que proteja a sensação do usuário sem bloquear entregas. Por exemplo:
Orçamentos são guardrails, não competição por pontuações perfeitas.
Hidratação é o trabalho de pegar uma página renderizada no servidor e torná-la interativa, anexando handlers e reconstruindo estado no navegador.
Se você hidratar demais, o carregamento inicial pode parecer lento mesmo que o HTML apareça rápido. Ferramentas de build podem reduzir hidratação marcando só o que precisa ser interativo.
Uma boa “thin slice” inclui os problemas reais:
Se estiver prototipando essa slice, a Koder.ai pode ajudar a montar o fluxo web + backend via chat e exportar o código, para você medir e comparar abordagens cedo sem reescrever tudo.