- feat(025): favoritos locais com FavoritesContext, HeartButton, PublicFavoritesPage
- feat(026): central de contatos admin (leads/contatos unificados)
- feat(027): configuração da página de contato via admin
- feat(028): trabalhe conosco - candidaturas com upload e admin
- feat(029): UX área do cliente - visitas, comparação, perfil
- feat(030): navbar UX - menu mobile, ThemeToggle, useFavorites
- feat(031): hero light/dark - imagens separadas por tema, upload, preview, seed
- feat(032): performance homepage - Promise.all parallel fetches, sessionStorage cache,
preload hero image, loading=lazy nos cards, useInView hook, will-change carrossel,
keyframes em index.css, AgentsCarousel e HomeScrollScene via props
- fix: light mode HomeScrollScene - gradiente, cores de texto, scroll hint
migrations: g1h2i3j4k5l6 (source em leads), h1i2j3k4l5m6 (contact_config),
i1j2k3l4m5n6 (job_applications), j2k3l4m5n6o7 (hero theme images)
4.6 KiB
4.6 KiB
Plan — 032: Performance Homepage
Visão Técnica
A estratégia central é inverter o fluxo de dados: em vez de cada componente buscar seus próprios dados (model-per-component), o HomePage orquestra todos os fetches em paralelo e distribui via props. Isso elimina o waterfall e permite paralelizar corretamente.
ANTES (serial):
HomePage mount → fetch config → render HomeScrollScene → fetch properties
→ fetch agents (AgentsCarousel)
DEPOIS (paralelo):
HomePage mount → Promise.all([config, properties, agents]) → render com dados prontos
Arquitetura das Mudanças
1. HomePage.tsx — Orchestrator
- Gerencia 3 estados:
config,featuredProperties,agents Promise.allno únicouseEffect- Injeta
<link rel="preload">via efeito secundário quandobackgroundImageresolve - Cache de
configemsessionStoragecom TTL 5 min - Passa
properties,loadingProperties,agents,loadingAgentscomo props para baixo
2. HomeScrollScene.tsx — Apresentação
- Remove
useEffectdegetFeaturedPropertiese estado interno deproperties - Recebe
properties: Property[]eloadingProperties: booleanvia props - Mantém
isLight(necessário para estilos internos) - Remove
<style>inline com@keyframes(migra paraindex.css) - Adiciona
fetchPriority="high"na hero<img> RiseCardpassa a usar hookuseInView
3. AgentsCarousel.tsx — Apresentação
- Remove
useEffectdegetAgentse estado interno deagents/loading - Recebe
agents: Agent[]eloading: booleanvia props - Adiciona
will-change: transformno track CSS
4. PropertyRowCard.tsx — Folha
SlideImage: adicionaloading="lazy"edecoding="async"
5. hooks/useInView.ts — Utilitário
- Encapsula
IntersectionObservercomdisconnectnoisIntersecting - Aceita
options?: IntersectionObserverInit - Retorna
{ ref, inView }
6. index.css
- Adiciona bloco
@keyframes fadeDownque estava inline emHomeScrollScene
Interface das Props Alteradas
// HomeScrollScene — novas props
interface HomeScrollSceneProps {
headline: string
subheadline: string | null
ctaLabel: string
ctaUrl: string
backgroundImage?: string | null
isLoading?: boolean
properties: Property[] // NOVO — antes buscado internamente
loadingProperties: boolean // NOVO
}
// AgentsCarousel — novas props
interface AgentsCarouselProps {
agents: Agent[] // NOVO — antes buscado internamente
loading: boolean // NOVO
}
Estratégia de Cache — sessionStorage
const CACHE_KEY = 'homepage_config'
const CACHE_TTL = 5 * 60 * 1000 // 5 min
function getCachedConfig(): HomepageConfig | null {
try {
const raw = sessionStorage.getItem(CACHE_KEY)
if (!raw) return null
const { data, ts } = JSON.parse(raw)
if (Date.now() - ts > CACHE_TTL) return null
return data
} catch { return null }
}
function setCachedConfig(data: HomepageConfig) {
try {
sessionStorage.setItem(CACHE_KEY, JSON.stringify({ data, ts: Date.now() }))
} catch {}
}
Tratamento de CLS na Hero
O HomeScrollScene receberá isLoading do pai. Enquanto isLoading=true, o container sticky terá min-height: 100svh preservado via className, garantindo que o browser reserve o espaço mesmo antes da imagem carregar.
O skeleton inline já existente está correto — o problema atual é que o backgroundImage resolve mais tarde que o skeleton desaparece, causando re-layout. Com o preload dinâmico, a imagem chega antes.
Decisões de Design
| Decisão | Alternativa rejeitada | Motivo |
|---|---|---|
sessionStorage para cache |
localStorage |
sessionStorage expira ao fechar a aba — adequado para conteúdo editorial |
Preload via document.createElement |
react-helmet |
Evita dependência extra para um caso simples |
| Props drilling em vez de Context | Context global de dados da home | Overkill para componentes de folha; props é suficiente e mais rastreável |
useInView hook simples |
Biblioteca externa (react-intersection-observer) | Sem dependência extra, controle total |
Sequência de Implementação
1. hooks/useInView.ts (sem dependências)
2. index.css (keyframes) (sem dependências)
3. PropertyRowCard.tsx (loading=lazy) (sem dependências)
4. AgentsCarousel.tsx (props) (depende de tipos Agent)
5. HomeScrollScene.tsx (props + refat) (depende de useInView)
6. HomePage.tsx (orchestrator) (depende de todos acima)