sass-imobiliaria/specs/032-performance-homepage/plan.md
MatheusAlves96 cf5603243c
Some checks failed
CI/CD → Deploy via SSH / Build & Push Docker Images (push) Successful in 1m0s
CI/CD → Deploy via SSH / Deploy via SSH (push) Successful in 4m35s
CI/CD → Deploy via SSH / Validate HTTPS & Endpoints (push) Failing after 46s
feat: features 025-032 - favoritos, contatos, trabalhe-conosco, area-cliente, navbar, hero-light-dark, performance-homepage
- 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)
2026-04-22 22:35:17 -03:00

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.all no único useEffect
  • Injeta <link rel="preload"> via efeito secundário quando backgroundImage resolve
  • Cache de config em sessionStorage com TTL 5 min
  • Passa properties, loadingProperties, agents, loadingAgents como props para baixo

2. HomeScrollScene.tsx — Apresentação

  • Remove useEffect de getFeaturedProperties e estado interno de properties
  • Recebe properties: Property[] e loadingProperties: boolean via props
  • Mantém isLight (necessário para estilos internos)
  • Remove <style> inline com @keyframes (migra para index.css)
  • Adiciona fetchPriority="high" na hero <img>
  • RiseCard passa a usar hook useInView

3. AgentsCarousel.tsx — Apresentação

  • Remove useEffect de getAgents e estado interno de agents/loading
  • Recebe agents: Agent[] e loading: boolean via props
  • Adiciona will-change: transform no track CSS

4. PropertyRowCard.tsx — Folha

  • SlideImage: adiciona loading="lazy" e decoding="async"

5. hooks/useInView.ts — Utilitário

  • Encapsula IntersectionObserver com disconnect no isIntersecting
  • Aceita options?: IntersectionObserverInit
  • Retorna { ref, inView }

6. index.css

  • Adiciona bloco @keyframes fadeDown que estava inline em HomeScrollScene

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)