create-sdd-ai-stack 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +164 -0
- package/APP-STACK.md +31 -0
- package/APP.md +46 -0
- package/ARCHITECTURE.md +115 -0
- package/DESIGN.md +219 -0
- package/LICENSE +21 -0
- package/NEXT.md +345 -0
- package/NODE.md +107 -0
- package/REACT.md +92 -0
- package/README.md +260 -0
- package/SKILLS/check-docs/SKILL.md +24 -0
- package/SKILLS/check-docs/check-docs.mjs +56 -0
- package/SKILLS/create-feature/SKILL.md +40 -0
- package/SKILLS/create-feature/create-feature.mjs +128 -0
- package/SKILLS/create-feature.sh +112 -0
- package/SKILLS/install-submodule/SKILL.md +42 -0
- package/SKILLS/install-submodule/install-submodule.mjs +119 -0
- package/bin/create-sdd-ai-stack.mjs +93 -0
- package/docs/CHANGELOG.md +209 -0
- package/docs/PLANNING.md +68 -0
- package/docs/PRODUCT.md +76 -0
- package/docs/RELEASE.md +332 -0
- package/lib/check-links.mjs +42 -0
- package/lib/constants.mjs +60 -0
- package/lib/scaffold.mjs +253 -0
- package/package.json +80 -0
- package/specs/BACKLOG.md +24 -0
- package/specs/PLAN.md +57 -0
- package/specs/ROADMAP.md +35 -0
- package/specs/history/phases/phase-0-bootstrap.md +67 -0
- package/specs/tasks/TASK_TEMPLATE.md +46 -0
- package/src/cli.mjs +119 -0
- package/stacks/README.md +33 -0
- package/stacks/ai.md +52 -0
- package/stacks/ci.md +59 -0
- package/stacks/database.md +56 -0
- package/stacks/git.md +51 -0
- package/stacks/shadcn.md +52 -0
- package/stacks/tailwind.md +65 -0
- package/stacks/testing.md +74 -0
- package/stacks/typescript.md +58 -0
- package/template/next/.env.example +4 -0
- package/template/next/README.md +47 -0
- package/template/next/biome.json +36 -0
- package/template/next/gitignore +32 -0
- package/template/next/next.config.ts +12 -0
- package/template/next/package.json +47 -0
- package/template/next/playwright.config.ts +21 -0
- package/template/next/postcss.config.mjs +7 -0
- package/template/next/src/app/(app)/app/page.tsx +18 -0
- package/template/next/src/app/(app)/error.tsx +30 -0
- package/template/next/src/app/(app)/layout.tsx +25 -0
- package/template/next/src/app/(marketing)/page.tsx +73 -0
- package/template/next/src/app/globals.css +437 -0
- package/template/next/src/app/layout.tsx +41 -0
- package/template/next/src/app/not-found.tsx +11 -0
- package/template/next/src/features/example/actions.ts +43 -0
- package/template/next/src/features/example/application/create-example.usecase.ts +26 -0
- package/template/next/src/features/example/container.ts +16 -0
- package/template/next/src/features/example/domain/IExampleRepository.ts +11 -0
- package/template/next/src/features/example/domain/example.schema.ts +18 -0
- package/template/next/src/features/example/infrastructure/example.repository.ts +26 -0
- package/template/next/src/features/example/ui/create-example-form.tsx +56 -0
- package/template/next/src/proxy.ts +23 -0
- package/template/next/src/shared/lib/cn.ts +6 -0
- package/template/next/src/shared/server/auth.ts +32 -0
- package/template/next/src/shared/server/env.ts +14 -0
- package/template/next/src/shared/ui/index.ts +3 -0
- package/template/next/tests/e2e/routes.spec.ts +34 -0
- package/template/next/tests/unit/create-example.test.ts +55 -0
- package/template/next/tsconfig.json +37 -0
- package/template/next/vitest.config.ts +19 -0
package/NEXT.md
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# ⚛️ NEXT.JS — REGRAS OFICIAIS (STACK PADRÃO)
|
|
2
|
+
|
|
3
|
+
> **STACK PADRÃO DESTE TEMPLATE.** Toda aplicação nova sai em Next.js.
|
|
4
|
+
> Node.js puro (workers, CLIs, cron, webhooks) é **complemento**, não o padrão — ver [NODE.md](./NODE.md).
|
|
5
|
+
> Alvo de versão: **Next.js 16** (App Router, React 19.2, Turbopack).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🧭 1. ROTEADOR (leia só o que precisa)
|
|
10
|
+
|
|
11
|
+
| Você está fazendo... | Leia |
|
|
12
|
+
| ------------------------------------------------- | ------------------------ |
|
|
13
|
+
| Criando página/rota | §2 Estrutura, §3 Server |
|
|
14
|
+
| Buscando dados | §4 Data |
|
|
15
|
+
| Escrevendo mutação (create/update/delete) | §5 Server Actions |
|
|
16
|
+
| Mexendo em cache | §6 Cache Components |
|
|
17
|
+
| Autenticação / rota protegida | §7 Segurança |
|
|
18
|
+
| Criando API para terceiro | §8 Route Handlers |
|
|
19
|
+
| Formulário | §9 Forms |
|
|
20
|
+
| Metadata / SEO | §10 Metadata |
|
|
21
|
+
| Desempenho | §11 Performance |
|
|
22
|
+
| Testes | [stacks/testing.md](./stacks/testing.md) |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 📂 2. ESTRUTURA DE PASTAS
|
|
27
|
+
|
|
28
|
+
Enxuta, Next-first. **Não existe `client/` nem `server/` separado** — o App Router já é a fronteira.
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/
|
|
32
|
+
├── app/ # ROTEAMENTO (só isto é "framework")
|
|
33
|
+
│ ├── (marketing)/ # Route groups (não afetam a URL)
|
|
34
|
+
│ ├── (app)/ # Área logada
|
|
35
|
+
│ │ ├── layout.tsx
|
|
36
|
+
│ │ ├── page.tsx
|
|
37
|
+
│ │ ├── loading.tsx # Skeleton/Suspense fallback
|
|
38
|
+
│ │ ├── error.tsx # Error Boundary (precisa ser "use client")
|
|
39
|
+
│ │ └── not-found.tsx
|
|
40
|
+
│ ├── api/ # Route Handlers (exceção, não padrão)
|
|
41
|
+
│ ├── proxy.ts # Network boundary (substitui middleware.ts)
|
|
42
|
+
│ ├── layout.tsx # Root layout: <html>, fontes, metadata base
|
|
43
|
+
│ └── globals.css # Tokens do DESIGN.md via @theme
|
|
44
|
+
│
|
|
45
|
+
├── features/ # 🌟 SLICES DE DOMÍNIO (a fatia vertical)
|
|
46
|
+
│ └── billing/
|
|
47
|
+
│ ├── domain/ # Entidades, value objects, contratos (I*.ts)
|
|
48
|
+
│ ├── application/ # Use cases, services, orquestração
|
|
49
|
+
│ ├── infrastructure/ # Prisma, HTTP clients, filas, storage
|
|
50
|
+
│ ├── actions.ts # Server Actions (entrypoint de mutação)
|
|
51
|
+
│ ├── queries.ts # Leitura (entrypoint de query)
|
|
52
|
+
│ ├── schemas.ts # Zod: validação de entrada
|
|
53
|
+
│ └── ui/ # Componentes do domínio (client ou server)
|
|
54
|
+
│
|
|
55
|
+
├── shared/ # Genérico compartilhado entre features
|
|
56
|
+
│ ├── ui/ # Design system (shadcn + customizados)
|
|
57
|
+
│ ├── lib/ # Helpers puros (cn, formatadores)
|
|
58
|
+
│ ├── server/ # server-only: db, auth, config, logger
|
|
59
|
+
│ └── types/ # Tipos GLOGAIS de infra (não de domínio)
|
|
60
|
+
│
|
|
61
|
+
├── proxy.ts # OU na raiz, se preferir (só 1 arquivo)
|
|
62
|
+
└── DI/
|
|
63
|
+
└── container.ts # Container de DI manual (server-side)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Regras de ouro da estrutura
|
|
67
|
+
1. **`app/` só roteia.** Ele delega para `features/`. Nenhuma regra de negócio dentro de `page.tsx`.
|
|
68
|
+
2. **Feature não importa feature.** Precisa de algo de outra? Promova a extração para `shared/` ou evento de domínio.
|
|
69
|
+
3. **Interface (`I*.ts`) mora dentro do slice, junto da implementação.** Proibido `src/types/` global de domínio.
|
|
70
|
+
4. **UI de domínio vive em `features/*/ui/`.** `shared/ui/` é para o que é genérico de verdade (Button, Modal, Input).
|
|
71
|
+
5. **Route Handler é exceção.** Pense 10x antes de criar `/api`. Server Action é o padrão para mutação da própria UI.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 🧊 3. SERVER-FIRST (a regra que mais economiza bundle)
|
|
76
|
+
|
|
77
|
+
| Situação | Diretiva | Onde fica |
|
|
78
|
+
| ------------------------------------- | ----------------- | ---------------------------- |
|
|
79
|
+
| Layout, página, listagem, form estático | (nenhuma) | **Server Component** |
|
|
80
|
+
| Busca de dados no render | (nenhuma) | **Server Component** |
|
|
81
|
+
| Botão com `onClick`, input controlado | `"use client"` | Client Component |
|
|
82
|
+
| `useState`, `useEffect`, `useRef` | `"use client"` | Client Component |
|
|
83
|
+
| Integração com lib de browser | `"use client"` | Client Component |
|
|
84
|
+
|
|
85
|
+
### Regras
|
|
86
|
+
1. **O padrão é Server Component.** `"use client"` é uma decisão deliberada, não automática.
|
|
87
|
+
2. **O `"use client"` só pode estar na folha mais baixa possível.** Se ele sobe, ele **arrasta a árvore inteira** para o bundle do cliente. Errou? Extraia o trecho interativo para um arquivo só.
|
|
88
|
+
3. **Client Component só pode passar para Server Component: `children`, `action` (bound), e dados serializáveis.** Jamais uma função comum, nunca um objeto de DI.
|
|
89
|
+
4. **Proibido `useEffect` para buscar dados.** Se precisa buscar, é Server Component ou Server Action.
|
|
90
|
+
5. **Event handler que é uma requisição ao servidor → `<form action={serverAction}>`**, não `onClick` + `fetch`.
|
|
91
|
+
|
|
92
|
+
### Exemplo do padrão "folha cliente"
|
|
93
|
+
```tsx
|
|
94
|
+
// features/billing/ui/subscribe-button.tsx
|
|
95
|
+
"use client";
|
|
96
|
+
import { useFormStatus } from "react-dom";
|
|
97
|
+
import { subscribeAction } from "../actions";
|
|
98
|
+
|
|
99
|
+
// ✅ FOLHA: só isto vai pro bundle do cliente
|
|
100
|
+
export function SubscribeButton({ planId }: { planId: string }) {
|
|
101
|
+
return (
|
|
102
|
+
<form action={subscribeAction.bind(null, planId)}>
|
|
103
|
+
<button type="submit" className="btn-primary">
|
|
104
|
+
<SubmitLabel />
|
|
105
|
+
</button>
|
|
106
|
+
</form>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function SubmitLabel() {
|
|
111
|
+
const { pending } = useFormStatus();
|
|
112
|
+
return <span>{pending ? "Processando..." : "Assinar"}</span>;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
// app/(app)/billing/page.tsx — continua NO SERVER
|
|
118
|
+
import { SubscribeButton } from "@/features/billing/ui/subscribe-button";
|
|
119
|
+
|
|
120
|
+
export default async function BillingPage() {
|
|
121
|
+
const subscription = await getSubscription(); // direto, sem useEffect
|
|
122
|
+
return (
|
|
123
|
+
<main>
|
|
124
|
+
<PlanList />
|
|
125
|
+
<SubscribeButton planId={subscription.planId} />
|
|
126
|
+
</main>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 📦 4. DATA LAYER
|
|
134
|
+
|
|
135
|
+
1. **Toda leitura passa por `features/*/queries.ts`.** Nenhum `fetch`/Prisma direto em `page.tsx`.
|
|
136
|
+
2. **Query = função `async` simples.** Sem react-query no server. O cache é do Next (§6).
|
|
137
|
+
3. **Client-side com estado remoto e interativo → TanStack Query.** Mas a fonte da verdade é o servidor; se não precisa de optimistic/background refetch, nem use.
|
|
138
|
+
4. **`unstable_cache` / `revalidate` do modelo antigo = PROIBIDO.** Vai para `'use cache'`.
|
|
139
|
+
5. **Tipagem no limite:** todo retorno de repository/query é tipado. `any` é proibido em fronteira.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
// features/billing/queries.ts
|
|
143
|
+
import "server-only";
|
|
144
|
+
import { db } from "@/shared/server/db";
|
|
145
|
+
import { cache } from "react";
|
|
146
|
+
|
|
147
|
+
export const getSubscription = cache(async (userId: string) => {
|
|
148
|
+
return db.subscription.findUnique({ where: { userId } });
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> `cache()` do React deduplica a mesma chamada dentro do mesmo render. Use à vontade.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## ⚡ 5. SERVER ACTIONS
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
// features/billing/actions.ts
|
|
160
|
+
"use server";
|
|
161
|
+
import { z } from "zod";
|
|
162
|
+
import { requireUser } from "@/shared/server/auth";
|
|
163
|
+
import { db } from "@/shared/server/db";
|
|
164
|
+
import { updateTag } from "next/cache";
|
|
165
|
+
import { revalidatePath } from "next/cache";
|
|
166
|
+
|
|
167
|
+
const SubscribeSchema = z.object({
|
|
168
|
+
planId: z.string().min(1),
|
|
169
|
+
coupon: z.string().optional(),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export async function subscribeAction(_prev: State, formData: FormData) {
|
|
173
|
+
// 1. AUTENTICAÇÃO — primeira linha, sempre
|
|
174
|
+
const user = await requireUser();
|
|
175
|
+
|
|
176
|
+
// 2. VALIDAÇÃO — Zod no server é a ÚNICA fonte de verdade
|
|
177
|
+
const parsed = SubscribeSchema.safeParse(Object.fromEntries(formData));
|
|
178
|
+
if (!parsed.success) {
|
|
179
|
+
return { ok: false, errors: parsed.error.flatten().fieldErrors };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 3. AUTORIZAÇÃO (foco/objeto) — depois da auth, antes do dado
|
|
183
|
+
if (user.role === "banned") {
|
|
184
|
+
return { ok: false, error: "Acesso negado" };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 4. MUTAÇÃO via use case da camada application (nada de regra aqui)
|
|
188
|
+
await new CreateSubscriptionUseCase(db).execute({ userId: user.id, ...parsed.data });
|
|
189
|
+
|
|
190
|
+
// 5. CACHE — read-your-writes em UI interativa
|
|
191
|
+
updateTag(`subscription-${user.id}`);
|
|
192
|
+
revalidatePath("/billing");
|
|
193
|
+
|
|
194
|
+
return { ok: true };
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Regras de Server Actions
|
|
199
|
+
1. **São endpoints HTTP públicos.** Tudo que é validação/auth que importa TEM que ser dentro da action. Nunca confie no form.
|
|
200
|
+
2. **Ordem fixa:** `auth → validação → autorização → mutação → cache`.
|
|
201
|
+
3. **Input sempre via Zod.** `FormData`, `searchParams`, `params` e JSON do body entram crus.
|
|
202
|
+
4. **Nunca exponha objeto de DB.** Faça `select` explícito do que a UI precisa.
|
|
203
|
+
5. **Erro: retorne estado, não lance.** `throw` só para erro inesperado (vai pro `error.tsx`).
|
|
204
|
+
6. **`revalidateTag(tag)` (1 arg) está DEPRECIADO.** Use `revalidateTag(tag, 'max')` ou `updateTag(tag)` em actions.
|
|
205
|
+
7. **Ação só pode importar de `features/*` e `shared/`.** Nunca de `app/`.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## ⚡ 6. CACHE COMPONENTS (Padrão Next 16)
|
|
210
|
+
|
|
211
|
+
Ative no `next.config.ts`:
|
|
212
|
+
```ts
|
|
213
|
+
const nextConfig = { cacheComponents: true };
|
|
214
|
+
export default nextConfig;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
| Quero... | Use |
|
|
218
|
+
| ------------------------------------------ | --------------------------------------- |
|
|
219
|
+
| Cache de leitura (HTML shell) | `'use cache'` + `cacheLife('max')` |
|
|
220
|
+
| Marcar cache para invalidação | `cacheTag('user-123')` |
|
|
221
|
+
| Invalidação com leitura na mesma request | `updateTag('user-123')` (só em Action) |
|
|
222
|
+
| SWR (usuário vê velho, revalida em bg) | `revalidateTag('tag', 'max')` |
|
|
223
|
+
| Atualizar dado NÃO cacheado (contador, etc) | `refresh()` (só em Action) |
|
|
224
|
+
| Given time-varying (agora, Math.random) | `connection()` + `<Suspense>` |
|
|
225
|
+
|
|
226
|
+
**PROIBIDO:** `export const dynamic`, `export const revalidate`, `export const fetchCache`, `unstable_cache`, `fetch(..., { next: { revalidate } })`. Todos substituídos pelo modelo acima.
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
// ✅ Correto
|
|
230
|
+
export async function getBlogPosts() {
|
|
231
|
+
"use cache";
|
|
232
|
+
cacheLife("hours");
|
|
233
|
+
cacheTag("blog-posts");
|
|
234
|
+
return db.post.findMany();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ❌ Proibido
|
|
238
|
+
export const revalidate = 3600;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Regra de ouro:** `use cache` o mais perto possível da leitura. Envolva a página inteira só como último recurso.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 🔐 7. SEGURANÇA
|
|
246
|
+
|
|
247
|
+
1. **`proxy.ts` NÃO é fronteira de segurança.** É otimização de UX (redirect rápido). Por causa do CVE-2025-29927, o header interno `x-middleware-subrequest` é confiavel por padrão — **nunca** confie nele.
|
|
248
|
+
2. **Autorização vai na camada de dado.** `requireUser()` / `requireRole()` dentro de cada action/query, o mais perto possível do banco.
|
|
249
|
+
3. **Zero validação só no client.** Client-side Zod é UX. O Zod do servidor é segurança.
|
|
250
|
+
4. **Env:** `NEXT_PUBLIC_*` vai pro bundle do browser. Nunca coloque secret lá. Valide env com Zod em `shared/server/env.ts` no boot.
|
|
251
|
+
5. **Imagens remotas:** só `images.remotePatterns` no `next.config.ts`. `images.domains` está deprecado.
|
|
252
|
+
6. **Server-only:** adicione `import "server-only"` no topo de todo módulo com credencial/DB.
|
|
253
|
+
7. **`next/image` com `src` local + query string** exige `images.localPatterns`. Sem isso, quebrou.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 🔌 8. ROUTE HANDLERS (`app/api/*/route.ts`)
|
|
258
|
+
|
|
259
|
+
Use **só** para: webhook de terceiro, endpoint público de terceiro, ou download de arquivo.
|
|
260
|
+
Para o resto: Server Action.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// app/api/webhooks/stripe/route.ts
|
|
264
|
+
import { z } from "zod";
|
|
265
|
+
import { headers } from "next/headers";
|
|
266
|
+
|
|
267
|
+
const StripeEvent = z.object({ id: z.string(), type: z.string() });
|
|
268
|
+
|
|
269
|
+
export async function POST(request: Request) {
|
|
270
|
+
const raw = await request.text(); // ← assinatura precisa do corpo cru
|
|
271
|
+
const parsed = StripeEvent.safeParse(JSON.parse(raw));
|
|
272
|
+
if (!parsed.success) return Response.json({ error: "invalid" }, { status: 400 });
|
|
273
|
+
|
|
274
|
+
await new ProcessStripeEventUseCase(db).execute(parsed.data);
|
|
275
|
+
return Response.json({ received: true });
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Regras: valide **tudo** (inclusive o body cru), sempre responda com `Response.json`, `await` em `headers()`/`cookies()`, e nunca exponha stack trace.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 📝 9. FORMS
|
|
284
|
+
|
|
285
|
+
1. **Padrão:** `<form action={serverAction}>` com `useFormStatus` para o loading. Zero JS de estado.
|
|
286
|
+
2. **Precisa de controlled input / validação ao vivo?** → Client Component isolado com `useActionState`.
|
|
287
|
+
3. **Erro de campo** renderize **próximo ao campo**, com `aria-invalid` e `aria-describedby`.
|
|
288
|
+
4. **Desabilite o botão no `pending`.** Sem duplo submit.
|
|
289
|
+
5. **Sucesso:** `toast` (shadcn `sonner`) + `updateTag`/`revalidatePath`. Erro: toast vermelho + mensagem no formulário.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 🔍 10. METADATA & SEO
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
// app/(marketing)/page.tsx
|
|
297
|
+
export const metadata: Metadata = {
|
|
298
|
+
title: { default: "Marca", template: "%s | Marca" },
|
|
299
|
+
description: "...",
|
|
300
|
+
};
|
|
301
|
+
export async function generateMetadata({ params }): Promise<Metadata> { /* por rota */ }
|
|
302
|
+
```
|
|
303
|
+
- `metadata` **sempre estático** quando possível. `generateMetadata` só quando o dado é o assunto da rota.
|
|
304
|
+
- `params` e `searchParams` são **Promises** no Next 16: `const { slug } = await params;`
|
|
305
|
+
- Cacheie o que for possível: `async function getMetadata(){ "use cache"; cacheLife('hours'); ... }`
|
|
306
|
+
- `viewport` e `themeColor` vão no `viewport` export, não em `metadata`.
|
|
307
|
+
- Gera `sitemap.ts` e `robots.ts` em `app/`.
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## ⚡ 11. PERFORMANCE
|
|
312
|
+
|
|
313
|
+
| Regra | Como |
|
|
314
|
+
| --- | --- |
|
|
315
|
+
| `next/image` sempre, com `sizes` explícito em grid/list | `<Image src fill sizes="(max-width:768px) 100vw, 33vw" />` |
|
|
316
|
+
| `next/font` para tudo (zero layout shift, zero request externo) | `import { Manrope, JetBrains_Mono } from "next/font/google"` |
|
|
317
|
+
| `next/link` para navegação interna | `<Link href>` |
|
|
318
|
+
| `Suspense` no limite, não na página inteira | streamed shell |
|
|
319
|
+
| `use cache` em tudo que é leitura de DB em rota estável | §6 |
|
|
320
|
+
| Bundle client mínimo | `"use client"` na folha (§3) |
|
|
321
|
+
| Analise antes de otimizar | `next build` mostra o custo de cada rota |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## 🚨 ARMADILHAS DO NEXT 16 (não caia nelas)
|
|
326
|
+
|
|
327
|
+
1. `params`/`searchParams` são **async**. `const { id } = params` → undefined silencioso.
|
|
328
|
+
2. `cookies()`, `headers()`, `draftMode()` são **async**.
|
|
329
|
+
3. `middleware.ts` foi **renomeado para `proxy.ts`** (função exportada `proxy`). `middleware.ts` é ignorado em silêncio no build.
|
|
330
|
+
4. `revalidateTag(tag)` de 1 argumento está **depreciado**.
|
|
331
|
+
5. `next lint` **foi removido**. Use Biome/ESLint direto.
|
|
332
|
+
6. Turbopack é o **padrão**. `--webpack` só em Emergency Exit.
|
|
333
|
+
7. Parallel routes exigem `default.js` explícito, senão **o build falha**.
|
|
334
|
+
8. Route segment configs (`dynamic`, `revalidate`, `fetchCache`) **não existem** com `cacheComponents`.
|
|
335
|
+
9. `images.domains` → `images.remotePatterns`.
|
|
336
|
+
10. AMP removido. `next/legacy/image` removido.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 📎 REGRAS complementares
|
|
341
|
+
|
|
342
|
+
- **Node.js puro (workers, cron, filas, webhooks pesados):** [NODE.md](./NODE.md)
|
|
343
|
+
- **React (quando a lógica é puramente de componente):** [REACT.md](./REACT.md)
|
|
344
|
+
- **Design/UI:** [DESIGN.md](./DESIGN.md)
|
|
345
|
+
- **TypeScript, Tailwind, shadcn, testes:** [stacks/](./stacks/README.md)
|
package/NODE.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 🟢 NODE.JS (complemento)
|
|
2
|
+
|
|
3
|
+
> **Stack padrão é Next.js** ([NEXT.md](./NEXT.md)). Node.js puro entra **só** quando o App Router não dá conta.
|
|
4
|
+
> Vertical Slices, igual. Sem MVC. Foco no domínio.
|
|
5
|
+
|
|
6
|
+
## 🤔 Quando usar Node.js puro (e quando NÃO)
|
|
7
|
+
|
|
8
|
+
| ✅ USE Node.js | ❌ NÃO USE — use Next.js |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| Worker de fila (processa job em background) | API/rota/mutação de UI |
|
|
11
|
+
| Cron / scheduler | Server Action |
|
|
12
|
+
| Consumer de webhook pesado / alto volume | Route Handler em `app/api` |
|
|
13
|
+
| CLI de batch / script de importação | Script pontual |
|
|
14
|
+
| Processamento longo que não pode segurar request | Qualquer coisa que responde a request |
|
|
15
|
+
| Processo de streaming de IA de longa duração | AI SDK dentro de Server Action |
|
|
16
|
+
|
|
17
|
+
> **Heurística:** "isso roda porque alguém clicou num botão?" → Next.js. "isso roda porque o relógio tocou / a fila encheu?" → Node.js.
|
|
18
|
+
|
|
19
|
+
## 📂 ESTRUTURA
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
server/ (ou worker/, jobs/)
|
|
23
|
+
├── main.ts # Entry point: boot, graceful shutdown
|
|
24
|
+
├── api/ # Só se precisar HTTP (Fastify/Hono). Vazamento controlado.
|
|
25
|
+
├── core/ # Config, logger, erros, DI, conexão de DB
|
|
26
|
+
└── features/ # 🌟 MESMA estrutura do Next (vertical slices)
|
|
27
|
+
└── billing/
|
|
28
|
+
├── domain/
|
|
29
|
+
├── application/
|
|
30
|
+
└── infrastructure/
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🧠 REGRAS
|
|
34
|
+
|
|
35
|
+
1. **Desacoplamento:** o use case NUNCA recebe `req`/`res`. Recebe dados puros, retorna dados puros.
|
|
36
|
+
2. **Isolamento:** uma `feature` não importa a outra. Compartilhe via `core/` ou extraia para um novo slice.
|
|
37
|
+
3. **Contrato na entrada:** worker/cron entra por uma **porta de entrada** com payload validado por Zod. Mesma lei das Server Actions ([NEXT.md](./NEXT.md) §5).
|
|
38
|
+
4. **Ordem fixa:** `auth/escopo → validação → autorização → lógica → I/O`.
|
|
39
|
+
5. **Idempotência:** job de fila pode rodar duas vezes. Use chave de idempotência.
|
|
40
|
+
6. **Retry com backoff exponencial** em falha transitória. **Dead-letter queue** em falha permanente.
|
|
41
|
+
7. **Graceful shutdown:** pegue `SIGTERM`, feche servidor e conexões antes de sair.
|
|
42
|
+
8. **Uma responsabilidade por processo.** Worker e API no mesmo processo = dívida.
|
|
43
|
+
|
|
44
|
+
## 💻 EXEMPLO: use case puro
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// features/billing/application/ProcessInvoice.ts
|
|
48
|
+
import type { IInvoiceRepository } from "../domain/IInvoiceRepository";
|
|
49
|
+
import { z } from "zod";
|
|
50
|
+
|
|
51
|
+
export const ProcessInvoiceInput = z.object({
|
|
52
|
+
invoiceId: z.string().min(1),
|
|
53
|
+
idempotencyKey: z.string().min(8),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export class ProcessInvoice {
|
|
57
|
+
constructor(private readonly repo: IInvoiceRepository) {}
|
|
58
|
+
|
|
59
|
+
async execute(raw: unknown) {
|
|
60
|
+
const input = ProcessInvoiceInput.parse(raw); // 1. valida na fronteira
|
|
61
|
+
const invoice = await this.repo.findById(input.invoiceId);
|
|
62
|
+
if (!invoice) throw new Error("INVOICE_NOT_FOUND");
|
|
63
|
+
|
|
64
|
+
if (await this.repo.wasProcessed(input.idempotencyKey)) {
|
|
65
|
+
return { status: "skipped" as const }; // 2. idempotência
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const total = invoice.lines.reduce((sum, l) => sum + l.amount, 0);
|
|
69
|
+
await this.repo.markProcessed(input.idempotencyKey, total);
|
|
70
|
+
return { status: "processed" as const, total }; // 3. efeito
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## ▶️ Boot de worker
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// main.ts
|
|
79
|
+
import { logger } from "./core/logger";
|
|
80
|
+
import { createBillingUseCases } from "./features/billing/container";
|
|
81
|
+
|
|
82
|
+
process.on("SIGTERM", async () => {
|
|
83
|
+
logger.info("shutdown signal");
|
|
84
|
+
await closeDb();
|
|
85
|
+
process.exit(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const billing = createBillingUseCases();
|
|
89
|
+
logger.info("worker online", { version: process.env.npm_package_version });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 🧪 Testes
|
|
93
|
+
|
|
94
|
+
- Use case = unit test com repositório falso. Zero mock de infra.
|
|
95
|
+
- Integração: teste contra o broker real em container de teste.
|
|
96
|
+
- **Obrigatório:** cenário de "processa 2×" (idempotência) e "falha no meio do job".
|
|
97
|
+
|
|
98
|
+
## 🚫 Proibido
|
|
99
|
+
|
|
100
|
+
| Padrão | Por quê |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| Express/Fastify como default | App Router já é a API |
|
|
103
|
+
| `setInterval` para job crítico | Use cron/queue real |
|
|
104
|
+
| `process.exit()` no meio de job | Graceful shutdown |
|
|
105
|
+
| `any` no payload | Zod na fronteira |
|
|
106
|
+
| Segredo em log | Redact obrigatório |
|
|
107
|
+
| lógica de negócio no handler HTTP | Vai pro `application/` |
|
package/REACT.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ⚛️ REACT (complemento)
|
|
2
|
+
|
|
3
|
+
> **O padrão NÃO é React Client.** Next.js 16 usa **Server Components** por padrão.
|
|
4
|
+
> Leia este doc só quando o problema for de **componente/estado de cliente**, não de servidor.
|
|
5
|
+
> Regras de Next.js: [NEXT.md](./NEXT.md) §3 (Server-First).
|
|
6
|
+
|
|
7
|
+
## 🧭 Roteador
|
|
8
|
+
|
|
9
|
+
| Situação | Faça |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Busca de dado no render | **Server Component.** Nem precisa deste doc. |
|
|
12
|
+
| Mutação vinda de formulário | **Server Action** ([NEXT.md](./NEXT.md) §5) |
|
|
13
|
+
| Só o botão tem `onClick` | Folha `"use client"` isolada (§3 do NEXT.md) |
|
|
14
|
+
| Estado local de UI (abrir/fechar) | `useState` neste arquivo |
|
|
15
|
+
| Estado que sobrevive a rota | Cookie/DB, não contexto |
|
|
16
|
+
| Cache remoto com optimistic update | TanStack Query (`@tanstack/react-query`) |
|
|
17
|
+
| Zustand/Redux | **Evite.** Substitua por props, contexto local ou `cache()` do React |
|
|
18
|
+
|
|
19
|
+
## 🚨 Regras não-negociáveis
|
|
20
|
+
|
|
21
|
+
1. **Server Component é o padrão.** `"use client"` é exceção deliberada — sempre na folha mais baixa (ver [NEXT.md](./NEXT.md) §3).
|
|
22
|
+
2. **Server Component pode ser async; Client Component NÃO pode** (exceto com `use()`, dentro de Suspense).
|
|
23
|
+
3. **Client Component nunca importa módulo `server-only`.** Isso quebra o build — e é o objetivo.
|
|
24
|
+
4. **Props de Server → Client precisam ser serializáveis.** Nada de função, class, Date é preciso (Date vira string).
|
|
25
|
+
5. **`useEffect` não busca dado.** Ele sincroniza efeito colateral. Busca = Server Component / Server Action.
|
|
26
|
+
6. **Estado é do mais local possível:** `useState` > contexto > store global.
|
|
27
|
+
7. **Lista com key estável, nunca índice.**
|
|
28
|
+
8. **Componente cliente pequeno e burro.** Lógica vai pro use case (`features/`).
|
|
29
|
+
|
|
30
|
+
## 🪝 Hooks — regras do React 19.2
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// ✅ Coloca hooks antes de qualquer return condicional
|
|
34
|
+
function Panel({ open }: { open: boolean }) {
|
|
35
|
+
const [tab, setTab] = useState("overview");
|
|
36
|
+
useEffect(() => { /* sync externo */ }, [open]);
|
|
37
|
+
if (!open) return null;
|
|
38
|
+
return <div>{tab}</div>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ✅ Lógica não-reativa isolada em useEffectEvent
|
|
42
|
+
useEffectEvent(() => { onSubmitRef.current(value); });
|
|
43
|
+
|
|
44
|
+
// ✅ Atividade em background sem desmontar estado
|
|
45
|
+
<Activity mode="hidden">…</Activity>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Hook | Use para | Não use para |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| `useState` | estado local de UI | estado derivado (calcule na render) |
|
|
51
|
+
| `useReducer` | máquina de estado complexa | 2 estados booleanos |
|
|
52
|
+
| `useEffect` | sincronizar com sistema externo (DOM, socket, subscription) | buscar dado, derivar estado |
|
|
53
|
+
| `useMemo` | cálculo caro | performance "por garantia" |
|
|
54
|
+
| `useCallback` | dep de hook/estabilizar referência | performance "por garantia" |
|
|
55
|
+
| `useContext` | dado de UI compartilhado na subárvore | estado global de domínio |
|
|
56
|
+
| `useOptimistic` | UI otimista em Server Action | cache geral |
|
|
57
|
+
|
|
58
|
+
> **React Compiler está ligado** no template (`reactCompiler: true` no `next.config.ts`). `useMemo`/`useCallback` manuais são, na maioria das vezes, ruído.
|
|
59
|
+
|
|
60
|
+
## 🧩 Composição
|
|
61
|
+
|
|
62
|
+
1. **Compound components** para UI complexa (`<Select>`, `<SelectItem>`). Menos props, mais API.
|
|
63
|
+
2. **Composição > configuração.** 3 componentes pequenos > 1 com 15 props booleanas.
|
|
64
|
+
3. **Server Component compose Client Components** ("children pattern"):
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// ✅ Passa markup, não função
|
|
68
|
+
<Shell sidebar={<ServerRenderedSidebar />}>
|
|
69
|
+
<InteractiveChart />
|
|
70
|
+
</Shell>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 📁 Onde cada coisa mora
|
|
74
|
+
|
|
75
|
+
| Tipo | Caminho |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| UI primitiva genérica | `src/shared/ui/` |
|
|
78
|
+
| UI com regra de negócio | `src/features/<x>/ui/` |
|
|
79
|
+
| Hook transversal | `src/shared/hooks/` |
|
|
80
|
+
| Provider de contexto | junto do hook que consome |
|
|
81
|
+
|
|
82
|
+
## 🚫 Anti-padrões
|
|
83
|
+
|
|
84
|
+
| ❌ | ✅ |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `"use client"` no topo do layout/page | folha isolada |
|
|
87
|
+
| `useEffect` + `fetch` para buscar dado | Server Component / Action |
|
|
88
|
+
| Zustand/Redux para estado de servidor | `cache()` do React / Server |
|
|
89
|
+
| Props drilling de 5 níveis | contexto local ou composição |
|
|
90
|
+
| `key={index}` em lista | id estável |
|
|
91
|
+
| `useMemo` em tudo | deixe o Compiler fazer |
|
|
92
|
+
| Context com objeto que muda a cada render | separe value e actions |
|