@polymorphism-tech/morph-spec 6.0.0 → 7.1.0

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.
Files changed (114) hide show
  1. package/README.md +367 -366
  2. package/docs/CHEATSHEET.md +3 -3
  3. package/docs/QUICKSTART.md +4 -4
  4. package/framework/CLAUDE.md +13 -0
  5. package/framework/MORPH.md +57 -36
  6. package/framework/agents.json +132 -127
  7. package/framework/commands/morph-apply.md +4 -21
  8. package/framework/commands/morph-archive.md +1 -1
  9. package/framework/commands/morph-proposal.md +2 -0
  10. package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
  11. package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
  12. package/framework/hooks/claude-code/core/state-sync.js +212 -0
  13. package/framework/hooks/shared/phase-utils.js +5 -4
  14. package/framework/hooks/shared/state-reader.js +1 -1
  15. package/framework/phases.json +1 -1
  16. package/framework/rubrics/backend.json +41 -36
  17. package/framework/rules/ai-agents-standards.md +45 -15
  18. package/framework/rules/morph-workflow.md +15 -34
  19. package/framework/rules/neon-standards.md +25 -26
  20. package/framework/schemas/tasks.schema.json +35 -0
  21. package/framework/skills/core/morph-apply/SKILL.md +154 -0
  22. package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
  23. package/framework/skills/core/morph-implement/SKILL.md +25 -14
  24. package/framework/skills/core/morph-plan/SKILL.md +91 -23
  25. package/framework/skills/core/morph-proposal/SKILL.md +137 -0
  26. package/framework/skills/core/morph-review/SKILL.md +43 -7
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
  28. package/framework/standards/STANDARDS.json +1729 -1134
  29. package/framework/standards/ai-agents/agent-session.md +160 -0
  30. package/framework/standards/ai-agents/batching.md +193 -0
  31. package/framework/standards/ai-agents/context-providers.md +188 -0
  32. package/framework/standards/ai-agents/mcp-server.md +250 -0
  33. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  34. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  35. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  36. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  37. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  38. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  39. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  40. package/framework/standards/ai-agents/production.md +114 -258
  41. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  42. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  43. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  44. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  45. package/framework/standards/ai-agents/setup.md +142 -316
  46. package/framework/standards/ai-agents/structured-output.md +249 -0
  47. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  48. package/framework/standards/ai-agents/workflows.md +114 -284
  49. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  50. package/framework/standards/backend/api/validation.md +92 -88
  51. package/framework/standards/backend/database/postgresql/database.md +336 -332
  52. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  53. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  54. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  55. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  56. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  57. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  58. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  59. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  60. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  61. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  62. package/framework/standards/data/nosql/cache/redis.md +106 -97
  63. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  64. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  65. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  66. package/framework/standards/frontend/design-system/animations.md +164 -160
  67. package/framework/standards/frontend/design-system/naming.md +68 -64
  68. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  69. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  70. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  71. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  72. package/framework/standards/frontend/nextjs/components.md +136 -132
  73. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  74. package/framework/standards/frontend/nextjs/forms.md +134 -128
  75. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  76. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  77. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  78. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  79. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  80. package/framework/standards/frontend/nextjs/testing.md +115 -111
  81. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  82. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  83. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  84. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  85. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  86. package/framework/templates/REGISTRY.json +1745 -1239
  87. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  88. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  89. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  90. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  91. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  92. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  93. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  96. package/framework/templates/docs/onboarding.md +4 -4
  97. package/framework/templates/tasks.template.json +17 -0
  98. package/package.json +89 -83
  99. package/src/commands/project/init.js +16 -0
  100. package/src/commands/validation/validate-feature.js +1 -1
  101. package/src/core/paths/output-schema.js +6 -6
  102. package/src/core/state/state-manager.js +1 -1
  103. package/src/core/templates/template-registry.js +289 -256
  104. package/src/lib/detectors/design-system-detector.js +1 -1
  105. package/src/lib/generators/context-generator.js +516 -518
  106. package/src/lib/generators/scaffold-generator.js +23 -26
  107. package/src/lib/installers/memory-installer.js +138 -0
  108. package/src/lib/phase-chain/phase-validator.js +4 -4
  109. package/src/lib/risk-detector.js +80 -0
  110. package/src/lib/tasks/tasks-json-parser.js +185 -0
  111. package/src/scripts/setup-infra.js +25 -0
  112. package/src/utils/hooks-installer.js +59 -10
  113. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  114. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -1,67 +1,71 @@
