7 MIN LEITURA · Pedro Thomaz

O custo de desempenho das frameworks JavaScript que nunca entra na fatura

O verdadeiro custo de desempenho de uma framework JavaScript não é o download — é o tempo de parsing em telemóveis baratos, a hidratação, a energia e anos de churn. Eis como o medimos.
O custo de desempenho das frameworks JavaScript que nunca entra na fatura

O custo de desempenho das frameworks JavaScript que afunda um projeto quase nunca é o que aparece no selo do tamanho do bundle. Os 45 kB comprimidos são a parte mais barata, mais visível e menos importante da conta. As partes caras são o tempo de parsing e execução num Android com quatro anos, a taxa de hidratação que pagas em cada navegação, a energia que um milhão desses carregamentos queima, e o churn de manutenção que come um dia por trimestre para sempre. Esses custos não aparecem em nenhum benchmark do README de uma framework, por isso quase ninguém os contabiliza.

Nós contabilizamos, porque entregamos PHP renderizado no servidor sem passo de build e mesmo assim temos de justificar, por escrito, cada kilobyte de JavaScript de cliente que adicionamos. Esta é a contabilidade que usamos.

A versão curta: o que significa mesmo "custo de desempenho de uma framework"

Quando se diz que uma framework é "rápida", normalmente quer dizer uma de duas coisas: renderiza depressa numa demo de dev-server num MacBook, ou o seu bundle comprimido é pequeno. Nenhuma é o custo que importa a um visitante real num telemóvel real. O custo verdadeiro são cinco rubricas:

Podes ter um bundle minúsculo e na mesma falhar todos os outros. O peso de transferência é o único custo trivial de resolver com um CDN e aquele com que toda a gente se obceca; os outros quatro são onde os projetos apodrecem em silêncio.

1. O peso do bundle é um engodo

A Cloudflare está à frente de todos os sites que mantemos, por isso um bundle de 200 kB de JavaScript comprime, faz cache na edge e chega em algumas dezenas de milissegundos para a maioria dos visitantes. Se a transferência fosse a história toda, o peso da framework seria um erro de arredondamento.

Não é, porque a contagem de bytes e o custo desses bytes são moedas diferentes. Uma imagem de 100 kB e 100 kB de JavaScript não são equivalentes: o browser descodifica a imagem fora da main thread e acabou. O JavaScript tem de ser parseado, compilado e executado na main thread — a mesma thread que trata de toques, scrolls e renderização. Bytes de script são os bytes mais caros que podes enviar, e o selo do bundle cobra-os como se fossem os mais baratos.

2. O tempo de parsing e execução é medido no dispositivo errado

Eis a rubrica que se salta. Parsear e compilar JavaScript é trabalho de CPU, e a CPU é o recurso que mais varia entre os dispositivos que de facto carregam o teu site. Um visitante português mediano não está no portátil da série M onde a framework foi demonstrada; uma fatia significativa está num Android abaixo dos €200 com dois ou três anos, onde o motor de JS corre a uma fração da velocidade.

Os mesmos 300 kB de framework + código de aplicação que parseiam em ~50 ms numa máquina de programador podem levar várias centenas de milissegundos a mais de um segundo num telemóvel de gama baixa, e esse tempo é gasto na main thread antes de a página estar interativa. É esta a diferença entre um resultado de Lighthouse corrido no teu portátil e os dados de campo no Chrome User Experience Report. Já vimos mais do que uma vez um "95 no laboratório" transformar-se numa careta sob throttling em dispositivo real.

A nossa regra: perfilar o trabalho pesado em JavaScript no Chrome DevTools com a CPU em throttling de 4x ou 6x, num perfil de rede Slow 4G. Se não estiver confortável aí, não está terminado. O número do MacBook é marketing; o número com throttling é a verdade.

3. Hidratação é pagar duas vezes pelo mesmo HTML

