Pessoal, tinha campanha de influenciador no ar. Eu abria o Sentry e via timeout. O canal falava de Lighthouse.
Checkout de infoproduto, alto volume, micro frontend. O link saía, o pico chegava junto, e o passo de pagar transbordava. A sessão morria. No dia seguinte o painel caía e a conversa era score. Ou Nuxt.
Aí o backoffice do cliente fechou o mês. +10% de conversão. Pico nessas campanhas. Métrica de venda, cruzada com o que eu via no Sentry (jornada de pagar) e no Datadog (o caminho até o arquivo). O JS chegou a tempo. O timeout apareceu no mesmo dia.
O ponto é: o Lighthouse verde não pagou. O POST de pagar, sim.
Um limite só: não tenho o print do Lighthouse daquela janela no arquivo público. O +10% veio do painel.
Como etiquetar o Sentry pra isso não virar TypeError órfão fica no complementar. Aqui é o dinheiro.
Tabela de Conteúdo
- 1. Sintoma: a campanha quebrava na hora de pagar
- 2. Causa: o MFE baixava o time inteiro
- 3. O que mudou: SSR, JS sob demanda, CDN
- 4. Como eu medi: quatro lentes
- 5. Resultado: +10% no mês
- 6. O que copiar no próximo PR
1. Sintoma: a campanha quebrava na hora de pagar
Checkout de infoproduto no organic é uma fila educada. Na campanha de influenciador não é. O link sai, o pico chega junto, e o passo de pagar ou aguenta o first load ou transborda.
Timeout, pra mim, era o browser não terminar o trabalho a tempo. A pessoa já tinha escolhido o produto. Muitas vezes o pixel de view já tinha ido. Faltava o POST de pagamento. Se o JS daquele passo ainda tava puxando o mundo, a sessão morria. O backoffice nem via a tentativa. No dia seguinte alguém falava “a campanha foi ruim”.
Transbordo é o irmão: origem fria, fila, saturação, bundle grande demais pro celular na 4G. Os dois matam a mesma linha do painel.
No canal a resposta fácil era stack. Vue 3, Nuxt, Lighthouse verde. Eu fiz isso também. Nenhum desses, sozinho, prova que vendeu. O Lighthouse roda no lab, no cabo, num CPU que não é o do comprador no horário da live.
Se o teu checkout só dói no organic, o bundle inchado passa. Dói quando o influenciador manda o link.
2. Causa: o MFE baixava o time inteiro
O checkout era um micro frontend. Vários times, várias fatias, um único caminho de pagamento. Isso é bom pra merge. Time de cupom não espera time de pixel. MFE resolve deploy. Não resolve o que o celular baixa nos primeiros dois segundos.
O que rolava: o host puxava o shell e, se ninguém olhasse o grafo, cada fatia trazia o que era dela e um pedaço do vizinho. Runtime duplicado. Pixel. Upsell. Modal. Às vezes um resto de área logada. O comprador, no passo de pagar, baixava isso antes de ver o botão.
SPA seca piora. HTML quase vazio, JS inteiro do MFE, parse, hidrata, aí o botão existe. No Wi-Fi do escritório passa. Na live, no celular, é timeout.
Pra mim a conta virou uma frase só: o passo atual baixa o JS do passo atual. O resto espera o clique.
“A gente tem MFE” descreve o organograma. Não descreve o Network daquele passo. Se você abre o DevTools no pagar e ainda vê chunk de dashboard, o MFE não te salvou. Ele te faturou o first load.
3. O que mudou: SSR, JS sob demanda, CDN
Tinha BFF. Por isso SSR era opção de verdade, não slide. Sem BFF, o browser monta o pagar com vários round-trips e um bundle que hidrata tudo. Com BFF, o servidor já conhece o passo. Dá pra mandar HTML que serve pra pessoa ver preço, meio e botão sem esperar o mundo hidratar.
Foi isso que eu fiz no first paint do crítico. O JS ainda hidrata o que o POST precisa. Não hidrata o dashboard no meio do caminho.
Vue 2 pra Vue 3 / Nuxt 3 entrou aqui. Não porque “a lib nova é mais rápida” no vazio. Porque o legado não entregava SSR e split de verdade no MFE com a mesma facilidade. E eu não virei o checkout inteiro num dia. As rotas que a campanha batia foram pro stack novo. O resto ficou no legado. Roteador de borda decide. Sem big bang.
SSR sozinho não basta. Se a hidratação ainda manda o MFE inteiro, você só adiou o timeout.
Aí entra JavaScript sob demanda. Code splitting é o bundler gerar mais de um arquivo. Lazy load é o browser só pedir o arquivo quando o passo precisa. Os dois andam juntos. Split sem lazy: você ainda baixa os chunks “por precaução”. Lazy sem split de verdade:
const PayStep = () => import('./PayStep.vue')
Se o PayStep.vue no topo importa upsell, chart e admin, o import() é teatro. O chunk do pagar ainda é o mundo.
O teste não é ter import() no PR. É abrir o Network no passo de pagar e não ver o JS do admin. Order bump, meio extra, modal que quase ninguém abre: depois do clique.
Tem um furo irmão: cada fatia do MFE traz o Vue de novo. Você splittou o app e duplicou o runtime.
Onde isso invertia tudo
SSR na tela interna. Bundle único no checkout. O Lighthouse do admin ficava lindo. A campanha caía. A regra que ficou: SSR quando o first paint é pagar. Lazy quando o código só existe depois do clique.
Depois do split, o arquivo ainda precisa chegar. Eu já tinha cortado o JS e o timeout continuava em região. O chunk do PayStep saía longe, ou frio, ou sem compressão.
CDN perto de quem compra. Cache longo no estático com hash no nome. Gzip no que sobrou. Brotli se o edge já tiver, sem religião. Se o lazy sai de origem fria, você não fez lazy load. Fez “espera a origem acordar”. O spinner só mudou de lugar. O POST continua sem sair.
4. Como eu medi: quatro lentes
Eu parei de tratar ferramenta como tese. Trato como pergunta.
O hábito que ficou: no pico, eu abria o Network no pagar antes do Lighthouse. Se tinha chunk de dashboard, o PR de bundle ainda não tinha acabado.
O Sentry pergunta: o timeout foi no pagar, ou foi pixel quebrado depois da venda? Sem a ação do usuário no evento, os dois viram o mesmo TypeError. Aí você trata ruído como incidente, ou trata pagar que não saiu como “JS genérico”. O contrato disso está no outro texto.
O Datadog pergunta: o caminho até o arquivo e até o POST tava saturado? Sentry conta o erro da jornada. Datadog conta se a origem ajoelhou. Timeout com origem ok é bundle. Origem ajoelhada com bundle ok é infra. No pico os dois acontecem. Uma lente só te faz otimizar a errada.
O Lighthouse pergunta: no lab, o que está bloqueando o first paint? Eu coletei. Serve pra hipótese. Não fecha receita. Não tem influenciador. Não tem a fila da CDN.
O backoffice pergunta: vendeu? Sem a métrica de venda, bundle menor é hipótese. Essa pergunta fecha o mês. A resposta está no próximo bloco.
Recibo (o que fecha, o que não linko)
PRs e dashboards reais ficam atrás de NDA. O que o leitor consegue checar é este recorte. O +10% é o número do painel. O resto é o hábito + o filtro. Sem print forjado.
| Lente | O que eu olhava | O que este post pode mostrar |
|---|---|---|
| Backoffice | conversão do mês, pico na campanha | +10% (número do cliente) |
| Sentry | timeout no passo de pagar, não TypeError órfão | query abaixo (sintético) |
| Datadog | TTFB / saturação / região no chunk do pagar | o que eu perguntava |
| Lighthouse | first paint no lab | limite: print não arquivado |
| Código | split, SSR, CDN | PRs internos. Não linko |
Network no /pagar. Sintético, de propósito. Sem kB inventado. O furo é a presença do arquivo, não um score:
antes (furo)
PayStep.js
vue.runtime.js
dashboard.chunk.js ← não deveria estar no pagar
admin-charts.js ← idem
depois (o teste)
PayStep.js
vue.runtime.js
Se o dashboard ainda aparece, o import() é teatro. Não importa o Lighthouse.
Sentry. Mesmo recorte do complementar. Cola no Discover:
extra.phase:checkout.pay.submit AND (
error.type:TimeoutError OR message:*timeout* OR mechanism:*timeout*
)
Pixel quebrado depois da venda não entra nesse filtro. É outro phase.
Datadog, a pergunta que eu fazia no pico: o TTFB do chunk do PayStep subiu na região da campanha, ou a origem tava ok e o bundle que era grande? Uma lente só te faz otimizar a errada.
Cupom inválido, cartão recusado, form incompleto: a regra funcionou. Breadcrumb. Não exception. Exception é gateway 500, timeout de rede, SDK que estourou no submit. Se misturar, o board grita e você trata recusa de cartão como queda de conversão.
5. Resultado: +10% no mês
O cliente leu o painel. +10% de conversão no mês. Pico nas campanhas de influenciador.
Esse número não veio de uma ferramenta só. Veio de três camadas juntas:
- Métrica de venda: backoffice do cliente. Quem vende lê isso.
- Jornada: Sentry com timeout/transbordo no pagar, não TypeError órfão.
- Caminho: Datadog com TTFB, saturação, região.
Lighthouse ficou de hipótese de lab. Não fecha o mês.
O +10% não veio de silenciar recusa de cartão. Veio de o JS do pagar chegar a tempo, e de eu ver o timeout no mesmo dia.
Eu toquei esse recorte sozinho: o caminho do pagar, o split, essas lentes. Depois pesou numa promoção. O que ficou copiável foi a ordem: painel primeiro, lab por último. Bundle menor sem esse fechamento era só PR bonito.
6. O que copiar no próximo PR
Antes de mergear “otimizar o bundle”:
1. Qual jornada perde dinheiro se o JS atrasar? (pagar, não o admin)
2. No Network do passo de pagar, aparece JS de dashboard?
3. SSR no first paint do pagar, ou SPA seca esperando hidratar?
4. O import() puxa o passo, ou o passo importa o mundo no topo?
5. O chunk lazy está no edge, com hash e gzip, ou sai frio?
6. Venda (backoffice) e obs (Sentry/Datadog) olham o mesmo número, ou só o Lighthouse?
7. Timeout/transbordo alertam sintoma de usuário, ou qualquer JS?
Ordem: backoffice → Sentry → Datadog → Lighthouse
Se o PR não responde 1 e 6, pode ser um PR bom. Não é este case.
O número que paga é a conversão. Vue 3, MFE e Lighthouse entram se ajudam esse número. Se não ajudam, são logo.
Última campanha que você pegou: olhou primeiro o timeout no Sentry, o TTFB no Datadog, ou o backoffice?
Top comments (0)