Files
pulse-libs/libs/react/NEXTJS_BEST_PRACTICES.md
T
Pulse ae39e45460 feat: biblioteca inteligente libs/ + 5 novas skills (20 skills total)
NOVAS SKILLS:
- next-best-practices      v0.1.0  (CLEAN) — Next.js App Router, RSC, caching, data
- nextjs-patterns          v1.0.0  (CLEAN) — Next.js 15: Server Actions, route handlers
- vite                     v1.0.0  (CLEAN) — env vars, aliases, proxy, CJS compat
- uncle-bob                v1.0.0  (CLEAN) — Clean Code, SOLID, Clean Architecture
- clean-code-review        v1.0.0  (CLEAN) — naming, guard clauses, anti-patterns, refactoring
- vue                      v1.0.0  (CLEAN) — Vue framework
- vue-composition-api-best-practices v1.0.0 (CLEAN) — composables, Pinia, reactivity

BIBLIOTECA INTELIGENTE libs/ (10 dominios, 11 arquivos):
- typescript/ — TS safe + generics gotchas
- react/ — Next.js App Router + Vite config
- vue/ — Composition API + Pinia
- linux/ — System diagnostic cheatsheet
- database/ — PostgreSQL + MySQL patterns
- browser/ — Chromium CLI + E2E testing
- security/ — SAST audit (OWASP Top 10)
- best-practices/ — Clean Code + SOLID + Clean Architecture
- deploy/ — Docker multi-stack + OpenClaw ops
- + INDEX.md como guia de navegacao

.learnings/ — LRN-20260519-003 criado (biblioteca compartilhada)
2026-05-19 21:03:25 -03:00

104 lines
3.6 KiB
Markdown

# Next.js — Best Practices
> Extraído de skills: `next-best-practices` v0.1.0 + `nextjs-patterns` v1.0.0
## 🏗️ Estrutura de Projeto (App Router)
```
app/
├── (marketing)/ # Route group — não aparece na URL
│ └── page.tsx
├── (dashboard)/
│ ├── layout.tsx # Layout compartilhado do grupo
│ ├── [id]/
│ │ └── page.tsx # Página dinâmica
│ └── loading.tsx # Suspense boundary automático
├── api/
│ └── route.ts # Route Handler (REST/GraphQL)
├── error.tsx # Error boundary por rota
├── not-found.tsx # Página 404
└── layout.tsx # Root layout (obrigatório)
```
## 🖥️ Server vs Client Components
```tsx
// ✅ Server Component (padrão) — roda no servidor, nenhum JS no cliente
async function ProductList() {
// DB query acontece no servidor!
const products = await db.product.findMany();
return <ul>{products.map(p => <li key={p.id}>{p.name}</li>)}</ul>;
}
// ✅ Client Component — só quando precisar de interatividade
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(n => n + 1)}>{count}</button>;
}
// ✅ Server Actions — mutations sem criar API routes
async function createProduct(formData: FormData) {
"use server";
await db.product.create({ data: { ... } });
}
```
## 📊 Padrões de Data Fetching
| Padrão | Quando usar | Exemplo |
|--------|-------------|---------|
| **Server Component `await`** | Read, página ou componente | `const posts = await db.post.findMany()` |
| **Server Actions** | Write/mutations, formulários | `"use server"` + `revalidatePath` |
| **Route Handlers** | REST/GraphQL API, webhooks, integrações | `app/api/users/route.ts` |
| **`use()` hook** | Ler promise em Client Components | `const data = use(fetchData())` |
### Evitando Data Waterfalls
```tsx
// ❌ Ruim — sequencial, cada await aguarda o anterior
async function Page() {
const user = await getUser(); // 200ms
const posts = await getPosts(); // 300ms (só começa após user)
return <Profile user={user} posts={posts} />;
}
// ✅ Bom — paralelo
async function Page() {
const [user, posts] = await Promise.all([
getUser(), // começa imediatamente
getPosts(), // começa imediatamente
]);
return <Profile user={user} posts={posts} />;
}
```
## 🗄️ Database Client Singleton
```ts
// lib/db.ts — criar uma vez por request (Server Component boundary)
import { PrismaClient } from '@prisma/client';
const globalForDb = globalThis as unknown as { prisma: PrismaClient };
export const db = globalForDb.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') globalForDb.prisma = db;
```
## 🔄 Caching — 4 Camadas Next.js
| Camada | O que cacheia | Controlado por |
|--------|---------------|----------------|
| **Request Memoization** | `fetch`, `React.cache()` | automático por request |
| **Data Cache** | `fetch` responses | `next: { revalidate: 60 }` |
| **Full Route Cache** | Render completo | `export const revalidate` |
| **Router Cache** | Estado do cliente após navegação | `next/router` |
```ts
// Revalidar após 60s
fetch(url, { next: { revalidate: 60 } });
// Não cachear (sempre fresh)
fetch(url, { next: { revalidate: 0 } });
```
## ⚠️ Regra Fundamental
> **`"use client"` é uma barreira de escape.** Tudo dentro de um arquivo com essa diretiva
> roda no navegador. Tudo SEM ela roda no servidor. Quando possível, fique no servidor.