Files
claude-plugins/dev-tools/skills/nextjs/references/performance-rules.md
Emanuel Almeida 6b3a6f2698 feat: refactor 30+ skills to Anthropic progressive disclosure pattern
- 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>
2026-03-12 15:05:03 +00:00

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á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)

// ❌ 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