@malamute/ai-rules 1.0.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 (46) hide show
  1. package/README.md +174 -0
  2. package/bin/cli.js +5 -0
  3. package/configs/_shared/.claude/commands/fix-issue.md +38 -0
  4. package/configs/_shared/.claude/commands/generate-tests.md +49 -0
  5. package/configs/_shared/.claude/commands/review-pr.md +77 -0
  6. package/configs/_shared/.claude/rules/accessibility.md +270 -0
  7. package/configs/_shared/.claude/rules/performance.md +226 -0
  8. package/configs/_shared/.claude/rules/security.md +188 -0
  9. package/configs/_shared/.claude/skills/debug/SKILL.md +118 -0
  10. package/configs/_shared/.claude/skills/learning/SKILL.md +224 -0
  11. package/configs/_shared/.claude/skills/review/SKILL.md +86 -0
  12. package/configs/_shared/.claude/skills/spec/SKILL.md +112 -0
  13. package/configs/_shared/CLAUDE.md +174 -0
  14. package/configs/angular/.claude/rules/components.md +257 -0
  15. package/configs/angular/.claude/rules/state.md +250 -0
  16. package/configs/angular/.claude/rules/testing.md +422 -0
  17. package/configs/angular/.claude/settings.json +31 -0
  18. package/configs/angular/CLAUDE.md +251 -0
  19. package/configs/dotnet/.claude/rules/api.md +370 -0
  20. package/configs/dotnet/.claude/rules/architecture.md +199 -0
  21. package/configs/dotnet/.claude/rules/database/efcore.md +408 -0
  22. package/configs/dotnet/.claude/rules/testing.md +389 -0
  23. package/configs/dotnet/.claude/settings.json +9 -0
  24. package/configs/dotnet/CLAUDE.md +319 -0
  25. package/configs/nestjs/.claude/rules/auth.md +321 -0
  26. package/configs/nestjs/.claude/rules/database/prisma.md +305 -0
  27. package/configs/nestjs/.claude/rules/database/typeorm.md +379 -0
  28. package/configs/nestjs/.claude/rules/modules.md +215 -0
  29. package/configs/nestjs/.claude/rules/testing.md +315 -0
  30. package/configs/nestjs/.claude/rules/validation.md +279 -0
  31. package/configs/nestjs/.claude/settings.json +15 -0
  32. package/configs/nestjs/CLAUDE.md +263 -0
  33. package/configs/nextjs/.claude/rules/components.md +211 -0
  34. package/configs/nextjs/.claude/rules/state/redux-toolkit.md +429 -0
  35. package/configs/nextjs/.claude/rules/state/zustand.md +299 -0
  36. package/configs/nextjs/.claude/rules/testing.md +315 -0
  37. package/configs/nextjs/.claude/settings.json +29 -0
  38. package/configs/nextjs/CLAUDE.md +376 -0
  39. package/configs/python/.claude/rules/database/sqlalchemy.md +355 -0
  40. package/configs/python/.claude/rules/fastapi.md +272 -0
  41. package/configs/python/.claude/rules/flask.md +332 -0
  42. package/configs/python/.claude/rules/testing.md +374 -0
  43. package/configs/python/.claude/settings.json +18 -0
  44. package/configs/python/CLAUDE.md +273 -0
  45. package/package.json +41 -0
  46. package/src/install.js +315 -0
