# /nextjs - Performance Rules (Vercel Engineering) 57 regras priorizadas por impacto. Fonte: Vercel Engineering react-best-practices. ## 1. Eliminating Waterfalls (CRITICAL) ```tsx // ❌ ERRADO: Awaits sequenciais (waterfall) const session = await getSession() const config = await getConfig() const user = await getUser(session.userId) // ✅ CORRECTO: Paralelo com Promise.all() const [session, config] = await Promise.all([ getSession(), getConfig() ]) const user = await getUser(session.userId) // depende de session // ✅ CORRECTO: Suspense boundaries estratégicos async function Page() { return ( }>
}> ) } // ✅ CORRECTO: Defer await até necessário async function handler(req) { const dataPromise = fetchData() // inicia imediatamente if (req.method === 'HEAD') return new Response() // early return const data = await dataPromise // só espera quando precisa return Response.json(data) } ``` **Regras:** - `async-defer-await`: Mover await para onde é necessário - `async-dependency-parallel`: Usar `better-all` para dependências parciais - `async-prevent-waterfall`: Iniciar promises antes de awaitar - `async-promise-all`: `Promise.all()` para operações independentes - `async-suspense-boundaries`: Suspense para UI progressiva --- ## 2. Bundle Size (CRITICAL) ```tsx // ❌ ERRADO: Barrel file imports import { Button, Icon } from '@mui/material' // carrega tudo // ✅ CORRECTO: Import directo import Button from '@mui/material/Button' // ✅ CORRECTO: optimizePackageImports (Next.js 13.5+) module.exports = { experimental: { optimizePackageImports: ['lucide-react', '@mui/material', 'lodash'] } } // ✅ CORRECTO: Dynamic imports para componentes pesados const MonacoEditor = dynamic(() => import('./MonacoEditor'), { ssr: false, loading: () => }) // ✅ CORRECTO: Preload em hover/focus function FeatureButton() { return ( ) } ``` --- ## 3. Server-Side Performance (HIGH) ```tsx // ✅ Server Actions: autenticar SEMPRE 'use server' async function updateProfile(formData: FormData) { const session = await auth() // 1. Autenticar if (!session) throw new Error('Unauthorized') const data = schema.parse(formData) // 2. Validar await db.user.update({ where: { id: session.userId }, data }) revalidatePath('/profile') } // ✅ React.cache() para deduplicação per-request const getUser = cache(async (id: string) => { return db.user.findUnique({ where: { id } }) }) // ✅ after() para operações não-bloqueantes import { after } from 'next/server' async function submitForm(data: FormData) { const result = await processForm(data) after(async () => { await logAnalytics(result) // executa APÓS resposta enviada }) return result } ``` --- ## 4. Client-Side Data Fetching (MEDIUM-HIGH) ```tsx // ✅ SWR para deduplicação automática const { data, isLoading } = useSWR('/api/user', fetcher) // ✅ Passive event listeners para scroll performance element.addEventListener('scroll', handler, { passive: true }) // ✅ localStorage versionado com try-catch const key = 'userConfig:v2' try { const data = JSON.parse(localStorage.getItem(key) ?? '{}') } catch { /* incognito mode, quota exceeded */ } ``` --- ## 5. Re-render Optimization (MEDIUM) ```tsx // ✅ Derived state: calcular durante render, NÃO em useEffect function FilteredList({ items, query }: Props) { const filtered = items.filter(i => i.name.includes(query)) // derive! return } // ✅ Functional setState para evitar stale closures setItems(curr => [...curr, ...newItems]) // ✅ // setItems([...items, ...newItems]) // ❌ stale closure // ✅ Lazy state initialization const [index] = useState(() => buildSearchIndex(items)) // computa 1x // ✅ useTransition para updates não-urgentes const [isPending, startTransition] = useTransition() startTransition(() => setSearchResults(results)) // ✅ Extrair defaults não-primitivos para module scope const EMPTY_ARRAY: string[] = [] const NOOP = () => {} function Component({ items = EMPTY_ARRAY, onClick = NOOP }) { ... } // ✅ Dependências de effect: primitivos, não objectos useEffect(() => { fetchUser(userId) }, [userId]) // ✅ primitivo ``` --- ## 6. Rendering Performance (MEDIUM) ```tsx // ✅ Hoist static JSX fora do componente const skeleton =
// module scope // ✅ Conditional rendering explícito (evitar && com falsy) {count > 0 ? {count} : null} // ✅ // {count && {count}} // ❌ renderiza "0" // ✅ useTransition em vez de useState manual para loading const [isPending, startTransition] = useTransition() ``` --- ## 7. JavaScript Performance (LOW-MEDIUM) ```tsx // ✅ Index Maps para lookups repetidos O(1) vs O(n) const userMap = new Map(users.map(u => [u.id, u])) orders.map(o => ({ ...o, user: userMap.get(o.userId) })) // ✅ Set para membership checks const allowedIds = new Set(['a', 'b', 'c']) if (allowedIds.has(id)) { ... } // O(1) // ✅ toSorted() para imutabilidade (React-safe) const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name)) // items.sort() // ❌ muta o array original // ✅ Combinar iterações de array const { active, expired } = items.reduce((acc, item) => { if (item.active) acc.active.push(item) else acc.expired.push(item) return acc }, { active: [], expired: [] }) ``` --- ## 8. Advanced Patterns (LOW) ```tsx // ✅ Init uma vez, não por mount (Strict Mode remonta) let didInit = false function App() { useEffect(() => { if (didInit) return didInit = true initializeAnalytics() }, []) } // ✅ useEffectEvent para callbacks estáveis em effects const onTick = useEffectEvent((value) => { console.log(value) // sempre valor mais recente }) useEffect(() => { const id = setInterval(() => onTick(count), 1000) return () => clearInterval(id) }, []) // sem dependência de count! ``` --- ## Resumo de Impacto | Prioridade | Categoria | Ganho | |---|---|---| | CRITICAL | Waterfalls | 2-10x latência | | CRITICAL | Bundle Size | 200-800ms load | | HIGH | Server-Side | RSC payload, TTI | | MEDIUM-HIGH | Client Fetch | Request dedup | | MEDIUM | Re-renders | UI responsiveness | | MEDIUM | Rendering | Paint performance | | LOW-MEDIUM | JS Perf | CPU cycles |