Files
claude-plugins/dev-tools/skills/nextjs/SKILL.md
Emanuel Almeida 9404af7ac9 feat: sync all plugins, skills, agents updates
New plugins: core-tools
New skills: auto-expense, ticket-triage, design, security-check,
  aiktop-tasks, daily-digest, imap-triage, index-update, mindmap,
  notebooklm, proc-creator, tasks-overview, validate-component,
  perfex-module, report, calendar-manager
New agents: design-critic, design-generator, design-lead,
  design-prompt-architect, design-researcher, compliance-auditor,
  metabase-analyst, gitea-integration-specialist
Updated: all plugin configs, knowledge datasets, existing skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:16:32 +00:00

742 lines
17 KiB
Markdown

---
name: nextjs
description: Next.js development best practices and patterns. App router, server components,
API routes, and deployment. Use when user mentions "nextjs", "next.js", "react server",
"app router", "next deployment".
author: Descomplicar® Crescimento Digital
version: 2.0.0
quality_score: 75
user_invocable: true
allowed-tools: Glob
---
# /nextjs - Next.js Development
Desenvolvimento Next.js moderno (13+) com App Router e Server Components.
## Regra #48 - Dev Container (OBRIGATORIO)
**TODOS os projectos Next.js devem ser desenvolvidos no container dev.**
```
SSH: server="dev" (mcp__ssh-unified__ssh_execute)
Path: /root/Dev/<projecto>
Sync: auto -> /media/ealmeida/Dados/Dev/<projecto> (Syncthing)
```
**Workflow:**
1. `mcp__ssh-unified__ssh_execute server:"dev" command:"mkdir -p /root/Dev/<projecto>"`
2. Desenvolver e testar no container
3. `npm run build` no container antes de deploy
4. Deploy via EasyPanel a partir do container
5. Syncthing propaga automaticamente para PC local e colegas
**NUNCA:** `npx create-next-app` directamente no PC local para projectos colaborativos.
## Quando Usar
- Criar aplicações Next.js
- Migrar de Pages para App Router
- Implementar Server Components
- Configurar Server Actions
- Optimizar SEO e performance
## App Router Structure
```
app/
├── layout.tsx # Root layout (obrigatório)
├── page.tsx # Home page
├── loading.tsx # Loading UI (Suspense)
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── globals.css # Global styles
├── (auth)/ # Route group (não afecta URL)
│ ├── login/page.tsx
│ └── register/page.tsx
├── dashboard/
│ ├── layout.tsx # Nested layout
│ ├── page.tsx
│ └── [id]/
│ └── page.tsx # Dynamic route
└── api/
└── route.ts # API route
```
## Server vs Client Components
### Server Component (default)
```tsx
// ✅ PADRÃO - Runs on server
async function ProductsPage() {
// Pode fazer fetch directo
const products = await db.product.findMany();
return (
<div>
{products.map(p => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}
export default ProductsPage;
```
**Vantagens:**
- Acesso directo a BD
- Menos JavaScript no cliente
- SEO melhor
- Dados sempre frescos
### Client Component
```tsx
'use client';
import { useState } from 'react';
// ✅ Para interactividade
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
);
}
```
**Quando usar:**
- useState, useEffect, useContext
- Event listeners (onClick, onChange)
- Browser APIs (localStorage, window)
- Custom hooks
## Data Fetching Patterns
### 1. Static (SSG)
```tsx
// Default behaviour - cache infinito
async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache' // Explícito (mas é default)
});
return <div>{data.title}</div>;
}
```
### 2. Dynamic (SSR)
```tsx
// Sempre fetch fresco
async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store' // NUNCA cache
});
return <div>{data.title}</div>;
}
```
### 3. Revalidate (ISR)
```tsx
// Cache com revalidação automática
async function Page() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 } // Revalida a cada 60s
});
return <div>{data.title}</div>;
}
```
## Server Actions
```tsx
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.post.create({
data: { title, content }
});
revalidatePath('/posts');
}
// app/create-post/page.tsx
import { createPost } from '@/app/actions';
export default function CreatePost() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create</button>
</form>
);
}
```
## Layouts e Loading States
### Root Layout
```tsx
// app/layout.tsx
import './globals.css';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'My App',
description: 'Description'
};
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="pt">
<body className={inter.className}>
<nav>...</nav>
{children}
<footer>...</footer>
</body>
</html>
);
}
```
### Loading UI
```tsx
// app/dashboard/loading.tsx
export default function Loading() {
return <div>Loading...</div>;
}
// Wrapper automático em <Suspense>
```
### Error Handling
```tsx
// app/error.tsx
'use client';
export default function Error({
error,
reset
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
```
## Routing Avançado
### Parallel Routes
```tsx
// app/layout.tsx
export default function Layout({
children,
modal,
sidebar
}: {
children: React.ReactNode;
modal: React.ReactNode;
sidebar: React.ReactNode;
}) {
return (
<>
{sidebar}
{children}
{modal}
</>
);
}
// app/@sidebar/page.tsx
// app/@modal/page.tsx
```
### Intercepting Routes
```tsx
// app/feed/page.tsx - Lista de fotos
// app/photo/[id]/page.tsx - Página completa da foto
// app/@modal/(.)photo/[id]/page.tsx - Modal da foto (intercepta)
// Navegar via <Link> abre modal
// Refresh ou URL directo abre página completa
```
## Middleware
```tsx
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('token');
// Auth guard
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Custom header
const response = NextResponse.next();
response.headers.set('x-custom-header', 'value');
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
};
```
## Performance Rules (Vercel Engineering)
57 regras priorizadas por impacto. Fonte: Vercel Engineering react-best-practices.
### 1. Eliminating Waterfalls (CRITICAL)
```tsx
// ❌ 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 sem await
const data = await dataPromise // só espera quando precisa
return Response.json(data)
}
```
**Regras:**
- `async-defer-await`: Mover await para onde é realmente 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)
```tsx
// ❌ 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+)
// next.config.js
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>
)
}
```
**Regras:**
- `bundle-barrel-imports`: Evitar barrel files (index.js re-exports)
- `bundle-conditional-loading`: Carregar módulos só quando feature activa
- `bundle-defer-third-party`: `next/dynamic` com `ssr: false` para analytics
- `bundle-dynamic-imports`: Lazy-load componentes pesados (>50KB)
- `bundle-preload-intent`: Preload em hover para UX instantânea
### 3. Server-Side Performance (HIGH)
```tsx
// ✅ Server Actions: autenticar SEMPRE (são endpoints públicos!)
'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 input
await db.user.update({ where: { id: session.userId }, data }) // 3. Executar
revalidatePath('/profile')
}
// ✅ Minimizar serialização RSC - só campos necessários
async function UserCard({ userId }: { userId: string }) {
const user = await db.user.findUnique({ where: { id: userId } })
// Passa só o necessário, não o objecto inteiro com 50+ campos
return <ClientCard name={user.name} avatar={user.avatar} />
}
// ✅ 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
}
```
**Regras:**
- `server-auth-actions`: Autenticar Server Actions como API routes
- `server-avoid-duplicate-serialization`: Transformar dados no cliente
- `server-lru-cache`: LRUCache para dados cross-request
- `server-minimize-serialization`: Só campos necessários em RSC props
- `server-parallel-composition`: Composição paralela de Server Components
- `server-react-cache`: `React.cache()` para deduplicação per-request
- `server-after`: `after()` para logging/analytics não-bloqueante
### 4. Client-Side Data Fetching (MEDIUM-HIGH)
```tsx
// ✅ 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)
```tsx
// ✅ 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[] = [] // ✅ fora do componente
const NOOP = () => {} // ✅ fora do componente
function Component({ items = EMPTY_ARRAY, onClick = NOOP }) { ... }
// ✅ Dependências de effect: primitivos, não objectos
useEffect(() => { fetchUser(userId) }, [userId]) // ✅ primitivo
// useEffect(() => { ... }, [user]) // ❌ objecto = nova ref cada render
```
### 6. Rendering Performance (MEDIUM)
```tsx
// ✅ CSS content-visibility para listas longas
// .message { content-visibility: auto; contain-intrinsic-size: 0 80px; }
// ✅ 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()
// const [isLoading, setIsLoading] = useState(false) // ❌ manual
// ✅ Hydration: inline script para evitar flicker
// <script dangerouslySetInnerHTML={{ __html: `
// document.documentElement.classList.add(
// localStorage.getItem('theme') === 'dark' ? 'dark' : 'light'
// )
// `}} />
```
### 7. JavaScript Performance (LOW-MEDIUM)
```tsx
// ✅ 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) vs .includes() O(n)
// ✅ toSorted() para imutabilidade (React-safe)
const sorted = items.toSorted((a, b) => a.name.localeCompare(b.name))
// items.sort() // ❌ muta o array original
// ✅ Early return para evitar processamento desnecessário
function validate(items: Item[]) {
for (const item of items) {
if (!item.valid) return { error: item.id } // sai cedo
}
return { success: true }
}
// ✅ 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: [] })
// Em vez de items.filter(active) + items.filter(expired) = 2 iterações
```
### 8. Advanced Patterns (LOW)
```tsx
// ✅ 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 | Regras | Ganho |
|---|---|---|---|
| CRITICAL | Waterfalls | 5 | 2-10x latência |
| CRITICAL | Bundle Size | 5 | 200-800ms load |
| HIGH | Server-Side | 7 | RSC payload, TTI |
| MEDIUM-HIGH | Client Fetch | 4 | Request dedup |
| MEDIUM | Re-renders | 12 | UI responsiveness |
| MEDIUM | Rendering | 9 | Paint performance |
| LOW-MEDIUM | JS Perf | 12 | CPU cycles |
| LOW | Advanced | 3 | Init correctness |
---
## Optimizações
### Images
```tsx
import Image from 'next/image';
<Image
src="/photo.jpg"
width={500}
height={300}
alt="Photo"
priority // LCP image
placeholder="blur"
blurDataURL="data:image/..."
/>
```
### Fonts
```tsx
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
weight: ['400', '700'],
});
```
### Metadata (SEO)
```tsx
// app/page.tsx
export const metadata = {
title: 'Home',
description: 'Home page',
openGraph: {
title: 'Home',
description: 'Home page',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
},
};
```
## Deployment
### Vercel (recomendado)
```bash
npm install -g vercel
vercel
```
### Docker
```dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
RUN npm ci --production
EXPOSE 3000
CMD ["npm", "start"]
```
### Static Export
```js
// next.config.js
module.exports = {
output: 'export',
trailingSlash: true,
};
```
```bash
npm run build
# Output: out/
```
## Datasets Dify
| Dataset | ID | Prioridade |
|---------|----|-----------:|
| **Desenvolvimento de Software** | `e7c7decc-0ded-4351-ab14-b110b3c38ec9` | 1 |
| **TI (Tecnologia da Informação)** | `7f63ec0c-6321-488c-b107-980140199850` | 1 |
---
**Versão**: 1.0.0 | **Autor**: Descomplicar®
---
## Quando NÃO Usar
- Para tarefas fora do domínio de especialização desta skill
- Quando outra skill mais específica está disponível
- Para operações que requerem confirmação manual do utilizador
## Protocolo
1. Analisar requisitos da tarefa
2. Verificar disponibilidade de ferramentas necessárias
3. Executar operações de forma incremental
4. Validar resultados antes de concluir
5. Reportar status e próximos passos
## Exemplos
### Exemplo 1: Uso Básico
```
Input: [descrição da tarefa]
Output: [resultado esperado]
```
### Exemplo 2: Uso Avançado
```
Input: [caso complexo]
Output: [resultado detalhado]
```