@malamute/ai-rules 1.0.0 → 1.2.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 (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,355 @@
1
+ ---
2
+ paths:
3
+ - "**/auth/**"
4
+ - "**/login/**"
5
+ - "**/api/auth/**"
6
+ - "middleware.ts"
7
+ ---
8
+
9
+ # Next.js Authentication
10
+
11
+ ## NextAuth.js v5 Setup
12
+
13
+ ### Configuration
14
+
15
+ ```typescript
16
+ // auth.ts
17
+ import NextAuth from 'next-auth';
18
+ import Credentials from 'next-auth/providers/credentials';
19
+ import Google from 'next-auth/providers/google';
20
+ import { authConfig } from './auth.config';
21
+
22
+ export const { handlers, auth, signIn, signOut } = NextAuth({
23
+ ...authConfig,
24
+ providers: [
25
+ Google({
26
+ clientId: process.env.GOOGLE_CLIENT_ID!,
27
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
28
+ }),
29
+ Credentials({
30
+ async authorize(credentials) {
31
+ const { email, password } = credentials;
32
+ const user = await verifyCredentials(email, password);
33
+ if (!user) return null;
34
+ return user;
35
+ },
36
+ }),
37
+ ],
38
+ });
39
+ ```
40
+
41
+ ### Auth Config (for Edge)
42
+
43
+ ```typescript
44
+ // auth.config.ts
45
+ import type { NextAuthConfig } from 'next-auth';
46
+
47
+ export const authConfig = {
48
+ pages: {
49
+ signIn: '/login',
50
+ error: '/auth/error',
51
+ },
52
+ callbacks: {
53
+ authorized({ auth, request: { nextUrl } }) {
54
+ const isLoggedIn = !!auth?.user;
55
+ const isProtected = nextUrl.pathname.startsWith('/dashboard');
56
+
57
+ if (isProtected && !isLoggedIn) {
58
+ return Response.redirect(new URL('/login', nextUrl));
59
+ }
60
+ return true;
61
+ },
62
+ jwt({ token, user }) {
63
+ if (user) {
64
+ token.id = user.id;
65
+ token.role = user.role;
66
+ }
67
+ return token;
68
+ },
69
+ session({ session, token }) {
70
+ session.user.id = token.id as string;
71
+ session.user.role = token.role as string;
72
+ return session;
73
+ },
74
+ },
75
+ providers: [], // Configured in auth.ts
76
+ } satisfies NextAuthConfig;
77
+ ```
78
+
79
+ ### Route Handlers
80
+
81
+ ```typescript
82
+ // app/api/auth/[...nextauth]/route.ts
83
+ import { handlers } from '@/auth';
84
+ export const { GET, POST } = handlers;
85
+ ```
86
+
87
+ ## Middleware Protection
88
+
89
+ ```typescript
90
+ // middleware.ts
91
+ import { auth } from './auth';
92
+
93
+ export default auth((req) => {
94
+ const { nextUrl } = req;
95
+ const isLoggedIn = !!req.auth;
96
+
97
+ const publicPaths = ['/login', '/register', '/'];
98
+ const isPublic = publicPaths.includes(nextUrl.pathname);
99
+
100
+ if (!isLoggedIn && !isPublic) {
101
+ return Response.redirect(new URL('/login', nextUrl));
102
+ }
103
+
104
+ // Role-based protection
105
+ if (nextUrl.pathname.startsWith('/admin')) {
106
+ if (req.auth?.user?.role !== 'admin') {
107
+ return Response.redirect(new URL('/forbidden', nextUrl));
108
+ }
109
+ }
110
+ });
111
+
112
+ export const config = {
113
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
114
+ };
115
+ ```
116
+
117
+ ## Server Components
118
+
119
+ ### Get Session
120
+
121
+ ```typescript
122
+ // app/dashboard/page.tsx
123
+ import { auth } from '@/auth';
124
+ import { redirect } from 'next/navigation';
125
+
126
+ export default async function DashboardPage() {
127
+ const session = await auth();
128
+
129
+ if (!session) {
130
+ redirect('/login');
131
+ }
132
+
133
+ return (
134
+ <div>
135
+ <h1>Welcome, {session.user.name}</h1>
136
+ </div>
137
+ );
138
+ }
139
+ ```
140
+
141
+ ### Protected Layout
142
+
143
+ ```typescript
144
+ // app/(protected)/layout.tsx
145
+ import { auth } from '@/auth';
146
+ import { redirect } from 'next/navigation';
147
+
148
+ export default async function ProtectedLayout({
149
+ children,
150
+ }: {
151
+ children: React.ReactNode;
152
+ }) {
153
+ const session = await auth();
154
+
155
+ if (!session) {
156
+ redirect('/login');
157
+ }
158
+
159
+ return <>{children}</>;
160
+ }
161
+ ```
162
+
163
+ ## Client Components
164
+
165
+ ### Sign In/Out
166
+
167
+ ```typescript
168
+ 'use client';
169
+
170
+ import { signIn, signOut } from 'next-auth/react';
171
+
172
+ export function LoginButton() {
173
+ return (
174
+ <button onClick={() => signIn('google', { callbackUrl: '/dashboard' })}>
175
+ Sign in with Google
176
+ </button>
177
+ );
178
+ }
179
+
180
+ export function LogoutButton() {
181
+ return (
182
+ <button onClick={() => signOut({ callbackUrl: '/' })}>
183
+ Sign out
184
+ </button>
185
+ );
186
+ }
187
+ ```
188
+
189
+ ### Use Session Hook
190
+
191
+ ```typescript
192
+ 'use client';
193
+
194
+ import { useSession } from 'next-auth/react';
195
+
196
+ export function UserProfile() {
197
+ const { data: session, status } = useSession();
198
+
199
+ if (status === 'loading') {
200
+ return <Skeleton />;
201
+ }
202
+
203
+ if (!session) {
204
+ return <LoginButton />;
205
+ }
206
+
207
+ return (
208
+ <div>
209
+ <img src={session.user.image} alt={session.user.name} />
210
+ <span>{session.user.name}</span>
211
+ </div>
212
+ );
213
+ }
214
+ ```
215
+
216
+ ## Server Actions
217
+
218
+ ### Login Action
219
+
220
+ ```typescript
221
+ // app/login/actions.ts
222
+ 'use server';
223
+
224
+ import { signIn } from '@/auth';
225
+ import { AuthError } from 'next-auth';
226
+
227
+ export async function loginAction(
228
+ prevState: { error?: string } | undefined,
229
+ formData: FormData
230
+ ) {
231
+ try {
232
+ await signIn('credentials', {
233
+ email: formData.get('email'),
234
+ password: formData.get('password'),
235
+ redirectTo: '/dashboard',
236
+ });
237
+ } catch (error) {
238
+ if (error instanceof AuthError) {
239
+ switch (error.type) {
240
+ case 'CredentialsSignin':
241
+ return { error: 'Invalid credentials' };
242
+ default:
243
+ return { error: 'Something went wrong' };
244
+ }
245
+ }
246
+ throw error;
247
+ }
248
+ }
249
+ ```
250
+
251
+ ### Login Form
252
+
253
+ ```typescript
254
+ 'use client';
255
+
256
+ import { useActionState } from 'react';
257
+ import { loginAction } from './actions';
258
+
259
+ export function LoginForm() {
260
+ const [state, action, isPending] = useActionState(loginAction, undefined);
261
+
262
+ return (
263
+ <form action={action}>
264
+ <input name="email" type="email" required />
265
+ <input name="password" type="password" required />
266
+
267
+ {state?.error && <p className="error">{state.error}</p>}
268
+
269
+ <button type="submit" disabled={isPending}>
270
+ {isPending ? 'Signing in...' : 'Sign in'}
271
+ </button>
272
+ </form>
273
+ );
274
+ }
275
+ ```
276
+
277
+ ## Type Extensions
278
+
279
+ ```typescript
280
+ // types/next-auth.d.ts
281
+ import 'next-auth';
282
+
283
+ declare module 'next-auth' {
284
+ interface User {
285
+ role: string;
286
+ }
287
+
288
+ interface Session {
289
+ user: User & {
290
+ id: string;
291
+ role: string;
292
+ };
293
+ }
294
+ }
295
+
296
+ declare module 'next-auth/jwt' {
297
+ interface JWT {
298
+ id: string;
299
+ role: string;
300
+ }
301
+ }
302
+ ```
303
+
304
+ ## Session Provider
305
+
306
+ ```typescript
307
+ // app/providers.tsx
308
+ 'use client';
309
+
310
+ import { SessionProvider } from 'next-auth/react';
311
+
312
+ export function Providers({ children }: { children: React.ReactNode }) {
313
+ return <SessionProvider>{children}</SessionProvider>;
314
+ }
315
+
316
+ // app/layout.tsx
317
+ import { Providers } from './providers';
318
+
319
+ export default function RootLayout({ children }) {
320
+ return (
321
+ <html>
322
+ <body>
323
+ <Providers>{children}</Providers>
324
+ </body>
325
+ </html>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ## Anti-patterns
331
+
332
+ ```typescript
333
+ // BAD: Checking auth in client component for protection
334
+ 'use client';
335
+ export function ProtectedPage() {
336
+ const { data: session } = useSession();
337
+ if (!session) return <Redirect />; // Too late, page already loaded
338
+ }
339
+
340
+ // GOOD: Check in middleware or server component
341
+ export default async function ProtectedPage() {
342
+ const session = await auth();
343
+ if (!session) redirect('/login');
344
+ }
345
+
346
+ // BAD: Exposing secrets
347
+ const user = await db.user.findUnique({ where: { id } });
348
+ return { ...user, password: user.password }; // Leaking password!
349
+
350
+ // GOOD: Select only needed fields
351
+ const user = await db.user.findUnique({
352
+ where: { id },
353
+ select: { id: true, name: true, email: true },
354
+ });
355
+ ```
@@ -209,3 +209,55 @@ export default function Page() {}
209
209
  - Inline styles (use CSS modules or Tailwind)
210
210
  - Anonymous components
211
211
  - Props spreading without type safety
212
+
213
+ ## Error and Loading UI
214
+
215
+ ### Error Boundary (error.tsx)
216
+
217
+ ```tsx
218
+ 'use client';
219
+
220
+ interface ErrorProps {
221
+ error: Error & { digest?: string };
222
+ reset: () => void;
223
+ }
224
+
225
+ export default function Error({ error, reset }: ErrorProps) {
226
+ return (
227
+ <div className="error-container">
228
+ <h2>Something went wrong!</h2>
229
+ <p>{error.message}</p>
230
+ <button onClick={reset}>Try again</button>
231
+ </div>
232
+ );
233
+ }
234
+ ```
235
+
236
+ ### Loading State (loading.tsx)
237
+
238
+ ```tsx
239
+ export default function Loading() {
240
+ return (
241
+ <div className="loading-container">
242
+ <Spinner />
243
+ <p>Loading...</p>
244
+ </div>
245
+ );
246
+ }
247
+ ```
248
+
249
+ ### Not Found (not-found.tsx)
250
+
251
+ ```tsx
252
+ import Link from 'next/link';
253
+
254
+ export default function NotFound() {
255
+ return (
256
+ <div>
257
+ <h2>Not Found</h2>
258
+ <p>Could not find the requested resource.</p>
259
+ <Link href="/">Return Home</Link>
260
+ </div>
261
+ );
262
+ }
263
+ ```
@@ -0,0 +1,249 @@
1
+ ---
2
+ paths:
3
+ - "app/**/*.tsx"
4
+ - "app/**/*.ts"
5
+ - "**/actions.ts"
6
+ - "**/actions/*.ts"
7
+ ---
8
+
9
+ # Data Fetching & Server Actions
10
+
11
+ ## Server Components - Data Fetching
12
+
13
+ ```tsx
14
+ // app/users/page.tsx
15
+ async function getUsers(): Promise<User[]> {
16
+ const response = await fetch('https://api.example.com/users', {
17
+ cache: 'no-store', // Dynamic - always fresh
18
+ // cache: 'force-cache', // Static (default)
19
+ // next: { revalidate: 60 }, // ISR - revalidate every 60s
20
+ });
21
+
22
+ if (!response.ok) {
23
+ throw new Error('Failed to fetch users');
24
+ }
25
+
26
+ return response.json();
27
+ }
28
+
29
+ export default async function UsersPage() {
30
+ const users = await getUsers();
31
+
32
+ return (
33
+ <ul>
34
+ {users.map((user) => (
35
+ <li key={user.id}>{user.name}</li>
36
+ ))}
37
+ </ul>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ## Cache Options
43
+
44
+ | Option | Behavior |
45
+ |--------|----------|
46
+ | `cache: 'force-cache'` | Static, cached indefinitely (default) |
47
+ | `cache: 'no-store'` | Dynamic, never cached |
48
+ | `next: { revalidate: N }` | ISR, revalidate every N seconds |
49
+ | `next: { tags: ['users'] }` | Tagged for on-demand revalidation |
50
+
51
+ ## Server Actions
52
+
53
+ ```tsx
54
+ // app/users/actions.ts
55
+ 'use server';
56
+
57
+ import { revalidatePath, revalidateTag } from 'next/cache';
58
+ import { redirect } from 'next/navigation';
59
+ import { z } from 'zod';
60
+
61
+ const CreateUserSchema = z.object({
62
+ name: z.string().min(1),
63
+ email: z.string().email(),
64
+ });
65
+
66
+ export async function createUser(formData: FormData) {
67
+ // Validate
68
+ const parsed = CreateUserSchema.safeParse({
69
+ name: formData.get('name'),
70
+ email: formData.get('email'),
71
+ });
72
+
73
+ if (!parsed.success) {
74
+ return { error: 'Invalid data' };
75
+ }
76
+
77
+ // Create
78
+ await db.user.create({ data: parsed.data });
79
+
80
+ // Revalidate and redirect
81
+ revalidatePath('/users');
82
+ redirect('/users');
83
+ }
84
+ ```
85
+
86
+ ## Form with Server Action
87
+
88
+ ```tsx
89
+ // app/users/_components/user-form.tsx
90
+ 'use client';
91
+
92
+ import { useActionState } from 'react';
93
+ import { createUser } from '../actions';
94
+
95
+ const initialState = { error: null };
96
+
97
+ export function UserForm() {
98
+ const [state, formAction, isPending] = useActionState(
99
+ createUser,
100
+ initialState
101
+ );
102
+
103
+ return (
104
+ <form action={formAction}>
105
+ <input name="name" required disabled={isPending} />
106
+ <input name="email" type="email" required disabled={isPending} />
107
+
108
+ {state.error && <p className="error">{state.error}</p>}
109
+
110
+ <button type="submit" disabled={isPending}>
111
+ {isPending ? 'Creating...' : 'Create User'}
112
+ </button>
113
+ </form>
114
+ );
115
+ }
116
+ ```
117
+
118
+ ## Optimistic Updates
119
+
120
+ ```tsx
121
+ 'use client';
122
+
123
+ import { useOptimistic } from 'react';
124
+ import { addItem } from './actions';
125
+
126
+ export function ItemList({ items }: { items: Item[] }) {
127
+ const [optimisticItems, addOptimisticItem] = useOptimistic(
128
+ items,
129
+ (state, newItem: Item) => [...state, newItem]
130
+ );
131
+
132
+ async function handleAdd(formData: FormData) {
133
+ const newItem = {
134
+ id: crypto.randomUUID(),
135
+ name: formData.get('name') as string,
136
+ pending: true,
137
+ };
138
+
139
+ addOptimisticItem(newItem);
140
+ await addItem(formData);
141
+ }
142
+
143
+ return (
144
+ <>
145
+ <form action={handleAdd}>
146
+ <input name="name" />
147
+ <button type="submit">Add</button>
148
+ </form>
149
+
150
+ <ul>
151
+ {optimisticItems.map((item) => (
152
+ <li key={item.id} style={{ opacity: item.pending ? 0.5 : 1 }}>
153
+ {item.name}
154
+ </li>
155
+ ))}
156
+ </ul>
157
+ </>
158
+ );
159
+ }
160
+ ```
161
+
162
+ ## Parallel Data Fetching
163
+
164
+ ```tsx
165
+ // Parallel - faster
166
+ export default async function Page() {
167
+ const [users, products] = await Promise.all([
168
+ getUsers(),
169
+ getProducts(),
170
+ ]);
171
+
172
+ return (/* ... */);
173
+ }
174
+
175
+ // Sequential - slower, avoid
176
+ export default async function Page() {
177
+ const users = await getUsers();
178
+ const products = await getProducts(); // Waits for users
179
+ return (/* ... */);
180
+ }
181
+ ```
182
+
183
+ ## Data Fetching with Suspense
184
+
185
+ ```tsx
186
+ import { Suspense } from 'react';
187
+
188
+ export default function Page() {
189
+ return (
190
+ <div>
191
+ <h1>Dashboard</h1>
192
+
193
+ <Suspense fallback={<UsersSkeleton />}>
194
+ <UsersSection />
195
+ </Suspense>
196
+
197
+ <Suspense fallback={<ProductsSkeleton />}>
198
+ <ProductsSection />
199
+ </Suspense>
200
+ </div>
201
+ );
202
+ }
203
+
204
+ async function UsersSection() {
205
+ const users = await getUsers();
206
+ return <UserList users={users} />;
207
+ }
208
+ ```
209
+
210
+ ## Revalidation Patterns
211
+
212
+ ```tsx
213
+ 'use server';
214
+
215
+ // Path-based
216
+ revalidatePath('/users'); // Revalidate specific path
217
+ revalidatePath('/users', 'layout'); // Revalidate layout and children
218
+
219
+ // Tag-based
220
+ revalidateTag('users'); // Revalidate all with this tag
221
+
222
+ // Usage with tags
223
+ await fetch('/api/users', {
224
+ next: { tags: ['users'] }
225
+ });
226
+ ```
227
+
228
+ ## Error Handling in Server Actions
229
+
230
+ ```tsx
231
+ 'use server';
232
+
233
+ export async function createUser(formData: FormData) {
234
+ try {
235
+ const user = await db.user.create({
236
+ data: { name: formData.get('name') as string }
237
+ });
238
+
239
+ revalidatePath('/users');
240
+ return { success: true, user };
241
+ } catch (error) {
242
+ // Log for debugging
243
+ console.error('Failed to create user:', error);
244
+
245
+ // Return user-friendly error
246
+ return { success: false, error: 'Failed to create user' };
247
+ }
248
+ }
249
+ ```