Voltar ao blog

Navegador ou servidor: onde gerar as imagens da sua automação

Gerar imagens no navegador ou no servidor? Comparo html2canvas e pureimage no caso real da automação do meu Instagram que parou de postar.

C
Caio Braga
08 de outubro de 2026 · 8 min de leitura
Sumário do artigo
Navegador ou servidor: onde gerar as imagens da sua automação

Durante algumas semanas, o carrossel automático do Instagram do lowcode.ai falhou quase todo dia do mesmo jeito. O painel mostrava um aviso vermelho: limite de 6 tentativas atingido, carrossel ainda renderizando, 1 de 6 imagens prontas, mantenha a aba "Agentes" aberta no navegador. O post ficava girando ali, sem sair do lugar.

A causa era uma decisão que eu nem percebi que tinha tomado: onde gerar as imagens. Se você monta qualquer automação que produz imagem sozinha (carrossel, capa de blog, thumbnail, certificado, card de produto), essa escolha entre gerar imagens no navegador ou no servidor aparece logo no começo. Aqui está o que aprendi comparando as duas abordagens num caso real, com os erros que encontrei no caminho.

Como a automação funcionava

O fluxo era simples de descrever. Um agente busca notícias, o Claude escolhe uma e escreve o texto dos slides, um modelo de imagem gera a capa e o sistema agenda o post para as 7h do dia seguinte. Os slides 2 a 6 seguem um template fixo: fundo, título, texto, paginação e logo.

Esses slides de template eram montados com html2canvas, uma biblioteca que pega um pedaço da página e transforma em imagem. Ela funciona muito bem: você monta o slide em HTML e CSS, com as fontes e cores do site, e ela devolve um PNG praticamente idêntico ao que aparece na tela.

O detalhe é que html2canvas roda dentro do navegador. Alguém precisa estar com a página aberta para o código executar. No meu caso, isso acontecia num useEffect da página Agentes do painel, que eu construí no Lovable: quando a página carregava e encontrava posts pendentes, ela desenhava os slides e subia as imagens para o storage.

Por que o navegador falhava

O post era criado à noite e agendado para as 7h. Às 7h, o publicador (uma função chamada por cron a cada 5 minutos) encontrava o post com só a capa pronta, contava uma tentativa e esperava a próxima rodada. Depois de 6 tentativas, ou seja, meia hora, marcava o post como falho. Ninguém estava com o painel aberto às 7h da manhã, então os slides nunca eram gerados a tempo.

Até aí, o problema era de arquitetura. Mas havia dois bugs em cima dele que explicavam o "renderizando pra sempre", mesmo quando eu abria a página:

  1. A consulta que alimentava o renderizador buscava só posts com status rascunho ou agendado. Quando o post virava "falhou", ele saía da fila, mas a marcação de "slides pendentes" continuava ativa. O selo "Renderizando slides…" lia só essa marcação, então girava sem nada rodando por trás.
  2. Se a renderização desse erro uma vez (uma fonte que não carregou, um upload que falhou), o código mostrava um aviso e não tentava de novo. A consulta era refeita a cada minuto, mas devolvia os mesmos dados, a biblioteca de cache mantinha a mesma referência e o efeito do React não disparava outra vez. Só recarregando a página.

Esse segundo bug é traiçoeiro porque o painel parecia vivo. A tela atualizava, o aviso mudava de horário, e nada acontecia.

A versão no servidor

Eu tinha outro projeto, um blog de casa com a mesma automação, onde os posts saíam sozinhos sem eu entrar no painel. A diferença estava justamente aí: lá, as imagens eram geradas no servidor.

Como os dois projetos usam a mesma base (TanStack Start rodando no Cloudflare Workers, com Supabase no banco), a migração foi quase direta:

  • Uma rota no próprio site recebe a chamada, busca até 5 posts com slides pendentes, desenha cada slide e sobe para o storage.
  • O desenho usa pureimage, uma implementação de canvas escrita em JavaScript puro, que roda em ambiente de servidor sem navegador.
  • O banco chama essa rota a cada 5 minutos com pg_cron, enviando um segredo no cabeçalho para ninguém de fora disparar a rota.
  • Logo depois que o agente cria o post, ele já chama a rota uma vez, então na maioria dos casos o carrossel fica pronto em poucos minutos. O cron é a rede de segurança.
  • Antes de desenhar, a rota grava um horário de "início da renderização" no post. Isso funciona como reserva: se o disparo imediato e o cron chegarem juntos, só um deles processa o post.
  • O publicador parou de contar tentativa enquanto os slides estão pendentes. Ele só espera a próxima rodada, com um limite de 2 horas depois do horário agendado.

O renderizador do navegador continua existindo, mas virou um botão manual, "Renderizar agora", para emergência.

O que quebrou no caminho

Gerar imagem no servidor tem armadilhas próprias, e a maioria só aparece no ambiente publicado.