Hidratação é o passo em que uma framework de cliente pega no HTML renderizado no servidor e lhe acopla os seus próprios event listeners e estado para a página ficar interativa. A definição importa porque o custo é invisível num screenshot: a página parece pronta enquanto a framework percorre toda a árvore do DOM, reconstrói a sua representação virtual e volta a correr lógica de componentes que o servidor já correu.

Numa página com muito conteúdo isto é desperdício puro. O servidor produziu HTML correto e completo; a função da hidratação é essencialmente tornar um documento estático clicável, e cobra-te o custo total de renderização uma segunda vez no cliente. Para um site de marketing, um caso de estudo, um artigo de journal — a maior parte do que um estúdio como o nosso constrói — não há genuinamente nada para hidratar. O HTML é o produto.

É por isto que o nosso default para conteúdo é não ter qualquer framework de cliente. HTML renderizado no servidor, um CDN, e alguns kilobytes de JavaScript vanilla escrito à mão para as duas ou três coisas que genuinamente precisam dele (um toggle de menu, o beacon de analytics, o lazy-loading de um embed Matterport). Custo zero de hidratação porque não há nada para hidratar. Quando um projeto precisa mesmo de estado rico no cliente — um configurador, a UI de recomendação que construímos para a Jofit — recorremos a ilhas: enviar interatividade apenas para os componentes que dela precisam, deixando o resto como HTML estático.

4. O custo que ninguém fatura: energia e carbono

Cada milissegundo de parsing e execução é um dispositivo algalgures a queimar eletricidade. Um carregamento de página é negligenciável. O tráfego ao longo da vida de um site não é. Se uma framework acrescenta 400 ms de trabalho de CPU por carregamento e uma página é servida alguns milhões de vezes por ano, estás a gastar joules reais em trabalho que o servidor podia ter feito uma só vez.

Isto não é abstrato para nós — temos uma disciplina de sustentabilidade e uma montagem de analytics sem cookies precisamente para podermos raciocinar sobre isto. A matemática do carbono é simples e incómoda: o JavaScript de cliente desloca a computação de um servidor bem gerido para milhares de dispositivos sem controlo, e não podes otimizar hardware que não te pertence. O pedido mais verde é o que chega como HTML acabado e pede ao dispositivo apenas que o pinte. Enviar menos script é a alavanca de carbono web mais eficaz que temos, e calha ser a mesma alavanca que ajuda os Core Web Vitals.

5. O churn de manutenção é um encargo recorrente

O custo que compõe é aquele a que aderes no dia um e pagas todos os trimestres a seguir. Uma framework moderna arrasta uma árvore de dependências de centenas de pacotes transitivos, uma toolchain de build, e uma cadência de versões major que não quer saber do teu calendário de releases. Adota-a e comprometeste-te com migrações, avisos de deprecação, breaking changes em ferramentas que não escolheste, e um node_modules que tens de continuar a auditar à procura de vulnerabilidades.

A nossa stack de PHP e vanilla tem outro modo de falha e somos honestos quanto a isso: escrevemos mais código à mão e não recebemos de graça o ecossistema de componentes de uma framework. A troca que fizemos é que a plataforma por baixo — a linguagem, as APIs do browser — avança num horizonte de cinco a dez anos, não de seis meses. O artigo de journal que estás a ler vai renderizar corretamente com zero manutenção durante anos. A dependência mais barata de manter é a que nunca acrescentaste.

Como isto se liga aos Core Web Vitals

Os cinco custos acima mapeiam quase um para um nos Core Web Vitals da Google, as métricas de campo que medem a experiência real do utilizador:

O nosso objetivo é um resultado de Lighthouse de 95 ou mais e — mais importante — passar nos dados de campo do CrUX, não apenas uma corrida verde no laboratório. Conseguimo-lo tratando o JavaScript de cliente como uma responsabilidade a justificar e não como um default a assumir. A maior parte das páginas que entregamos carrega kilobytes de script de um só dígito. A framework mais rápida é o código que não enviaste.

Perguntas frequentes

O React é lento?

O React em si não é intrinsecamente lento, mas uma aplicação React típica entrega um runtime mais o código dos seus componentes, que o browser tem de descarregar, interpretar e executar antes de algo ficar interativo. Num telemóvel Android de gama baixa com um CPU fraco, esse passo de interpretação e execução é o verdadeiro custo, acrescentando muitas vezes centenas de milissegundos ao Time to Interactive e ao Interaction to Next Paint, mesmo quando o resultado renderizado é trivial.

Quais são os custos ocultos de usar uma framework JavaScript?

Os custos ocultos são tudo o que vai além do tamanho do bundle comprimido que vê na documentação: o tempo de interpretação e execução de JavaScript em CPUs lentos, o trabalho de hidratação que volta a correr a sua lógica de renderização no cliente, maiores consumos de memória, a energia e o carbono de correr tudo isto em milhões de dispositivos, e o esforço contínuo de manutenção com a rotatividade de dependências e atualizações que quebram. Uma framework de 40 KB pode custar muito mais em segundos-CPU do que o seu tamanho de transferência sugere, porque os bytes são baratos mas interpretá-los e executá-los não.

Uma framework JavaScript prejudica os Core Web Vitals?

Pode prejudicar, sobretudo através do Interaction to Next Paint e do Largest Contentful Paint. JavaScript pesado no cliente bloqueia a main thread durante a hidratação, atrasando a interatividade (INP), e o conteúdo renderizado no cliente costuma empurrar o LCP para mais tarde do que o HTML renderizado no servidor faria. A solução costuma ser menos JavaScript, não uma framework mais rápida.

Quando é que uma framework compensa, e quando devo evitá-la?

Uma framework justifica o seu peso em interfaces genuinamente do tipo aplicação, com estado, como dashboards, editores e ferramentas em tempo real, onde de outra forma teria de reinventar reatividade e routing à mão. Para sites de conteúdo, páginas de marketing e experiências sobretudo estáticas, HTML renderizado no servidor com uma pequena dose de JavaScript de progressive enhancement é mais rápido, mais barato de manter e mais amigo dos dispositivos de gama baixa. Por defeito usamos PHP renderizado no servidor sem build e só adicionamos JavaScript onde a interação realmente o exige.

Como meço o custo real do meu JavaScript?

Meça num dispositivo de gama baixa com throttling, e não no seu portátil de programador, usando o CPU throttling do Chrome DevTools (4–6x mais lento) mais um perfil de rede "Slow 4G". Observe o tempo total de execução de JavaScript e o bloqueio da main thread no painel Performance, e acompanhe o INP e o LCP de campo em monitorização de utilizadores reais, como o Chrome UX Report, em vez de confiar apenas em números de laboratório.

Então quando recorremos a uma framework?

Isto não é niilismo de frameworks. Uma aplicação complexa e com estado — um dashboard, um editor, uma ferramenta em tempo real — é exatamente para o que as frameworks de cliente foram feitas, e reimplementar uma à mão em vanilla JS é o seu próprio erro caro. A questão é fazer a escolha com base em evidência, não em hábito.

A pergunta que fazemos em cada projeto é simplesmente: qual é o orçamento de interatividade, e esta página gasta-o? O orçamento de uma página de conteúdo é quase zero, por isso recebe quase zero JavaScript. O orçamento de uma aplicação é real, por isso gastamo-lo deliberadamente — e mesmo aí apoiamo-nos em ilhas para que a framework só corra onde o estado de facto vive.

Contabiliza todas as cinco rubricas antes de te comprometeres, não só a do selo. O tamanho do bundle é o preço de etiqueta. Tempo de parsing, hidratação, energia e churn são o custo total de propriedade — e num telemóvel real, no terreno, ao longo da vida de um projeto, é essa a conta que de facto chega.