O paradoxo da performance no frontend e o pato
Olá, galera do TabNews!
Front-end...
Nós provavelmente somos uma das áreas que mais falam sobre performance e, ao mesmo tempo, uma das que mais normalizaram jogar abstrações, dependências e JavaScript no problema até ele desaparecer.
Falamos de Core Web Vitals.
Falamos de tree shaking.
Falamos de lazy loading, code splitting, Server Components, SSR, streaming, hydration e redução de bundle.
Aí chega segunda-feira e precisamos colocar um gráfico de sete barras em um dashboard:
npm install alguma-lib-de-graficos
E, de repente, temos dezenas ou centenas de kilobytes de JavaScript, dependências transitivas, abstrações que nunca vamos utilizar e uma API gigantesca para resolver algo que, conceitualmente, é desenhar sete retângulos.
É daí que nasce o que eu chamo de paradoxo da performance no frontend.
Nós nos preocupamos profundamente com performance — depois que construímos sistemas que precisam ser constantemente otimizados para recuperá-la.
Frontend não é "fazer telinha"
Existe ainda uma percepção curiosa de que frontend é simplesmente a camada visual do software.
Botão.
Modal.
Formulário.
Tabela.
Gráfico.
Só que o frontend moderno deixou de ser apenas apresentação há muito tempo.
Uma aplicação React pode administrar estado, cache, sincronização com servidor, autenticação, roteamento, validação, optimistic updates, internacionalização, acessibilidade, observabilidade, telemetria e uma quantidade considerável de regras de negócio.
React, Next.js, Angular, Vue, Nuxt, SvelteKit e tantos outros projetos não existem simplesmente porque desenvolvedores queriam maneiras diferentes de escrever um <button>.
Eles existem porque estamos tentando resolver um problema bastante complicado:
como construir aplicações cada vez maiores, com cada vez mais funcionalidades, por equipes cada vez maiores, sem perder completamente a capacidade de entregar software?
E acredito que isso criou uma característica importante na cultura do frontend:
ship fast, optimize later.
Não necessariamente porque os desenvolvedores são negligentes.
É consequência dos incentivos.
Uma feature entregue hoje tem valor imediatamente visível para o negócio.
Uma dependência que adicionou 80 kB ao bundle dificilmente gera uma reunião emergencial.
Uma abstração desnecessária não aparece no Jira.
Um componente que poderia ter 100 linhas mas utiliza quatro bibliotecas diferentes continua aparecendo como:
✅ Feature concluída.
A dívida aparece depois.
O frontend é cheio de patos

