- All SKILL.md files now <500 lines (avg reduction 69%) - Detailed content extracted to references/ subdirectories - Frontmatter standardised: only name + description (Anthropic standard) - New skills: brand-guidelines, spec-coauthor, report-templates, skill-creator - Design skills: anti-slop guidelines, premium-proposals reference - Removed non-standard frontmatter fields (triggers, version, author, category) Plugins affected: infraestrutura, marketing, dev-tools, crm-ops, gestao, core-tools, negocio, perfex-dev, wordpress, design-media Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.5 KiB
6.5 KiB
/nextjs - Performance Rules (Vercel Engineering)
57 regras priorizadas por impacto. Fonte: Vercel Engineering react-best-practices.
1. Eliminating Waterfalls (CRITICAL)
// ❌ 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 (
<Layout>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<SlowContent />
</Suspense>
</Layout>
)
}
// ✅ 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árioasync-dependency-parallel: Usarbetter-allpara dependências parciaisasync-prevent-waterfall: Iniciar promises antes de awaitarasync-promise-all:Promise.all()para operações independentesasync-suspense-boundaries: Suspense para UI progressiva
2. Bundle Size (CRITICAL)
// ❌ 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: () => <EditorSkeleton />
})
// ✅ CORRECTO: Preload em hover/focus
function FeatureButton() {
return (
<button
onMouseEnter={() => import('./HeavyFeature')}
onClick={openFeature}
>
Open Feature
</button>
)
}
3. Server-Side Performance (HIGH)
// ✅ 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)
// ✅ 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)
// ✅ 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 <List items={filtered} />
}
// ✅ 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)
// ✅ Hoist static JSX fora do componente
const skeleton = <div className="skeleton h-4 w-full" /> // module scope
// ✅ Conditional rendering explícito (evitar && com falsy)
{count > 0 ? <Badge>{count}</Badge> : null} // ✅
// {count && <Badge>{count}</Badge>} // ❌ renderiza "0"
// ✅ useTransition em vez de useState manual para loading
const [isPending, startTransition] = useTransition()
7. JavaScript Performance (LOW-MEDIUM)
// ✅ 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)
// ✅ 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 |