1
- # Next.js Naming Conventions Standard
2
-
3
- > **Scope:** frontend/nextjs/naming-conventions
4
- > **Layer:** 1 (always)
5
- > **Keywords:** naming, conventions, file names, component names, hooks, typescript
6
- > **Load When:** creating any new file in a Next.js project
7
-
8
- Consistent naming prevents filesystem bugs (Linux case-sensitivity) and keeps the codebase predictable.
9
-
10
- ## Core Rules
11
-
12
- - ALWAYS use kebab-case for file names — never PascalCase or camelCase
13
- - ALWAYS use PascalCase for React component exports
14
- - ALWAYS use camelCase starting with `use` for hook exports
15
- - ALWAYS suffix schema files with `.schemas.ts` and type files with `.types.ts`
16
- - NEVER mix cases in the same category — no `UserCard.tsx` alongside `user-profile.tsx`
17
-
18
- ## Complete Reference Table
19
-
20
- | Artifact | File Name | Export Name | Example |
21
- |----------|-----------|-------------|---------|
22
- | React Component | `user-card.tsx` | `UserCard` | `export function UserCard()` |
23
- | Client Component | `user-form.tsx` | `UserForm` | `export function UserForm()` + `'use client'` |
24
- | TanStack Query hook | `use-users.ts` | `useUsers` | `export function useUsers()` |
25
- | Mutation hook | `use-create-user.ts` | `useCreateUser` | `export function useCreateUser()` |
26
- | Utility hook | `use-debounce.ts` | `useDebounce` | `export function useDebounce()` |
27
- | Zod schema file | `user.schemas.ts` | `createUserSchema` | `export const createUserSchema = z.object(...)` |
28
- | TypeScript types | `user.types.ts` | `User`, `CreateUserInput` | `export type User = z.infer<typeof userSchema>` |
29
- | Feature index | `index.ts` | (re-exports) | `export { UserCard } from './components/user-card'` |
30
- | Page file | `page.tsx` | `default` | `export default function UsersPage()` |
31
-
32
- ## Hook Naming Rules
33
-
34
- ```
35
- GET list: use-users.ts → useUsers()
36
- GET single: use-user.ts → useUser(id: string)
37
- POST: use-create-user.ts → useCreateUser()
38
- PUT/PATCH: use-update-user.ts → useUpdateUser()
39
- DELETE: use-delete-user.ts → useDeleteUser()
40
- ```
41
-
42
- ## Schema and Type Naming Rules
43
-
44
- ```typescript
45
- // user.schemas.ts
46
- export const userSchema = z.object({ id: z.string(), name: z.string() });
47
- export const createUserSchema = z.object({ name: z.string().min(2), email: z.string().email() });
48
- export const updateUserSchema = createUserSchema.partial();
49
-
50
- // user.types.ts — derive from schemas, don't duplicate
51
- export type User = z.infer<typeof userSchema>;
52
- export type CreateUserInput = z.infer<typeof createUserSchema>;
53
- export type UpdateUserInput = z.infer<typeof updateUserSchema>;
54
- ```
55
-
56
- ## Common Mistakes
57
-
58
- | Wrong | Right | Why |
59
- |-------|-------|-----|
60
- | `UserCard.tsx` | `user-card.tsx` | Linux servers are case-sensitive |
61
- | `useUsers.ts` | `use-users.ts` | Inconsistent with Next.js file conventions |
62
- | `export default function UserCard` | `export function UserCard` | Named exports are more refactor-safe |
63
- | `type User = { id: string }` | `type User = z.infer<typeof userSchema>` | Single source of truth |
64
-
65
- ---
66
-
67
- *MORPH-SPEC by Polymorphism Tech*
1
+ # Next.js Naming Conventions Standard
2
+
3
+ > **Scope:** frontend/nextjs/naming-conventions
4
+ > **Layer:** 1 (always)
5
+ > **Keywords:** naming, conventions, file names, component names, hooks, typescript
6
+ > **Load When:** creating any new file in a Next.js project
7
+
8
+ **Verified against:** Next.js 15 + Zod 4. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Consistent naming prevents filesystem bugs (Linux case-sensitivity) and keeps the codebase predictable.
13
+
14
+ ## Core Rules
15
+
16
+ - ALWAYS use kebab-case for file names — never PascalCase or camelCase
17
+ - ALWAYS use PascalCase for React component exports
18
+ - ALWAYS use camelCase starting with `use` for hook exports
19
+ - ALWAYS suffix schema files with `.schemas.ts` and type files with `.types.ts`
20
+ - NEVER mix cases in the same category — no `UserCard.tsx` alongside `user-profile.tsx`
21
+
22
+ ## Complete Reference Table
23
+
24
+ | Artifact | File Name | Export Name | Example |
25
+ |----------|-----------|-------------|---------|
26
+ | React Component | `user-card.tsx` | `UserCard` | `export function UserCard()` |
27
+ | Client Component | `user-form.tsx` | `UserForm` | `export function UserForm()` + `'use client'` |
28
+ | TanStack Query hook | `use-users.ts` | `useUsers` | `export function useUsers()` |
29
+ | Mutation hook | `use-create-user.ts` | `useCreateUser` | `export function useCreateUser()` |
30
+ | Utility hook | `use-debounce.ts` | `useDebounce` | `export function useDebounce()` |
31
+ | Zod schema file | `user.schemas.ts` | `createUserSchema` | `export const createUserSchema = z.object(...)` |
32
+ | TypeScript types | `user.types.ts` | `User`, `CreateUserInput` | `export type User = z.infer<typeof userSchema>` |
33
+ | Feature index | `index.ts` | (re-exports) | `export { UserCard } from './components/user-card'` |
34
+ | Page file | `page.tsx` | `default` | `export default function UsersPage()` |
35
+
36
+ ## Hook Naming Rules
37
+
38
+ ```
39
+ GET list: use-users.ts → useUsers()
40
+ GET single: use-user.ts → useUser(id: string)
41
+ POST: use-create-user.ts → useCreateUser()
42
+ PUT/PATCH: use-update-user.ts → useUpdateUser()
43
+ DELETE: use-delete-user.ts → useDeleteUser()
44
+ ```
45
+
46
+ ## Schema and Type Naming Rules
47
+
48
+ ```typescript
49
+ // user.schemas.ts
50
+ export const userSchema = z.object({ id: z.string(), name: z.string() });
51
+ export const createUserSchema = z.object({ name: z.string().min(2), email: z.email() });
52
+ export const updateUserSchema = createUserSchema.partial();
53
+
54
+ // user.types.ts — derive from schemas, don't duplicate
55
+ export type User = z.infer<typeof userSchema>;
56
+ export type CreateUserInput = z.infer<typeof createUserSchema>;
57
+ export type UpdateUserInput = z.infer<typeof updateUserSchema>;
58
+ ```
59
+
60
+ ## Common Mistakes
61
+
62
+ | Wrong | Right | Why |
63
+ |-------|-------|-----|
64
+ | `UserCard.tsx` | `user-card.tsx` | Linux servers are case-sensitive |
65
+ | `useUsers.ts` | `use-users.ts` | Inconsistent with Next.js file conventions |
66
+ | `export default function UserCard` | `export function UserCard` | Named exports are more refactor-safe |
67
+ | `type User = { id: string }` | `type User = z.infer<typeof userSchema>` | Single source of truth |
68
+
69
+ ---
70
+
71
+ *MORPH-SPEC by Polymorphism Tech*
@@ -1,225 +1,229 @@
1
- # Next.js 15 Patterns Standard
2
-
3
- > **Scope:** nextjs-neon
4
- > **Layer:** 2 (on keyword)
5
- > **Keywords:** nextjs, react, app router, server component, client component
6
- > **Load When:** nextjs keywords detected
7
-
8
- Stack: Next.js 15 + Neon PostgreSQL + .NET Backend
9
-
10
- ## Core Rules
11
-
12
- - ALWAYS use App Router (not Pages Router)
13
- - Default to Server Components -- add `'use client'` only when needed
14
- - ALWAYS colocate loading.tsx and error.tsx with page.tsx
15
- - NEVER call the database directly from client -- use Route Handlers as BFF
16
- - ALWAYS validate inputs with Zod on both client and server
17
- - Use TypeScript strict mode (`"strict": true`)
18
-
19
- ## Server vs Client Components
20
-
21
- | Aspect | Server Component (default) | Client Component (`'use client'`) |
22
- |--------|---------------------------|-----------------------------------|
23
- | Renders | Server only | Server SSR + Client hydration |
24
- | Access to | DB, env vars, fs, async/await | Browser APIs, useState, useEffect, events |
25
- | Bundle | Not included | Included in JS bundle |
26
- | Use when | Data fetching, static content | Interactivity, forms, real-time |
27
-
28
- Decision: Need useState/useEffect/onClick/browser APIs? Client Component. Otherwise Server Component.
29
-
30
- ## File-Based Routing
31
-
32
- ```
33
- app/
34
- layout.tsx # Root layout
35
- page.tsx # / (home)
36
- loading.tsx / error.tsx # Loading UI / Error boundary
37
- not-found.tsx # 404
38
- dashboard/
39
- layout.tsx # Nested layout
40
- page.tsx # /dashboard
41
- loading.tsx
42
- api/documents/
43
- route.ts # GET/POST /api/documents
44
- [id]/route.ts # GET/PUT/DELETE /api/documents/:id
45
- ```
46
-
47
- ## Layout and Error Boundaries
48
-
49
- ```tsx
50
- export default function RootLayout({ children }: { children: React.ReactNode }) {
51
- return <html lang="en"><body><Providers>{children}</Providers></body></html>;
52
- }
53
-
54
- // loading.tsx
55
- export default function Loading() { return <div className="animate-pulse">Loading...</div>; }
56
-
57
- // error.tsx — MUST be 'use client'
58
- 'use client';
59
- export default function Error({ error, reset }: { error: Error; reset: () => void }) {
60
- return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
61
- }
62
- ```
63
-
64
- ## Route Handlers (BFF Pattern)
65
-
66
- ```ts
67
- // app/api/documents/route.ts — BFF pattern (delegates to .NET API)
68
- import { auth } from "@/lib/auth/server";
69
- import { NextResponse } from "next/server";
70
- import { z } from "zod";
71
-
72
- export const dynamic = "force-dynamic";
73
-
74
- const API_URL = process.env.API_URL!; // .NET backend
75
-
76
- const CreateSchema = z.object({
77
- title: z.string().min(1).max(200),
78
- content: z.string().min(1),
79
- });
80
-
81
- export async function GET() {
82
- const { data: session } = await auth.getSession();
83
- if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
84
-
85
- const res = await fetch(`${API_URL}/api/documents`, {
86
- headers: { Authorization: `Bearer ${session.session.access_token}` },
87
- });
88
- return NextResponse.json(await res.json());
89
- }
90
-
91
- export async function POST(request: Request) {
92
- const { data: session } = await auth.getSession();
93
- if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
94
-
95
- const parsed = CreateSchema.safeParse(await request.json());
96
- if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
97
-
98
- const res = await fetch(`${API_URL}/api/documents`, {
99
- method: "POST",
100
- headers: { Authorization: `Bearer ${session.session.access_token}`, "Content-Type": "application/json" },
101
- body: JSON.stringify(parsed.data),
102
- });
103
- return NextResponse.json(await res.json(), { status: res.status });
104
- }
105
- ```
106
-
107
- > **Rule:** Route handlers act as BFF (Backend-For-Frontend) — they delegate to the .NET API. Database access lives in the .NET backend via EF Core, never in Next.js route handlers.
108
-
109
- ## React Query + Neon
110
-
111
- ```tsx
112
- // providers/query-provider.tsx
113
- 'use client';
114
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
115
- import { useState } from "react";
116
-
117
- export function QueryProvider({ children }: { children: React.ReactNode }) {
118
- const [client] = useState(() => new QueryClient({
119
- defaultOptions: { queries: { staleTime: 60_000, retry: 1 } },
120
- }));
121
- return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
122
- }
123
- ```
124
-
125
- ```tsx
126
- // hooks/use-documents.ts
127
- export function useDocuments() {
128
- return useQuery({
129
- queryKey: ["documents"],
130
- queryFn: async () => {
131
- const res = await fetch("/api/documents");
132
- if (!res.ok) throw new Error("Failed to fetch");
133
- return res.json();
134
- },
135
- });
136
- }
137
-
138
- export function useCreateDocument() {
139
- const qc = useQueryClient();
140
- return useMutation({
141
- mutationFn: async (data: { title: string; content: string }) => {
142
- const res = await fetch("/api/documents", {
143
- method: "POST", headers: { "Content-Type": "application/json" },
144
- body: JSON.stringify(data),
145
- });
146
- if (!res.ok) throw new Error("Failed to create");
147
- return res.json();
148
- },
149
- onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }),
150
- });
151
- }
152
- ```
153
-
154
- ## Form Handling (react-hook-form + Zod)
155
-
156
- ```tsx
157
- 'use client';
158
- import { useForm } from "react-hook-form";
159
- import { zodResolver } from "@hookform/resolvers/zod";
160
- import { z } from "zod";
161
-
162
- const schema = z.object({
163
- title: z.string().min(1, "Required").max(200),
164
- content: z.string().min(1, "Required"),
165
- });
166
-
167
- export function DocumentForm() {
168
- const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<z.infer<typeof schema>>({
169
- resolver: zodResolver(schema),
170
- });
171
- const create = useCreateDocument();
172
- return (
173
- <form onSubmit={handleSubmit((data) => create.mutateAsync(data))}>
174
- <input {...register("title")} />
175
- {errors.title && <span>{errors.title.message}</span>}
176
- <textarea {...register("content")} />
177
- {errors.content && <span>{errors.content.message}</span>}
178
- <button type="submit" disabled={isSubmitting}>Save</button>
179
- </form>
180
- );
181
- }
182
- ```
183
-
184
- ## shadcn/ui
185
-
186
- Install: `npx shadcn@latest init` then `npx shadcn@latest add button input card dialog form`.
187
- Components are copied to `components/ui/` -- NOT an npm dependency, your code to customize.
188
-
189
- ## TypeScript Strict Patterns
190
-
191
- | Pattern | Approach |
192
- |---------|----------|
193
- | API responses | Zod schema + `z.infer<typeof schema>` |
194
- | Props | Explicit interface, no `any` |
195
- | Event handlers | `React.ChangeEvent<HTMLInputElement>` |
196
- | Null safety | `?.` over type assertions, `if (!data) return null` |
197
-
198
- ## Common Mistakes
199
-
200
- | Wrong | Right | Why |
201
- |-------|-------|-----|
202
- | `'use client'` on every component | Default to Server Components | Unnecessary JS bundle size |
203
- | Direct DB from client | Route Handler `/api/*` as BFF | Exposes queries, harder to secure |
204
- | `any` for API responses | Zod schema + infer | No runtime safety |
205
- | Missing loading.tsx | Colocate with page.tsx | Blank page during load |
206
- | `useEffect` for data fetching | React Query `useQuery` | No caching, race conditions |
207
- | Form validation on submit only | Zod resolver + react-hook-form | Delayed error feedback |
208
- | shadcn as npm package | `npx shadcn@latest add` | Copy-paste system, not a dependency |
209
-
210
- ## TypeScript Strict Mode (Required)
211
-
212
- Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
213
- agent-assisted development because agents rely on compiler errors to self-correct.
214
- Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
215
-
216
- ```json
217
- {
218
- "compilerOptions": {
219
- "strict": true,
220
- "noUncheckedIndexedAccess": true
221
- }
222
- }
223
- ```
224
-
225
- Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.
1
+ # Next.js 15 Patterns Standard
2
+
3
+ > **Scope:** nextjs-neon
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** nextjs, react, app router, server component, client component
6
+ > **Load When:** nextjs keywords detected
7
+
8
+ **Verified against:** Next.js 15 + TanStack Query 5 + Zod 4 + react-hook-form 7. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Stack: Next.js 15 + Neon PostgreSQL + .NET Backend
13
+
14
+ ## Core Rules
15
+
16
+ - ALWAYS use App Router (not Pages Router)
17
+ - Default to Server Components -- add `'use client'` only when needed
18
+ - ALWAYS colocate loading.tsx and error.tsx with page.tsx
19
+ - NEVER call the database directly from client -- use Route Handlers as BFF
20
+ - ALWAYS validate inputs with Zod on both client and server
21
+ - Use TypeScript strict mode (`"strict": true`)
22
+
23
+ ## Server vs Client Components
24
+
25
+ | Aspect | Server Component (default) | Client Component (`'use client'`) |
26
+ |--------|---------------------------|-----------------------------------|
27
+ | Renders | Server only | Server SSR + Client hydration |
28
+ | Access to | DB, env vars, fs, async/await | Browser APIs, useState, useEffect, events |
29
+ | Bundle | Not included | Included in JS bundle |
30
+ | Use when | Data fetching, static content | Interactivity, forms, real-time |
31
+
32
+ Decision: Need useState/useEffect/onClick/browser APIs? Client Component. Otherwise Server Component.
33
+
34
+ ## File-Based Routing
35
+
36
+ ```
37
+ app/
38
+ layout.tsx # Root layout
39
+ page.tsx # / (home)
40
+ loading.tsx / error.tsx # Loading UI / Error boundary
41
+ not-found.tsx # 404
42
+ dashboard/
43
+ layout.tsx # Nested layout
44
+ page.tsx # /dashboard
45
+ loading.tsx
46
+ api/documents/
47
+ route.ts # GET/POST /api/documents
48
+ [id]/route.ts # GET/PUT/DELETE /api/documents/:id
49
+ ```
50
+
51
+ ## Layout and Error Boundaries
52
+
53
+ ```tsx
54
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
55
+ return <html lang="en"><body><Providers>{children}</Providers></body></html>;
56
+ }
57
+
58
+ // loading.tsx
59
+ export default function Loading() { return <div className="animate-pulse">Loading...</div>; }
60
+
61
+ // error.tsx — MUST be 'use client'
62
+ 'use client';
63
+ export default function Error({ error, reset }: { error: Error; reset: () => void }) {
64
+ return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
65
+ }
66
+ ```
67
+
68
+ ## Route Handlers (BFF Pattern)
69
+
70
+ ```ts
71
+ // app/api/documents/route.ts — BFF pattern (delegates to .NET API)
72
+ import { auth } from "@/lib/auth/server";
73
+ import { NextResponse } from "next/server";
74
+ import { z } from "zod";
75
+
76
+ export const dynamic = "force-dynamic";
77
+
78
+ const API_URL = process.env.API_URL!; // .NET backend
79
+
80
+ const CreateSchema = z.object({
81
+ title: z.string().min(1).max(200),
82
+ content: z.string().min(1),
83
+ });
84
+
85
+ export async function GET() {
86
+ const { data: session } = await auth.getSession();
87
+ if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
88
+
89
+ const res = await fetch(`${API_URL}/api/documents`, {
90
+ headers: { Authorization: `Bearer ${session.session.access_token}` },
91
+ });
92
+ return NextResponse.json(await res.json());
93
+ }
94
+
95
+ export async function POST(request: Request) {
96
+ const { data: session } = await auth.getSession();
97
+ if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
98
+
99
+ const parsed = CreateSchema.safeParse(await request.json());
100
+ if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
101
+
102
+ const res = await fetch(`${API_URL}/api/documents`, {
103
+ method: "POST",
104
+ headers: { Authorization: `Bearer ${session.session.access_token}`, "Content-Type": "application/json" },
105
+ body: JSON.stringify(parsed.data),
106
+ });
107
+ return NextResponse.json(await res.json(), { status: res.status });
108
+ }
109
+ ```
110
+
111
+ > **Rule:** Route handlers act as BFF (Backend-For-Frontend) — they delegate to the .NET API. Database access lives in the .NET backend via EF Core, never in Next.js route handlers.
112
+
113
+ ## React Query + Neon
114
+
115
+ ```tsx
116
+ // providers/query-provider.tsx
117
+ 'use client';
118
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
119
+ import { useState } from "react";
120
+
121
+ export function QueryProvider({ children }: { children: React.ReactNode }) {
122
+ const [client] = useState(() => new QueryClient({
123
+ defaultOptions: { queries: { staleTime: 60_000, retry: 1 } },
124
+ }));
125
+ return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
126
+ }
127
+ ```
128
+
129
+ ```tsx
130
+ // hooks/use-documents.ts
131
+ export function useDocuments() {
132
+ return useQuery({
133
+ queryKey: ["documents"],
134
+ queryFn: async () => {
135
+ const res = await fetch("/api/documents");
136
+ if (!res.ok) throw new Error("Failed to fetch");
137
+ return res.json();
138
+ },
139
+ });
140
+ }
141
+
142
+ export function useCreateDocument() {
143
+ const qc = useQueryClient();
144
+ return useMutation({
145
+ mutationFn: async (data: { title: string; content: string }) => {
146
+ const res = await fetch("/api/documents", {
147
+ method: "POST", headers: { "Content-Type": "application/json" },
148
+ body: JSON.stringify(data),
149
+ });
150
+ if (!res.ok) throw new Error("Failed to create");
151
+ return res.json();
152
+ },
153
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }),
154
+ });
155
+ }
156
+ ```
157
+
158
+ ## Form Handling (react-hook-form + Zod)
159
+
160
+ ```tsx
161
+ 'use client';
162
+ import { useForm } from "react-hook-form";
163
+ import { zodResolver } from "@hookform/resolvers/zod";
164
+ import { z } from "zod";
165
+
166
+ const schema = z.object({
167
+ title: z.string().min(1, "Required").max(200),
168
+ content: z.string().min(1, "Required"),
169
+ });
170
+
171
+ export function DocumentForm() {
172
+ const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<z.infer<typeof schema>>({
173
+ resolver: zodResolver(schema),
174
+ });
175
+ const create = useCreateDocument();
176
+ return (
177
+ <form onSubmit={handleSubmit((data) => create.mutateAsync(data))}>
178
+ <input {...register("title")} />
179
+ {errors.title && <span>{errors.title.message}</span>}
180
+ <textarea {...register("content")} />
181
+ {errors.content && <span>{errors.content.message}</span>}
182
+ <button type="submit" disabled={isSubmitting}>Save</button>
183
+ </form>
184
+ );
185
+ }
186
+ ```
187
+
188
+ ## shadcn/ui
189
+
190
+ Install: `npx shadcn@latest init` then `npx shadcn@latest add button input card dialog form`.
191
+ Components are copied to `components/ui/` -- NOT an npm dependency, your code to customize.
192
+
193
+ ## TypeScript Strict Patterns
194
+
195
+ | Pattern | Approach |
196
+ |---------|----------|
197
+ | API responses | Zod schema + `z.infer<typeof schema>` |
198
+ | Props | Explicit interface, no `any` |
199
+ | Event handlers | `React.ChangeEvent<HTMLInputElement>` |
200
+ | Null safety | `?.` over type assertions, `if (!data) return null` |
201
+
202
+ ## Common Mistakes
203
+
204
+ | Wrong | Right | Why |
205
+ |-------|-------|-----|
206
+ | `'use client'` on every component | Default to Server Components | Unnecessary JS bundle size |
207
+ | Direct DB from client | Route Handler `/api/*` as BFF | Exposes queries, harder to secure |
208
+ | `any` for API responses | Zod schema + infer | No runtime safety |
209
+ | Missing loading.tsx | Colocate with page.tsx | Blank page during load |
210
+ | `useEffect` for data fetching | React Query `useQuery` | No caching, race conditions |
211
+ | Form validation on submit only | Zod resolver + react-hook-form | Delayed error feedback |
212
+ | shadcn as npm package | `npx shadcn@latest add` | Copy-paste system, not a dependency |
213
+
214
+ ## TypeScript Strict Mode (Required)
215
+
216
+ Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
217
+ agent-assisted development because agents rely on compiler errors to self-correct.
218
+ Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
219
+
220
+ ```json
221
+ {
222
+ "compilerOptions": {
223
+ "strict": true,
224
+ "noUncheckedIndexedAccess": true
225
+ }
226
+ }
227
+ ```
228
+
229
+ Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.