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)
This commit is contained in:
parent
6ef5a7a17e
commit
cf5603243c
106 changed files with 11927 additions and 1367 deletions
136
specs/032-performance-homepage/plan.md
Normal file
136
specs/032-performance-homepage/plan.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# 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
|
||||
|
||||
```ts
|
||||
// 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`
|
||||
|
||||
```ts
|
||||
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)
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue