Pitch: Um site 100% estático, zero backend — e mesmo assim precisei resolver 3 problemas clássicos de backend
Nas últimas semanas construí o SIGNAL — um agregador de notícias de tecnologia em HTML, CSS e JS puro (sem framework, sem servidor próprio) que busca manchetes reais de várias fontes e se atualiza sozinho. O objetivo era simples: peça de portfólio, sem infraestrutura paga.
No meio do caminho esbarrei numa ironia: um site sem backend ainda precisa se comportar como se tivesse um. Compartilho aqui as três decisões que mais importaram — não como "olha o que eu fiz", mas como os problemas reais que apareceram e como pensei cada solução.
- Dependência gratuita tem limite, e isso quebra em silêncio
O site busca manchetes do Hacker News (API própria, sem limite de uso) e de veículos como TechCrunch e The Verge via RSS, convertido pra JSON por um serviço gratuito de terceiros. Esse serviço tem cota diária de requisições. Sozinho, testando, eu nunca batia nesse limite. Mas pensando em tráfego real — várias pessoas abrindo o site ao mesmo tempo, cada navegador disparando a mesma leva de chamadas — ficou óbvio que isso quebraria exatamente quando o site tivesse mais visitantes. O pior momento possível pra falhar.
A solução foi tirar essa dependência do navegador do visitante: um Cloudflare Worker busca os feeds direto no servidor (parseando RSS na mão, sem passar pelo serviço terceiro) e cacheia o resultado por alguns minutos. Mil visitantes agora geram o mesmo tráfego pras fontes originais que um visitante só.
Mas o Worker também pode falhar — cair, ficar lento, não estar configurado ainda. Então o carregamento virou uma cadeia de fallback:
async function loadAll() {
let items = await loadFromWorker(); // 1) tenta o cache no servidor
if (!items) items = await loadDirect(); // 2) senão, busca direto no navegador
// 3) se mesmo assim vier vazio, mostra um estado de erro claro
}
Cada camada existe porque a anterior pode falhar, e o visitante nunca deveria perceber isso.
- Uma categoria "padrão" que mentia sobre o conteúdo
Cada notícia é categorizada por palavra-chave (IA, hardware, segurança etc). O que eu não previ: boa parte do que sai no Hacker News não é sobre nenhuma dessas categorias — é filosofia, negócio, cultura. O código tratava esse caso caindo, por padrão, na categoria "Software". Resultado: um post tipo "Is The Economist Always Wrong?" aparecia catalogado como Software — e como eu usava uma foto de banco fixa por categoria, várias notícias sem nada a ver entre si acabavam mostrando literalmente a mesma imagem.
A correção não foi visual, foi lógica: criar uma categoria "Geral" de verdade em vez de forçar tudo num balde errado. E pra resolver a repetição de imagem sem depender de mais fotos de banco (que cedo ou tarde voltam a se repetir), troquei por arte gerada no próprio código — um hash do título escolhe um ícone, um padrão de fundo e uma cor de destaque. Sempre a mesma combinação pro mesmo artigo, sempre diferente de artigo pra artigo, e nunca depende de link externo que pode cair.
- Tradução sob demanda sem estourar limite gratuito (de novo)
As notícias chegam em inglês. Pra oferecer tradução automática sem pagar por API (Google Cloud Translate, DeepL), usei a API gratuita do MyMemory — que também tem cota diária. A saída foi: traduzir só o que está de fato visível na tela (não a lista inteira de uma vez), guardar em cache o que já foi traduzido pra nunca repetir a mesma chamada, e limitar quantas traduções rodam em paralelo. Se a tradução falhar ou bater no limite, o texto original em inglês continua aparecendo — a experiência nunca quebra, só para de traduzir temporariamente.
O padrão que se repetiu
As três situações têm a mesma forma: uma dependência gratuita e útil, com um limite real, e a pergunta "o que acontece quando esse limite é atingido?". Se a resposta é "o site quebra", isso é um problema de arquitetura, não só de código. Tratar isso como fallback em camadas — em vez de simplesmente torcer pra não acontecer — foi a decisão que mais valeu a pena no projeto inteiro.
O site tá no ar aqui: https://techsignal.netlify.app/
Fica aberto a crítica. Se alguém tiver uma abordagem diferente pra esses mesmos problemas (ou já resolveu isso de um jeito melhor em algum projeto próprio), tenho bastante interesse em ouvir nos comentários.