PWA com HTML, CSS e JS puro: por que evitei framework
A escolha de não usar React no Prev Calc não foi nostalgia. Foi cálculo: bundle size, tempo de hidratação, complexidade de build e a régua de manutenção a longo prazo.
Quando contei que o Prev Calc era HTML, CSS e JavaScript puro, recebi mais perguntas sobre essa escolha do que sobre o cálculo de previdência em si.
"Por que não React?" "Por que não Vue?" "Por que não Astro, pelo menos?"
A resposta curta: porque framework não me dava nada que eu precisasse, e cobrava coisas que eu não queria pagar. A resposta longa é esse post.
O que um framework te dá
Antes de defender minha escolha, é honesto reconhecer o que React (ou similar) entrega:
- Componentização — reutilização e composição de UI
- Estado reativo —
useState,useEffectautomatizam atualizações de DOM - Roteamento e navegação — React Router, Next.js routing
- Ecossistema — bibliotecas para tudo (forms, dates, charts, auth)
- Hot reload e dev experience — ferramental moderno
- TypeScript de primeira classe — tipagem fluida em componentes
Para um app com 50+ telas, formulários complexos, fluxos de auth, isso justifica o custo. O Prev Calc tem 5 telas.
O custo que poucos contam
O custo de um framework não é só o bundle JavaScript. É:
- Build pipeline. Webpack, Vite ou Turbopack. Lock file. Update de dependências. Patch de segurança. CVEs no pipeline de build.
- Hidratação. O HTML chega rápido, mas o app só fica interativo depois que o JavaScript executa.
- Versionamento de runtime. React 16 → 17 → 18 → 19. Server Components mudou tudo. Cada major rompe alguma coisa.
- Stack de dependências. React tem ~50KB. Mas você raramente usa "só React" — você usa React + React DOM + React Router + uma lib de form + uma lib de date + um charting + uma de fetch...
- Hosting que entende isso. SSR vs SSG vs ISR. Edge runtime. Você precisa de uma plataforma como Vercel ou cuidar do servidor.
Para um app de 5 telas com cálculo síncrono no client, isso é overkill com taxa de manutenção mensal.
A régua que apliquei
Antes de cravar a stack, fiz quatro perguntas:
1. O app precisa de navegação SPA real?
Não. As 5 telas são abas de uma calculadora. Mostro/escondo <section> com display. O Prev Calc não precisa de URL por tela.
2. Tenho componentes reutilizáveis suficientes para justificar componentização? Não. São inputs, sliders e o gráfico. Cada elemento tem layout próprio, comportamento próprio.
3. O estado é complexo?
Não. É um único objeto de "configuração de simulação" que recalcula tudo a cada mudança. addEventListener('input', recalcular) resolve.
4. Quero que esse app rode em 2030? Sim. E HTML, CSS e JS vanilla são as únicas tecnologias web que eu tenho razoável certeza que vão estar funcionais sem update em cinco anos.
Resposta nesse caso específico: o framework não pagaria, e teria risco de virar dívida.
A stack que sobrou
prev-calc/
├── index.html // 5 <section> com inputs e o gráfico
├── style.css // ~400 linhas, vanilla CSS com custom properties
├── calc.js // o motor de simulação (funções puras)
├── ui.js // event handlers, atualização do DOM
├── chart.js // wrapper sobre Chart.js para renderizar gráficos
├── sw.js // Service Worker para offline
├── manifest.json // PWA manifest
└── assets/
└── icons/ // PNGs em vários tamanhos
Sem package.json. Sem node_modules. Sem build.
O deploy é: git push para o repositório, e o Vercel serve os arquivos estáticos. Total: ~150KB de assets, ~50KB de JavaScript não-comprimido. Carrega em 200ms até em 3G.
Estado reativo sem framework
A pergunta mais frequente: "como você gerencia estado sem React?"
Resposta:
// Estado da simulação
const state = {
idade: 35,
contribMensal: 1000,
taxaReal: 5,
inflacao: 4,
// ...
};
// Função pura: recebe state, retorna resultado
function simular(state) {
// ...cálculos...
return { patrimonioFinal, rendaMensal, fluxos };
}
// Renderização: recebe resultado, atualiza DOM
function renderizar(resultado) {
document.getElementById("patrimonio").textContent = formatar(resultado.patrimonioFinal);
document.getElementById("renda").textContent = formatar(resultado.rendaMensal);
atualizarGrafico(resultado.fluxos);
}
// Loop reativo
document.querySelectorAll("input").forEach((input) => {
input.addEventListener("input", (e) => {
state[e.target.name] = parseFloat(e.target.value);
const resultado = simular(state);
renderizar(resultado);
});
});
São 30 linhas. Isso replica o que useState + useEffect fazem em um framework.
A diferença é que aqui o state é uma variável, não um hook. Você lê e escreve diretamente. Não tem cerimônia, não tem regras de "não pode mutar", não tem useCallback para memoizar. É só JavaScript.
Service Worker para offline
A parte que importa para um PWA real: funcionar sem internet.
// sw.js
const CACHE_NAME = "prev-calc-v1";
const ASSETS = [
"/",
"/index.html",
"/style.css",
"/calc.js",
"/ui.js",
"/chart.js",
"/manifest.json",
// ...
];
self.addEventListener("install", (e) => {
e.waitUntil(caches.open(CACHE_NAME).then((c) => c.addAll(ASSETS)));
});
self.addEventListener("fetch", (e) => {
e.respondWith(
caches.match(e.request).then((res) => res || fetch(e.request)),
);
});
50 linhas, cache-first, funciona. O usuário instala o app, e ele continua respondendo offline. Zero servidor, zero infraestrutura.
O que dói nessa stack
Não é tudo flores. Coisas que dói:
1. Sem TypeScript. Para esse projeto, eu tolerei. JSDoc + IDE com inferência cobre 60% dos benefícios. Mas para qualquer coisa maior, perderia TypeScript me afetaria.
2. Manipulação manual de DOM.
Em projetos pequenos, getElementById + textContent é OK. Em projetos com listas dinâmicas longas, vira um inferno. Aí, sim, framework paga.
3. Sem componentes reutilizáveis automáticos.
Se eu quiser reaproveitar um "input com label e tooltip" em 20 lugares, preciso fazer um helper. Em React eu faria um <Field /> em 5 minutos.
4. Falta de convenções.
Em projeto solo, isso é só estilo pessoal. Em time, vira anarquia. Cada dev tem sua maneira de organizar addEventListener.
A pergunta que importa
A escolha de framework não é estética. É quanto da complexidade da sua aplicação justifica importar a complexidade da ferramenta.
Para o Prev Calc, a aplicação é simples (5 telas, cálculo síncrono, sem auth, sem servidor). Importar React e o ecossistema dele triplicaria a complexidade do projeto sem entregar valor.
Para o BeachTennis Manager — outro projeto meu, com 30+ telas, sync na nuvem, formulários complexos, autenticação — Next.js é a escolha que faz mais sentido.
A régua não é "framework é bom" ou "framework é ruim". É: a complexidade do problema justifica a complexidade da ferramenta?
Se sim, framework. Se não, vanilla.
E em mais projetos do que costuma se assumir por padrão, a resposta honesta é "não".