A primeira tentativa no outro projeto usava Satori com Resvg, a dupla que muita gente usa para gerar imagens Open Graph. Ela depende de WebAssembly e quebrou em produção com o erro "Wasm code generation disallowed by embedder". O Cloudflare Workers não permite compilar WebAssembly a partir de bytes em tempo de execução. Por isso a escolha final foi uma biblioteca sem WASM.

A segunda armadilha veio do próprio pureimage: o degradê linear dele ignora transparência. Um degradê de preto para transparente, para escurecer a parte de baixo de uma foto e deixar o texto legível, saía sólido. A solução foi desenhar o degradê em faixas finas com fillRect, cada uma com uma opacidade um pouco diferente.

A terceira foi a leitura de JPEG. No outro projeto, a capa vinha de uma foto, e decodificar a imagem a partir de stream falhava sem erro nenhum dentro do Workers. A versão que funcionou baixa o arquivo inteiro para a memória e decodifica com jpeg-js.

E as fontes precisam viajar junto. No navegador, o slide usa as fontes que a página já carregou. No servidor não existe página: os arquivos TTF ficam empacotados no projeto e são registrados no pureimage antes de desenhar.

Comparativo direto

Critério Navegador (html2canvas) Servidor (pureimage)
Precisa de alguém com a página aberta Sim Não
Funciona de madrugada e no fim de semana Só se a aba estiver aberta Sim
Fidelidade ao layout Copia o que está na tela, com todo o CSS Redesenho em código, exige ajuste fino
Fontes As que a página já carregou TTF empacotado no projeto
Onde aparecem os erros Console do navegador de quem estava com a aba aberta Log do servidor e campo de erro no banco
Custo de manutenção Baixo para começar Maior no início, previsível depois
Melhor uso Prévia, editor visual, exportação sob demanda Qualquer coisa que roda sozinha

O ponto que mais pesa é a primeira linha. Uma automação que depende de alguém com uma aba aberta só automatiza metade do trabalho. A outra metade continua sendo você lembrando de abrir o painel.

Quando o navegador ainda faz sentido

O html2canvas continua útil em vários cenários: um editor onde a pessoa ajusta o slide e baixa a imagem na hora, uma prévia antes de publicar, um botão de exportar card. Em todos eles existe alguém olhando para a tela no momento em que a imagem é gerada, e a fidelidade ao CSS vira vantagem, porque o que a pessoa vê é exatamente o que ela baixa.

O problema começa quando uma etapa que precisa rodar sozinha fica dentro de um componente de tela. Isso acontece muito em projeto feito com IA: você pede "gere os slides e salve", o código mais rápido de escrever usa o navegador, e funciona perfeitamente em todos os testes, porque durante o teste você está com a página aberta. Se você já passou por algo parecido, o guia de como debugar um app que a IA escreveu ajuda a achar esse tipo de dependência escondida.

Veredito

Se a imagem precisa sair sem você, gere no servidor. O navegador fica para tudo que acontece com você presente.

No meu caso, a troca resolveu as falhas das 7h e ainda deixou o sistema mais fácil de diagnosticar: quando algo dá errado, o motivo fica gravado no post e aparece no painel, em vez de sumir no console de um navegador que ninguém abriu. Erros de rede e de limite de API seguem a mesma lógica, e eu detalhei esse lado em rate limit, timeout e retry.

O que fazer agora

  1. Liste cada etapa da sua automação e marque as que rodam dentro de uma página. Procure no código por html2canvas, canvas.toDataURL, toBlob ou qualquer geração de arquivo disparada por useEffect.
  2. Para cada etapa marcada, pergunte: ela precisa acontecer num horário em que ninguém vai estar online? Se sim, ela vai para o servidor.
  3. Crie uma rota de servidor ou edge function que processe um lote pequeno por vez (5 itens é um bom começo) e chame essa rota por cron. Grave um horário de início antes de processar cada item, para evitar processamento duplicado.
  4. Se o seu projeto roda no Cloudflare Workers, escolha bibliotecas sem WebAssembly ou confirme que a biblioteca suporta o ambiente, e teste no ambiente publicado antes de confiar no teste local.
  5. Separe gerar de publicar. O publicador deve esperar a geração terminar, sem queimar tentativas enquanto ela não acontece.
  6. Grave o erro no próprio registro e mostre no painel. Erro que só existe no console não ajuda ninguém às 7h da manhã.
  7. Antes de colocar tudo no ar, passe pelo checklist de segurança do Lovable com Supabase, principalmente a parte de rotas públicas protegidas por segredo.
Tags
#automacao#instagram#lovable#cloudflare workers#geracao de imagens
● Não perca essa chance

Não perca a próxima edição.

Toda quinta, 9h. Direto na sua caixa.

  • Ferramentas que economizam horas do seu trabalho
  • Agentes e automações que funcionam
  • Bastidores do que estamos construindo

100% gratuito. Cancele quando quiser.

Compartilhar