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)
3.6 KiB
3.6 KiB
Next.js — Best Practices
Extraído de skills:
next-best-practicesv0.1.0 +nextjs-patternsv1.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
// ✅ 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
// ❌ 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
// 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 |
// 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.