---
title: "Reduzi o tempo de carregamento da home de ~60s para 2s em usuários ativos em 3G."
description: "Os usuários abriam o app na academia em 3G e esperavam quase um minuto para a home terminar de carregar. A requisição mais lenta arrastava a tela inteira junto. Indexei o join que não tinha índice, movi as seções compartilhadas para trás de um cache, reorganizei o lazy loading, e parei de refazer a home a cada volta. Cheguei a 2 segundos em conexões confiáveis."
author: "André Ramos"
url: "https://andreramos.dev/pt/work/bit-health/"
lang: "pt-BR"
type: "article"
company: "Bit Health"
role: "Staff Engineer"
period: "2019 — 2024 (dev solo → consultor → Staff Engineer)"
stack:
  - "Angular"
  - "NgRx"
  - "Laravel"
  - "MySQL"
---

# Reduzi o tempo de carregamento da home de ~60s para 2s em usuários ativos em 3G.

**Cargo:** Staff Engineer  
**Período:** 2019 — 2024 (dev solo → consultor → Staff Engineer)  
**Time:** 7  
**Stack:** Angular · NgRx · Laravel · MySQL

## Resumo

> Os usuários abriam o app na academia em 3G e esperavam quase um minuto para a home terminar de carregar. A requisição mais lenta arrastava a tela inteira junto. Indexei o join que não tinha índice, movi as seções compartilhadas para trás de um cache, reorganizei o lazy loading, e parei de refazer a home a cada volta. Cheguei a 2 segundos em conexões confiáveis.

## Contexto

Bit Health é um app de treino. A home é a tela em que o usuário cai depois do login, e é onde tudo acontece: continue assistindo, recomendações, conteúdos adicionados recentemente, categorias. A maioria dos usuários abre o app na academia ou num parque público. Wifi não é garantia. Toda decisão a partir daí tem que assumir 3G.

## O desafio

A home disparava várias requisições em paralelo a cada abertura. Para usuário novo, dava conta. Mas para usuário ativo, alguém com 'continue de onde parou' populado, a requisição mais lenta arrastava a tela inteira junto. Estávamos vendo perto de um minuto até a home carregar de verdade. Os usuários abandonavam antes de a gente ter algo pra mostrar.

## O que fizemos

1. **Indexamos o join que estava nos puxando pra baixo** — A consulta de 'continue assistindo' fazia join na tabela de progresso por user_id e content_id. Nenhuma das duas colunas tinha índice. Toda abertura batia no banco com uma query que não tinha como responder rápido. Adicionei o índice que faltava e enxuguei o SELECT para buscar só as colunas que a home realmente renderiza: thumbnail, título, percentual de progresso. Essa única mudança domou a requisição mais lenta.

2. **Separamos conteúdo compartilhado de conteúdo personalizado** — Parte da home é igual para todo usuário num dado momento: recomendações, lançamentos, categorias. Nada disso precisava ser recalculado por requisição. Movi essas seções para trás de um cache com refresh agendado, e deixei só as chamadas personalizadas batendo no banco por usuário.

3. **Reorganizamos o lazy loading no cliente** — O Angular estava bootando módulos que a home não precisava. Reestruturei a árvore de rotas para a home carregar só o que a home renderiza. Configurações, edição de perfil e compras viraram chunks lazy, carregados só quando o usuário ia até lá.

4. **Paramos de refazer a busca toda vez que voltava** — O NgRx estava ingênuo. Toda vez que o usuário voltava para a home, refazíamos tudo do zero. Fresco, sim. Mas lento. Mudei a home para ler do store por padrão. Quando o usuário terminava de assistir um vídeo, eu disparava o refresh em background durante o redirect, então quando ele chegava na home o store já estava atualizado. Para ações que mexiam só em estado local, tipo favoritar um vídeo ou atualizar progresso, eu patcheava o store direto e pulava o round-trip de rede inteiro.

## Resultados

- **15s → 2s** — Tempo de carregamento da home (em conexões confiáveis (redução de 87%))
- **~60s → 2s** — Usuários ativos em 3G (resolveu a queixa de quase-um-minuto pra carregar)
- **~3×** — Conversão de demo em contrato (reportada pelo co-fundador após demos em eventos da indústria (1 em 10 → 3 em 10))

## Em retrospecto

- Mantivemos a arquitetura multi-endpoint porque cada endpoint moldava sua própria resposta: enriquecimento de URL de thumbnail, formatação de categoria. Um endpoint agregador, com moldagem feita num lugar só, teria sido mais rápido e mais simples de raciocinar. Eu pressionaria por essa abordagem mais cedo da próxima vez.

- Todo card da home fazia join na tabela de mídia pra buscar sua URL de thumbnail. Era um N+1 que a gente nunca resolveu. Eloquent tem eager loading exatamente pra esse caso, e se eu tivesse auditado as chamadas do ORM e arrumado o carregamento das relações, acredito que teríamos colocado a home abaixo de 500ms. Não investi o tempo na hora.