@@ -0,0 +1,315 @@
1
+ ---
2
+ paths:
3
+ - "**/*.test.tsx"
4
+ - "**/*.test.ts"
5
+ - "**/*.spec.tsx"
6
+ - "**/*.spec.ts"
7
+ - "**/*.e2e.ts"
8
+ ---
9
+
10
+ # Testing Guidelines (Next.js)
11
+
12
+ ## Framework
13
+
14
+ - **Vitest** or **Jest** for unit/integration tests
15
+ - **React Testing Library** for component tests
16
+ - **Playwright** for E2E tests
17
+
18
+ ## Test File Structure
19
+
20
+ ```
21
+ component.tsx
22
+ component.test.tsx # Co-located tests
23
+
24
+ # or
25
+ __tests__/
26
+ component.test.tsx
27
+ ```
28
+
29
+ ## Component Testing
30
+
31
+ ### Basic Component Test
32
+
33
+ ```tsx
34
+ import { render, screen } from '@testing-library/react';
35
+ import userEvent from '@testing-library/user-event';
36
+ import { UserCard } from './user-card';
37
+
38
+ describe('UserCard', () => {
39
+ const mockUser = { id: '1', name: 'John Doe', email: 'john@example.com' };
40
+
41
+ it('should render user information', () => {
42
+ render(<UserCard user={mockUser} onSelect={vi.fn()} />);
43
+
44
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
45
+ expect(screen.getByText('john@example.com')).toBeInTheDocument();
46
+ });
47
+
48
+ it('should call onSelect when clicked', async () => {
49
+ const handleSelect = vi.fn();
50
+ const user = userEvent.setup();
51
+
52
+ render(<UserCard user={mockUser} onSelect={handleSelect} />);
53
+
54
+ await user.click(screen.getByRole('button'));
55
+
56
+ expect(handleSelect).toHaveBeenCalledWith(mockUser);
57
+ });
58
+ });
59
+ ```
60
+
61
+ ### Testing Async Components
62
+
63
+ ```tsx
64
+ import { render, screen } from '@testing-library/react';
65
+
66
+ // Mock the fetch/data function
67
+ vi.mock('@/lib/api', () => ({
68
+ getUsers: vi.fn().mockResolvedValue([
69
+ { id: '1', name: 'John' },
70
+ { id: '2', name: 'Jane' },
71
+ ]),
72
+ }));
73
+
74
+ describe('UsersPage', () => {
75
+ it('should render users', async () => {
76
+ // For async Server Components, render and await
77
+ const Page = await import('./page');
78
+ render(await Page.default());
79
+
80
+ expect(screen.getByText('John')).toBeInTheDocument();
81
+ expect(screen.getByText('Jane')).toBeInTheDocument();
82
+ });
83
+ });
84
+ ```
85
+
86
+ ### Testing with Providers
87
+
88
+ ```tsx
89
+ import { render } from '@testing-library/react';
90
+
91
+ function renderWithProviders(ui: React.ReactElement) {
92
+ return render(
93
+ <QueryClientProvider client={queryClient}>
94
+ <ThemeProvider>
95
+ {ui}
96
+ </ThemeProvider>
97
+ </QueryClientProvider>
98
+ );
99
+ }
100
+
101
+ it('should render with providers', () => {
102
+ renderWithProviders(<MyComponent />);
103
+ });
104
+ ```
105
+
106
+ ## Server Actions Testing
107
+
108
+ ```tsx
109
+ import { createUser } from './actions';
110
+
111
+ describe('createUser', () => {
112
+ it('should create a user', async () => {
113
+ const formData = new FormData();
114
+ formData.set('name', 'John Doe');
115
+ formData.set('email', 'john@example.com');
116
+
117
+ const result = await createUser(formData);
118
+
119
+ expect(result.success).toBe(true);
120
+ });
121
+
122
+ it('should validate input', async () => {
123
+ const formData = new FormData();
124
+ formData.set('name', ''); // Invalid
125
+
126
+ const result = await createUser(formData);
127
+
128
+ expect(result.error).toBeDefined();
129
+ });
130
+ });
131
+ ```
132
+
133
+ ## Hook Testing
134
+
135
+ ```tsx
136
+ import { renderHook, act } from '@testing-library/react';
137
+ import { useCounter } from './use-counter';
138
+
139
+ describe('useCounter', () => {
140
+ it('should increment counter', () => {
141
+ const { result } = renderHook(() => useCounter());
142
+
143
+ expect(result.current.count).toBe(0);
144
+
145
+ act(() => {
146
+ result.current.increment();
147
+ });
148
+
149
+ expect(result.current.count).toBe(1);
150
+ });
151
+ });
152
+ ```
153
+
154
+ ## E2E Testing with Playwright
155
+
156
+ ### Setup
157
+
158
+ ```typescript
159
+ // playwright.config.ts
160
+ import { defineConfig } from '@playwright/test';
161
+
162
+ export default defineConfig({
163
+ testDir: './e2e',
164
+ use: {
165
+ baseURL: 'http://localhost:3000',
166
+ trace: 'on-first-retry',
167
+ },
168
+ webServer: {
169
+ command: 'nx serve app-name',
170
+ url: 'http://localhost:3000',
171
+ reuseExistingServer: !process.env.CI,
172
+ },
173
+ });
174
+ ```
175
+
176
+ ### E2E Test Example
177
+
178
+ ```typescript
179
+ // e2e/users.e2e.ts
180
+ import { test, expect } from '@playwright/test';
181
+
182
+ test.describe('Users Page', () => {
183
+ test.beforeEach(async ({ page }) => {
184
+ await page.goto('/users');
185
+ });
186
+
187
+ test('should display user list', async ({ page }) => {
188
+ await expect(page.getByRole('heading', { name: 'Users' })).toBeVisible();
189
+ await expect(page.getByTestId('user-list')).toBeVisible();
190
+ });
191
+
192
+ test('should create new user', async ({ page }) => {
193
+ await page.getByRole('button', { name: 'Add User' }).click();
194
+
195
+ await page.getByLabel('Name').fill('John Doe');
196
+ await page.getByLabel('Email').fill('john@example.com');
197
+ await page.getByRole('button', { name: 'Submit' }).click();
198
+
199
+ await expect(page.getByText('John Doe')).toBeVisible();
200
+ });
201
+
202
+ test('should filter users', async ({ page }) => {
203
+ await page.getByPlaceholder('Search...').fill('John');
204
+
205
+ await expect(page.getByTestId('user-card')).toHaveCount(1);
206
+ });
207
+ });
208
+ ```
209
+
210
+ ### Page Object Pattern
211
+
212
+ ```typescript
213
+ // e2e/pages/users.page.ts
214
+ import { Page, Locator } from '@playwright/test';
215
+
216
+ export class UsersPage {
217
+ readonly page: Page;
218
+ readonly heading: Locator;
219
+ readonly userList: Locator;
220
+ readonly searchInput: Locator;
221
+ readonly addButton: Locator;
222
+
223
+ constructor(page: Page) {
224
+ this.page = page;
225
+ this.heading = page.getByRole('heading', { name: 'Users' });
226
+ this.userList = page.getByTestId('user-list');
227
+ this.searchInput = page.getByPlaceholder('Search...');
228
+ this.addButton = page.getByRole('button', { name: 'Add User' });
229
+ }
230
+
231
+ async goto(): Promise<void> {
232
+ await this.page.goto('/users');
233
+ }
234
+
235
+ async search(query: string): Promise<void> {
236
+ await this.searchInput.fill(query);
237
+ }
238
+
239
+ async addUser(name: string, email: string): Promise<void> {
240
+ await this.addButton.click();
241
+ await this.page.getByLabel('Name').fill(name);
242
+ await this.page.getByLabel('Email').fill(email);
243
+ await this.page.getByRole('button', { name: 'Submit' }).click();
244
+ }
245
+ }
246
+ ```
247
+
248
+ ## Mocking
249
+
250
+ ### Mocking Modules
251
+
252
+ ```tsx
253
+ // Mock entire module
254
+ vi.mock('@/lib/api', () => ({
255
+ getUsers: vi.fn(),
256
+ createUser: vi.fn(),
257
+ }));
258
+
259
+ // Mock with implementation
260
+ import { getUsers } from '@/lib/api';
261
+
262
+ vi.mocked(getUsers).mockResolvedValue([
263
+ { id: '1', name: 'John' },
264
+ ]);
265
+ ```
266
+
267
+ ### Mocking Next.js
268
+
269
+ ```tsx
270
+ // Mock next/navigation
271
+ vi.mock('next/navigation', () => ({
272
+ useRouter: () => ({
273
+ push: vi.fn(),
274
+ replace: vi.fn(),
275
+ back: vi.fn(),
276
+ }),
277
+ usePathname: () => '/users',
278
+ useSearchParams: () => new URLSearchParams(),
279
+ }));
280
+
281
+ // Mock next/headers
282
+ vi.mock('next/headers', () => ({
283
+ cookies: () => ({
284
+ get: vi.fn(),
285
+ set: vi.fn(),
286
+ }),
287
+ headers: () => new Headers(),
288
+ }));
289
+ ```
290
+
291
+ ## Commands
292
+
293
+ ```bash
294
+ # Run tests
295
+ nx test [project]
296
+
297
+ # Run with coverage
298
+ nx test [project] --coverage
299
+
300
+ # Run E2E
301
+ nx e2e [project]-e2e
302
+
303
+ # Run E2E with UI
304
+ nx e2e [project]-e2e --ui
305
+
306
+ # Run specific test
307
+ nx test [project] --testFile=user-card.test.tsx
308
+ ```
309
+
310
+ ## Coverage Expectations
311
+
312
+ - >80% coverage on business logic
313
+ - Test all Server Actions
314
+ - Test user interactions
315
+ - E2E for critical user flows
@@ -0,0 +1,29 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(nx serve *)",
5
+ "Bash(nx dev *)",
6
+ "Bash(nx build *)",
7
+ "Bash(nx test *)",
8
+ "Bash(nx lint *)",
9
+ "Bash(nx run-many *)",
10
+ "Bash(nx affected *)",
11
+ "Bash(nx g *)",
12
+ "Bash(nx generate *)",
13
+ "Bash(npm run *)",
14
+ "Bash(npm install *)",
15
+ "Bash(npm ci)",
16
+ "Bash(npx nx *)",
17
+ "Read",
18
+ "Edit",
19
+ "Write"
20
+ ],
21
+ "deny": [
22
+ "Bash(rm -rf *)",
23
+ "Bash(nx reset)",
24
+ "Read(.env)",
25
+ "Read(.env.*)",
26
+ "Read(**/secrets/**)"
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,376 @@
1
+ # Next.js Project Guidelines
2
+
3
+ @../_shared/CLAUDE.md
4
+
5
+ ## Stack
6
+
7
+ - Next.js 15+ (App Router)
8
+ - React 19+
9
+ - TypeScript strict mode
10
+ - Nx monorepo
11
+
12
+ ## Architecture - Nx Structure
13
+
14
+ ```
15
+ apps/
16
+ [app-name]/
17
+ app/ # App Router
18
+ (routes)/ # Route groups
19
+ users/
20
+ page.tsx
21
+ _components/ # Private (co-located)
22
+ user-list.tsx
23
+ products/
24
+ page.tsx
25
+ _components/
26
+ layout.tsx
27
+ error.tsx
28
+ loading.tsx
29
+
30
+ libs/
31
+ [domain]/
32
+ feature/ # Feature-specific logic
33
+ ui/ # Presentational components
34
+ data-access/ # API calls, server actions
35
+ util/ # Helpers
36
+
37
+ shared/
38
+ ui/ # Design system components
39
+ util/ # Shared utilities
40
+ ```
41
+
42
+ ### Folder Conventions
43
+
44
+ | Pattern | Meaning |
45
+ |---------|---------|
46
+ | `_folder/` | Private - not a route, co-located components |
47
+ | `(folder)/` | Route group - organizational, not in URL |
48
+ | `[param]/` | Dynamic segment |
49
+ | `[...param]/` | Catch-all segment |
50
+ | `[[...param]]/` | Optional catch-all |
51
+
52
+ ## React 19 / Next.js 15 - Core Principles
53
+
54
+ ### Server Components by Default
55
+
56
+ Components are Server Components unless marked with `'use client'`.
57
+
58
+ ```tsx
59
+ // Server Component (default) - runs on server
60
+ // Can: fetch data, access DB, use secrets
61
+ // Cannot: use hooks, browser APIs, event handlers
62
+ export default async function UsersPage() {
63
+ const users = await fetchUsers();
64
+ return <UserList users={users} />;
65
+ }
66
+ ```
67
+
68
+ ### Client Components
69
+
70
+ Add `'use client'` directive for interactivity:
71
+
72
+ ```tsx
73
+ 'use client';
74
+
75
+ // Client Component - runs in browser
76
+ // Can: use hooks, event handlers, browser APIs
77
+ // Cannot: directly access DB, use secrets
78
+ import { useState } from 'react';
79
+
80
+ export function SearchInput({ onSearch }: Props) {
81
+ const [query, setQuery] = useState('');
82
+
83
+ return (
84
+ <input
85
+ value={query}
86
+ onChange={(event) => setQuery(event.target.value)}
87
+ onKeyDown={(event) => event.key === 'Enter' && onSearch(query)}
88
+ />
89
+ );
90
+ }
91
+ ```
92
+
93
+ ### When to Use Client Components
94
+
95
+ | Use Client Component | Use Server Component |
96
+ |---------------------|---------------------|
97
+ | `useState`, `useEffect`, hooks | Data fetching |
98
+ | Event handlers (`onClick`, etc.) | Database access |
99
+ | Browser APIs | Sensitive operations |
100
+ | Interactive UI (forms, modals) | Static content |
101
+
102
+ ## Data Fetching
103
+
104
+ ### Server Components (Preferred)
105
+
106
+ ```tsx
107
+ // app/users/page.tsx
108
+ async function getUsers(): Promise<User[]> {
109
+ const response = await fetch('https://api.example.com/users', {
110
+ cache: 'no-store', // Dynamic data
111
+ // cache: 'force-cache', // Static data (default)
112
+ // next: { revalidate: 60 }, // ISR - revalidate every 60s
113
+ });
114
+ return response.json();
115
+ }
116
+
117
+ export default async function UsersPage() {
118
+ const users = await getUsers();
119
+
120
+ return (
121
+ <ul>
122
+ {users.map((user) => (
123
+ <li key={user.id}>{user.name}</li>
124
+ ))}
125
+ </ul>
126
+ );
127
+ }
128
+ ```
129
+
130
+ ### Server Actions (Mutations)
131
+
132
+ ```tsx
133
+ // app/users/actions.ts
134
+ 'use server';
135
+
136
+ import { revalidatePath } from 'next/cache';
137
+
138
+ export async function createUser(formData: FormData): Promise<void> {
139
+ const name = formData.get('name') as string;
140
+
141
+ await db.user.create({ data: { name } });
142
+
143
+ revalidatePath('/users');
144
+ }
145
+ ```
146
+
147
+ ```tsx
148
+ // app/users/_components/user-form.tsx
149
+ 'use client';
150
+
151
+ import { createUser } from '../actions';
152
+
153
+ export function UserForm() {
154
+ return (
155
+ <form action={createUser}>
156
+ <input name="name" required />
157
+ <button type="submit">Create User</button>
158
+ </form>
159
+ );
160
+ }
161
+ ```
162
+
163
+ ### React 19 Hooks for Data
164
+
165
+ ```tsx
166
+ 'use client';
167
+
168
+ import { useActionState, useOptimistic } from 'react';
169
+
170
+ // useActionState - form submission state
171
+ const [state, formAction, isPending] = useActionState(createUser, initialState);
172
+
173
+ // useOptimistic - optimistic UI updates
174
+ const [optimisticItems, addOptimistic] = useOptimistic(
175
+ items,
176
+ (state, newItem) => [...state, newItem]
177
+ );
178
+ ```
179
+
180
+ ## Component Patterns
181
+
182
+ ### Page Components (Server)
183
+
184
+ ```tsx
185
+ // app/users/page.tsx
186
+ import { UserList } from './_components/user-list';
187
+ import { getUsers } from '@/lib/api';
188
+
189
+ export default async function UsersPage() {
190
+ const users = await getUsers();
191
+
192
+ return (
193
+ <main>
194
+ <h1>Users</h1>
195
+ <UserList users={users} />
196
+ </main>
197
+ );
198
+ }
199
+ ```
200
+
201
+ ### Interactive Components (Client)
202
+
203
+ ```tsx
204
+ // app/users/_components/user-list.tsx
205
+ 'use client';
206
+
207
+ import { useState } from 'react';
208
+ import { UserCard } from './user-card';
209
+
210
+ interface UserListProps {
211
+ users: User[];
212
+ }
213
+
214
+ export function UserList({ users }: UserListProps) {
215
+ const [selectedId, setSelectedId] = useState<string | null>(null);
216
+
217
+ return (
218
+ <ul>
219
+ {users.map((user) => (
220
+ <UserCard
221
+ key={user.id}
222
+ user={user}
223
+ isSelected={user.id === selectedId}
224
+ onSelect={() => setSelectedId(user.id)}
225
+ />
226
+ ))}
227
+ </ul>
228
+ );
229
+ }
230
+ ```
231
+
232
+ ### Shared UI Components
233
+
234
+ ```tsx
235
+ // libs/shared/ui/button.tsx
236
+ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
237
+ variant?: 'primary' | 'secondary';
238
+ isLoading?: boolean;
239
+ }
240
+
241
+ export function Button({
242
+ variant = 'primary',
243
+ isLoading,
244
+ children,
245
+ disabled,
246
+ ...props
247
+ }: ButtonProps) {
248
+ return (
249
+ <button
250
+ className={`btn btn-${variant}`}
251
+ disabled={disabled || isLoading}
252
+ {...props}
253
+ >
254
+ {isLoading ? 'Loading...' : children}
255
+ </button>
256
+ );
257
+ }
258
+ ```
259
+
260
+ ## Build & Commands
261
+
262
+ ```bash
263
+ # Development
264
+ nx serve [app-name]
265
+ nx dev [app-name]
266
+
267
+ # Build
268
+ nx build [app-name]
269
+ nx build [app-name] --configuration=production
270
+
271
+ # Test
272
+ nx test [lib-name]
273
+ nx run-many -t test
274
+ nx affected -t test
275
+
276
+ # Lint
277
+ nx lint [project-name]
278
+ nx run-many -t lint
279
+
280
+ # Generate
281
+ nx g @nx/next:component [name] --project=[app]
282
+ nx g @nx/react:component [name] --project=[lib]
283
+ nx g @nx/next:page [name] --project=[app]
284
+ ```
285
+
286
+ ## Code Style
287
+
288
+ ### Component Files
289
+
290
+ - One component per file
291
+ - Named exports (not default) for reusable components
292
+ - Default export only for pages (`page.tsx`)
293
+ - Props interface above component
294
+
295
+ ```tsx
296
+ // Good
297
+ interface UserCardProps {
298
+ user: User;
299
+ onSelect: () => void;
300
+ }
301
+
302
+ export function UserCard({ user, onSelect }: UserCardProps) {
303
+ return (/* ... */);
304
+ }
305
+ ```
306
+
307
+ ### Naming Conventions
308
+
309
+ | Element | Convention | Example |
310
+ |---------|------------|---------|
311
+ | Components | PascalCase | `UserCard` |
312
+ | Files | kebab-case | `user-card.tsx` |
313
+ | Hooks | camelCase with `use` | `useUserData` |
314
+ | Server Actions | camelCase | `createUser` |
315
+ | Route folders | kebab-case | `user-profile/` |
316
+
317
+ ### Imports Order
318
+
319
+ ```tsx
320
+ // 1. React/Next
321
+ import { useState } from 'react';
322
+ import { useRouter } from 'next/navigation';
323
+
324
+ // 2. External libraries
325
+ import { z } from 'zod';
326
+
327
+ // 3. Internal libs (@/)
328
+ import { Button } from '@/components/ui/button';
329
+ import { getUsers } from '@/lib/api';
330
+
331
+ // 4. Relative imports
332
+ import { UserCard } from './user-card';
333
+
334
+ // 5. Types
335
+ import type { User } from '@/types';
336
+ ```
337
+
338
+ ## Error Handling
339
+
340
+ ### Error Boundaries
341
+
342
+ ```tsx
343
+ // app/users/error.tsx
344
+ 'use client';
345
+
346
+ interface ErrorProps {
347
+ error: Error & { digest?: string };
348
+ reset: () => void;
349
+ }
350
+
351
+ export default function Error({ error, reset }: ErrorProps) {
352
+ return (
353
+ <div>
354
+ <h2>Something went wrong!</h2>
355
+ <button onClick={reset}>Try again</button>
356
+ </div>
357
+ );
358
+ }
359
+ ```
360
+
361
+ ### Loading States
362
+
363
+ ```tsx
364
+ // app/users/loading.tsx
365
+ export default function Loading() {
366
+ return <div>Loading...</div>;
367
+ }
368
+ ```
369
+
370
+ ## Performance
371
+
372
+ - Use Server Components for data-heavy UI
373
+ - Lazy load heavy Client Components with `next/dynamic`
374
+ - Use `<Image>` from `next/image` for optimized images
375
+ - Use `<Link>` from `next/link` for client-side navigation
376
+ - Avoid `useEffect` for data fetching - use Server Components or `use()`