React para quem tem celular fraco: lição de um relato no HN
Alguém postou no Hacker News que paga há dez anos os estudos de um jovem da zona rural da Tanzânia. O post se chama "Tell HN: I've been paying for a rural Tanzanian's education for 10 years". Não é lançamento, nem framework, nem release note. Mesmo assim, ele me fez abrir o projeto em React que estou tocando agora e fazer uma pergunta incômoda: esse app funcionaria na mão de quem está do outro lado dessa história?
Este blog costuma comentar notícia técnica. Essa aqui é humana. Só que todo relato desse tipo esconde uma pergunta técnica. Quando alguém sai da zona rural e chega na escola, na faculdade ou no primeiro emprego, o acesso à internet passa por uma tela. Quase sempre é um Android de entrada, com rede instável e plano de dados contado. E boa parte dessa tela hoje é feita com React.
O que o relato tem a ver com React?
Diretamente, nada. Não li em lugar nenhum que o post fale de código. A ligação é outra.
Pense no caminho de alguém que estuda numa região rural da África Oriental. A matrícula, o material, o resultado da prova, a bolsa, a vaga de emprego: cada vez mais isso tudo vira formulário web. Quem mantém esses formulários somos nós.
E nós desenvolvemos com um MacBook novo, fibra de 500 mega e Chrome com cache quente. O usuário real abre o mesmo site num aparelho com um quarto da sua CPU, em 3G que cai a cada túnel, pagando por megabyte.
O seu usuário mais importante quase nunca tem a sua máquina.
Esse é o ponto que o relato me lembrou. Dez anos bancando a educação de alguém é um compromisso longo e constante. A versão dev disso é bem mais modesta: não deixar a porta pesada demais para quem está tentando entrar.
Quanto pesa um app React num celular barato
Vamos aos números que você consegue conferir aí.
O par react + react-dom em produção fica perto de 60 KB comprimidos com gzip. Parece pouco. O problema é que ninguém entrega só isso. Some o roteador, uma lib de formulário, uma de data, um kit de componentes, um SDK de analytics e um chat de suporte. Não é raro um app "simples" mandar 500 KB a 1 MB de JavaScript comprimido na primeira carga.
Em JavaScript, o tamanho do download é só metade da conta. O navegador ainda precisa descomprimir, fazer o parse, compilar e executar. Num aparelho de entrada, essa parte pode levar várias vezes mais tempo do que no seu notebook. Já vi tela de login travada por segundos num Android antigo enquanto o bundle terminava de rodar, sem nenhum erro no console, só um usuário olhando para um botão que não responde.
Para o usuário, isso aparece de três formas:
- Tela branca demorada antes do primeiro conteúdo.
- Toque que não responde enquanto a hidratação não termina.
- Dados do plano gastos com código que ele nem vai usar naquela visita.
Como testar seu app como se estivesse lá
Você não precisa viajar nem comprar um celular de R$ 400 para ter uma ideia. Precisa de cinco minutos no DevTools.
- Abra o Chrome DevTools, aba Performance.
- Em CPU, escolha 6x slowdown.
- Em Network, escolha Slow 4G ou crie um perfil mais lento.
- Marque Disable cache e recarregue.
- Grave e olhe quanto tempo a thread principal fica ocupada antes da página aceitar um clique.
Faça isso na sua tela mais importante. Pode ser o login, o checkout ou o formulário de cadastro. Na primeira vez dá um pouco de vergonha. Na segunda, dá vontade de abrir um PR.
Se você tiver um Android velho na gaveta, melhor ainda. Ligue o USB debugging, abra chrome://inspect e teste no aparelho de verdade. Nenhuma simulação substitui o aparelho real esquentando na sua mão.
O que mudar no código hoje
Não precisa reescrever nada nem trocar de framework. A maior parte do ganho vem de ajustes chatos e baratos.
Carregue sob demanda o que não aparece na primeira tela. Gráfico, editor rico, mapa, modal de configurações: nada disso precisa estar no bundle inicial.
import { lazy, Suspense } from 'react'
const RelatorioChart = lazy(() => import('./RelatorioChart'))
export function Painel() {
return (
<Suspense fallback={<p>Carregando relatório…</p>}>
<RelatorioChart />
</Suspense>
)
}
Mande menos JavaScript, e não só JavaScript menor. Se você usa Next.js com App Router, Server Components resolvem muita coisa. Uma lista de cursos que só exibe dados não precisa virar código no cliente. Deixe 'use client' só nos pedaços que realmente têm interação.
Olhe o que entrou no bundle. Rode um analisador uma vez e procure as surpresas de sempre: lib de data inteira para formatar um dia, ícones importados em massa, polyfill que nenhum navegador atual precisa.
bunx vite-bundle-visualizer
# ou, no Next.js
ANALYZE=true bun run build
Coloque um limite e deixe o CI cobrar. Uma regra simples evita que o bundle engorde 20 KB por sprint sem ninguém perceber. Com o size-limit, por exemplo:
{
"size-limit": [
{ "path": "dist/assets/index-*.js", "limit": "170 KB" }
]
}
O número exato importa menos do que existir um número. Sem limite, o bundle só cresce.
Formulário que sobrevive à rede ruim. Salve o rascunho no localStorage a cada mudança. Quem preenche uma inscrição longa e perde tudo porque o 3G caiu no último campo raramente tenta de novo.
const [form, setForm] = useState(() =>
JSON.parse(localStorage.getItem('inscricao') ?? '{}')
)
useEffect(() => {
localStorage.setItem('inscricao', JSON.stringify(form))
}, [form])
São seis linhas. Para alguém com plano pré-pago, essas seis linhas podem decidir se a inscrição sai ou não.
"Mas meu público não está na Tanzânia"
Essa objeção é justa, e a resposta é: talvez esteja mais perto do que você imagina.
Aqui no Brasil, muita gente acessa a internet só pelo celular, e uma parte grande disso acontece em aparelho de entrada e em plano pré-pago. Cliente de app de banco, aluno de EAD, entregador olhando a rota, gente do interior com sinal de uma barrinha. Se o seu produto atende "o Brasil", ele atende esse público também, quer você teste para ele ou não.
E tem um efeito colateral bom: tudo o que deixa o app leve num aparelho fraco também deixa ele mais rápido no iPhone do seu chefe. Performance para o pior caso nunca atrapalha o melhor caso. O contrário acontece o tempo todo.
Minha opinião, sem romantizar
Não vou fingir que otimizar bundle é caridade. Não é. A pessoa do relato faz algo muito maior do que qualquer PR que eu vá abrir este ano. Comparar as duas coisas seria ridículo, tipo achar que tirar o moment.js do projeto me qualifica para o Nobel.
Mas o post me lembrou de uma coisa que a gente esquece fácil. Do outro lado da tela tem alguém com menos recursos do que você tentando concluir uma tarefa. Às vezes é uma matrícula, às vezes uma vaga de emprego. React não tem culpa de nada. Ele só obedece ao que a gente escreve.
Minha sugestão prática é pequena: escolha uma tela crítica do seu app, teste com CPU 6x mais lenta e rede ruim esta semana e corrija a pior coisa que aparecer. Uma só já ajuda.
Se quiser ver como venho aplicando isso nos meus projetos, eles estão aqui.
Resumo para o LinkedIn
Um formulário de matrícula pesado pode barrar alguém com um celular fraco. Li no Hacker News o relato de alguém que paga há 10 anos os estudos de um jovem da zona rural da Tanzânia. Fiquei com uma pergunta: meu app React funcionaria na mão dele? A gente programa com MacBook novo e fibra. O usuário real usa um Android de entrada, com 3G instável e plano pré-pago. Aqui no Brasil é igual: muita gente só acessa a internet pelo celular, em aparelho barato. Bastam 5 minutos no DevTools: CPU 6x mais lenta, Slow 4G e cache desligado. Depois, lazy loading, limite de bundle no CI e rascunho salvo no localStorage. Escolha uma tela crítica do seu app, teste assim esta semana e corrija a pior coisa que aparecer. Escrevi o passo a passo no blog, com código. #React #PerformanceWeb #Frontend #Acessibilidade #DesenvolvimentoWeb