Existe uma metáfora que gosto bastante para pensar sobre isso.
O frontend moderno é cheio de patos.
O pato nada.
O pato anda.
O pato voa.
Ele consegue fazer praticamente tudo.
Só que dificilmente será o melhor animal em qualquer uma dessas coisas.
Boa parte das ferramentas frontend modernas funciona dessa maneira.
São ferramentas extraordinariamente generalistas.
Precisamos de renderização no servidor?
Tem.
Aplicação SPA?
Tem.
Site estático?
Tem.
API?
Tem.
Streaming?
Tem.
Middleware?
Tem.
Cache?
Tem.
Aplicação híbrida?
Também tem.
Isso é extremamente conveniente.
E conveniência é uma característica importantíssima de uma ferramenta.
O problema começa quando conveniência passa a ser confundida com eficiência.
Não escolhemos necessariamente a ferramenta que executa melhor determinada tarefa. Frequentemente escolhemos aquela que consegue resolver o maior número possível de problemas suficientemente bem.
E isso faz muito sentido economicamente.
Uma empresa não quer necessariamente cinco especialistas trabalhando com cinco tecnologias diferentes.
Ela quer contratar pessoas que consigam entregar.
Quanto maior o ecossistema, maior a quantidade de problemas que já possuem uma solução pronta:
npm install problema-resolvido
E seguimos para a próxima tarefa.
A sprint precisa terminar
Acho que existe outro fator muito mais importante que qualquer discussão técnica:
software corporativo possui prazo.
Se existe uma biblioteca que resolve meu problema em uma tarde e desenvolver uma implementação específica levaria três dias, existe uma pressão completamente racional para utilizar a biblioteca.
Multiplique isso por:
- componentes;
- formulários;
- tabelas;
- gráficos;
- datas;
- autenticação;
- analytics;
- gerenciamento de estado;
- internacionalização;
- validação;
- animações.
Individualmente, praticamente todas as decisões parecem razoáveis.
O problema é que arquitetura também é o resultado acumulado de centenas de decisões localmente razoáveis.
Nenhum desenvolvedor decidiu:
"Hoje vou adicionar 2 MB de JavaScript ao produto."
Foram 30 decisões de:
"Não vale a pena reinventar isso."
E talvez nenhuma delas estivesse errada isoladamente.
Esse é justamente o ponto.
Quando até o frontend começa a ser dividido
Talvez um dos exemplos mais interessantes dessa busca por velocidade organizacional sejam os microfrontends.
A ideia é levar parte da filosofia dos microservices para a interface: dividir uma aplicação grande em unidades menores que possam ser desenvolvidas, testadas e implantadas independentemente.
Isso permite que uma equipe altere uma parte do produto sem precisar coordenar cada release com todas as outras equipes.
Mas existe algo interessante nisso.
Criamos sistemas tão grandes que passamos a introduzir fronteiras arquiteturais dentro da própria interface para preservar autonomia de desenvolvimento.
Microfrontends podem reduzir o raio de impacto de determinadas mudanças e permitir pipelines e deploys independentes. Ao mesmo tempo, eles introduzem seus próprios custos: contratos entre aplicações, integração em runtime, possíveis dependências duplicadas e maior complexidade operacional.
Ou seja:
estamos trocando complexidade técnica por independência organizacional.
E, em empresas suficientemente grandes, essa troca pode fazer todo sentido.
Talvez esse seja um dos melhores exemplos de como arquitetura de frontend não é determinada somente por performance.
Ela também é determinada pela estrutura das equipes e pela necessidade de continuar entregando software.
Mas a web nem sempre foi construída assim
Existe uma armadilha fácil aqui: dizer que “antigamente tudo era melhor”.
Não era.
A web antiga tinha seus próprios horrores. Compatibilidade entre navegadores era um inferno, Internet Explorer exigia hacks específicos, CSS tinha limitações enormes e atualizar uma pequena parte da página frequentemente significava recarregar tudo.
Mas havia uma diferença arquitetural importante:
o navegador não era necessariamente o lugar onde a aplicação inteira precisava existir.
Durante muito tempo, boa parte da web funcionava de uma maneira quase brutalmente simples.
Você tinha PHP no servidor:
<?php foreach ($produtos as $produto): ?>
<div>
<h2><?= $produto["nome"] ?></h2>
<span>R$ <?= $produto["preco"] ?></span>
</div>
<?php endforeach; ?>
O servidor buscava os dados, processava a regra necessária e devolvia:
<div>
<h2>Teclado mecânico</h2>
<span>R$ 299</span>
</div>
E acabou.
O navegador recebia HTML.
Não existia necessariamente um runtime inteiro reconstruindo a interface.
Não existia hidratação daquela lista.
Não existia estado React representando os produtos.
Não existia uma camada de fetching no cliente para buscar novamente uma informação que o servidor já possuía.
Não existia useEffect esperando a página montar para descobrir o que deveria aparecer nela.
PHP foi provavelmente o maior representante dessa filosofia, mas não estava sozinho.
Tivemos ASP e depois ASP.NET, JSP/Servlets no ecossistema Java, Ruby on Rails, Django em Python e vários sistemas de templates server-side.
Cada ecossistema tinha seus problemas, mas compartilhavam uma ideia extremamente poderosa:
o servidor produz o documento e o navegador apresenta o documento.
JavaScript entrava principalmente quando precisávamos de comportamento adicional.
PHP era bonito?
Depende do PHP que você viu.
Quem já abriu um sistema legado com SQL, HTML, CSS e regra de negócio misturados no mesmo arquivo provavelmente está rindo agora.
Mas não estou defendendo PHP spaghetti.
Estou falando do modelo arquitetural.
Era muito difícil superar a simplicidade operacional de:
Request
↓
Servidor
↓
Banco
↓
Template
↓
HTML
↓
Browser
Compare conceitualmente com uma aplicação moderna:
Request
↓
CDN
↓
SSR / Server Component
↓
API
↓
Client Component
↓
Hydration
↓
State
↓
Cache
↓
Re-render
É claro que a segunda arquitetura consegue fazer coisas que seriam muito mais difíceis na primeira.
Esse é justamente o ponto.
Nós ganhamos capacidade em troca de complexidade.
O problema aparece quando utilizamos a segunda arquitetura para resolver problemas que ainda poderiam ser resolvidos perfeitamente pela primeira.
O curioso retorno das ideias antigas
E existe uma ironia nisso tudo.
Depois de anos levando cada vez mais responsabilidade para o cliente, o frontend começou a descobrir novamente o servidor.
SSR.
Static Site Generation.
Server Components.
Streaming HTML.
Server Actions.
Islands Architecture.
Progressive Enhancement.
Até frameworks modernos começam a perguntar:
“Será que esse JavaScript realmente precisa chegar ao navegador?”
Não estamos voltando para 2005.
As ferramentas são incomparavelmente mais poderosas.
Mas várias das ideias consideradas modernas são, em algum nível, uma redescoberta de uma propriedade antiga da web:
se algo pode ser resolvido antes de chegar ao cliente, talvez não precisemos transformar isso em trabalho para o cliente.
Talvez o PHP não estivesse tão errado assim.
Um pequeno jabá: foi assim que comecei o pure-svg-charts
Toda essa discussão começou para mim por causa de uma situação ridiculamente simples:
eu precisava colocar gráficos em React.
E temos bibliotecas excelentes para isso.
Recharts, Chart.js, Victory, ECharts, D3 e várias outras.
Só que comecei a olhar para o que realmente precisava em boa parte dos dashboards:
linha.
barra.
área.
eixo.
tooltip.
E então olhar para tudo que estava entrando no projeto para fazer isso.
Foi daí que surgiu um experimento pessoal:
até onde dá para levar uma biblioteca de gráficos React se a restrição principal for fazer menos?
Estou desenvolvendo o pure-svg-charts.
A proposta é deliberadamente limitada:
SVG nativo, zero dependências de runtime e bundle abaixo de 12 kB gzip.
Não quero substituir D3.
Não quero construir um novo ECharts.
E definitivamente não quero que daqui a três anos a biblioteca tenha 300 opções diferentes de configuração e 40 dependências.
Para não cair no clássico:
blazing fast 🚀
montei também uma suíte de benchmarks com um dataset de 5.000 pontos:
| Biblioteca | Motor | Gzip | Dependências | Mount | Nós DOM | Streaming |
|---|---|---|---|---|---|---|
| pure-svg-charts | SVG | 11.1 kB | 0 | 1.45 ms | 33 | 0.95 ms |
| Recharts | SVG + D3 | 162.4 kB | 14 | 132.50 ms | 106 | 138.20 ms |
| Chart.js + react-chartjs-2 | Canvas | 68.2 kB | 4 | 0.45 ms | 7 | 0.35 ms |
| Victory | SVG + D3 | 184.6 kB | 22 | 48.20 ms | 69 | 38.50 ms |
E aqui existe uma observação importante:
esse benchmark não prova que pure-svg-charts é melhor que essas bibliotecas.
Chart.js, inclusive, é mais rápido nesse cenário.
D3, ECharts, Recharts e Victory também conseguem resolver problemas que minha biblioteca simplesmente não tenta resolver.
Para mim, o resultado interessante é outro:
quanto podemos economizar quando aceitamos deliberadamente resolver um problema menor?
Às vezes, especialização também é uma otimização.
Fim do jabá.
No fim, o pato virou o desenvolvedor
E é aqui que volto para o pato.
No começo falei sobre ferramentas que parecem patos.
Nadam.
Andam.
Voam.
Fazem um pouco de tudo porque precisam funcionar em praticamente qualquer cenário.
Mas existe uma consequência interessante dessa evolução:
o próprio desenvolvedor frontend virou um pato.
Olhe para uma vaga moderna de Frontend Engineer.
Naturalmente você espera encontrar:
HTML
CSS
JavaScript
TypeScript
React
Perfeito.
Então você continua descendo.
Next.js
SSR
SSG
React Query
Redux
WebSockets
REST
GraphQL
Tudo bem.
Ainda estamos relativamente perto de casa.
Continua:
Jest
Testing Library
Cypress
Playwright
Storybook
Design System
Accessibility
SEO
Core Web Vitals
Ok.
Continua.
Node.js
Docker
CI/CD
GitHub Actions
AWS / Azure / GCP
Kubernetes
Observability
Datadog
Grafana
OpenTelemetry
E, em algumas vagas:
Microservices
Kafka
Redis
CDN
Edge Computing
BFF
OAuth
OpenID Connect
Terraform
Em algum momento você olha para a descrição e pensa:
eu ainda estou concorrendo para frontend?
"Mas frontend precisa saber Kubernetes?"
Talvez.
E esse “talvez” diz bastante sobre onde chegamos.
Um frontend moderno não vive isolado.
Se você trabalha com SSR, precisa entender minimamente o servidor.
Se utiliza Next.js, precisa entender o que executa no cliente e o que executa no servidor.
Se sua aplicação está containerizada, Docker deixa de ser completamente distante.
Se existem pipelines de deploy, CI/CD começa a fazer parte da sua rotina.
Se a aplicação possui problemas em produção, observabilidade importa.
Se você precisa diagnosticar por que uma página demora quatro segundos para aparecer, talvez tenha que entender CDN, cache, HTTP, DNS, API, banco, processamento no backend e execução no browser.
Se trabalha com microfrontends, arquitetura distribuída começa a aparecer.
Se trabalha com autenticação, inevitavelmente aparecem cookies, tokens, OAuth, CORS, CSP e segurança web.
Se trabalha com performance, começa a precisar entender browser internals, parsing, rendering pipeline, layout, paint, compositing, memória e garbage collection.
Se trabalha com acessibilidade, precisa entender semântica, tecnologias assistivas e comportamento nativo do navegador.
Se trabalha com Design System, começa a tocar em design, UX, arquitetura de componentes e governança.
E se trabalha com um produto grande o suficiente, provavelmente precisa entender pelo menos um pouco de tudo isso.
Não necessariamente para administrar um cluster Kubernetes.
Não necessariamente para configurar uma VPC do zero.
Não necessariamente para ser DBA.
Mas o suficiente para saber onde o frontend termina e onde o problema começa.
Frontend virou uma área multidisciplinar
Talvez seja por isso que seja cada vez mais difícil explicar para alguém de fora o que exatamente um Frontend Engineer faz.
A resposta simples seria:
“Ele faz a interface.”
Mas isso descreve cada vez menos o trabalho.
O frontend está no encontro entre várias disciplinas:
UX / DESIGN
│
│
BACKEND ─────── FRONTEND ─────── BROWSER
│
│
DEVOPS / CLOUD
│
│
PERFORMANCE / DATA
É provavelmente uma das poucas áreas da engenharia de software em que uma mudança aparentemente inocente na interface pode exigir conhecimento sobre:
- redes;
- protocolos web;
- APIs;
- segurança;
- infraestrutura;
- design;
- acessibilidade;
- performance;
- arquitetura;
- testes;
- observabilidade;
- SEO;
- comportamento do navegador.
O desenvolvedor frontend moderno não precisa necessariamente dominar todas essas áreas.
Mas precisa saber o suficiente de várias delas para conseguir desempenhar bem a própria função.
Ele precisa nadar.
Precisa andar.
E de vez em quando alguém coloca Kubernetes na descrição da vaga e manda ele voar também.
O frontend virou o pato da engenharia de software.
E talvez isso explique parte do paradoxo
Existe então uma contradição ainda maior.
Queremos que o frontend seja simples.
Mas esperamos que ele rode em milhares de dispositivos diferentes.
Queremos desenvolvimento rápido.
Mas esperamos SSR, acessibilidade, SEO, responsividade, testes e observabilidade.
Queremos bundles pequenos.
Mas queremos um ecossistema com uma biblioteca pronta para cada problema.
Queremos especialistas.
Mas publicamos vagas procurando alguém que conheça React, Node, Docker, AWS, Kubernetes, CI/CD, Figma, performance e talvez faça um cafezinho se sobrar tempo.
Queremos abstrações para esconder a complexidade.
Depois precisamos aprender como essas abstrações funcionam internamente quando alguma delas quebra.
Talvez seja por isso que performance no frontend seja uma discussão tão interessante.
Performance não é apenas fazer uma função executar em 1 ms em vez de 5 ms.
É decidir quanto trabalho deveria existir em primeiro lugar.
Tree shaking é ótimo.
Lazy loading é ótimo.
Code splitting é ótimo.
Memoização pode ser ótima.
Mas nenhum deles é tão eficiente quanto não enviar código desnecessário.
E é aqui que acho perigosa a interpretação que demos à famosa ideia de Donald Knuth sobre otimização prematura.
Evitar uma alocação microscópica em um código executado três vezes pode ser otimização prematura.
Passar três dias tentando economizar 800 bytes provavelmente também.
Mas perguntar:
“Precisamos realmente de toda essa arquitetura para resolver esse problema?”
não é otimização prematura.
É arquitetura.
Talvez precisemos aprender a fazer menos
Não acho que a solução seja abandonar React.
Muito menos voltar todo mundo para PHP.
Também não acho que devemos implementar manualmente cada dropdown porque instalar uma dependência fere nossos princípios.
Isso seria apenas trocar um extremo pelo outro.
Mas acho saudável recuperar uma pergunta que às vezes desaparece no desenvolvimento moderno:
qual é a menor quantidade de tecnologia necessária para resolver bem este problema?
Às vezes a resposta será PHP entregando HTML.
Às vezes será React.
Às vezes Next.js.
Às vezes um monolito.
Às vezes microfrontends.
Às vezes D3.
Às vezes uma biblioteca de 200 kB é absolutamente justificável.
E às vezes queremos desenhar sete barras.
O importante é perceber que cada camada possui um custo.
Cada abstração possui um custo.
Cada dependência possui um custo.
Cada generalização possui um custo.
E conveniência não elimina esse custo — apenas faz com que seja mais fácil não enxergá-lo.
No fim, talvez responsabilidade técnica não seja escolher sempre a solução mais performática.
Também não é escolher sempre aquela que entrega mais rápido.
É entender conscientemente o que estamos comprando e o que estamos pagando com cada decisão.
Se uma biblioteca de 160 kB economiza uma semana de desenvolvimento e resolve exatamente o problema do produto, talvez seja uma ótima troca.
Se estamos adicionando 160 kB porque ninguém parou cinco minutos para perguntar se precisava, talvez não seja.
E vocês?
Até onde o frontend deveria ir?
Kubernetes, cloud, observabilidade e arquitetura distribuída fazem parte naturalmente da profissão hoje ou estamos transformando o frontend em um pato que precisa saber fazer tudo?
E, principalmente:
onde vocês traçam a linha entre “não reinvente a roda” e “talvez essa roda esteja carregando peso demais”?
E aproveitando o jabá: se alguém quiser testar, quebrar os benchmarks ou contribuir com o pure-svg-charts: