ae39e45460
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)
104 lines
3.6 KiB
Markdown
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.
|