@malamute/ai-rules 1.0.0 → 1.3.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 (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -0,0 +1,358 @@
1
+ ---
2
+ paths:
3
+ - "app/api/**/*.ts"
4
+ - "src/app/api/**/*.ts"
5
+ ---
6
+
7
+ # Next.js API Routes (App Router)
8
+
9
+ ## Basic Route Handler
10
+
11
+ ```typescript
12
+ // app/api/users/route.ts
13
+ import { NextResponse } from 'next/server';
14
+
15
+ export async function GET() {
16
+ const users = await db.user.findMany();
17
+ return NextResponse.json(users);
18
+ }
19
+
20
+ export async function POST(request: Request) {
21
+ const body = await request.json();
22
+ const user = await db.user.create({ data: body });
23
+ return NextResponse.json(user, { status: 201 });
24
+ }
25
+ ```
26
+
27
+ ## Dynamic Routes
28
+
29
+ ```typescript
30
+ // app/api/users/[id]/route.ts
31
+ import { NextResponse } from 'next/server';
32
+
33
+ export async function GET(
34
+ request: Request,
35
+ { params }: { params: Promise<{ id: string }> }
36
+ ) {
37
+ const { id } = await params;
38
+ const user = await db.user.findUnique({ where: { id } });
39
+
40
+ if (!user) {
41
+ return NextResponse.json(
42
+ { error: 'User not found' },
43
+ { status: 404 }
44
+ );
45
+ }
46
+
47
+ return NextResponse.json(user);
48
+ }
49
+
50
+ export async function PUT(
51
+ request: Request,
52
+ { params }: { params: Promise<{ id: string }> }
53
+ ) {
54
+ const { id } = await params;
55
+ const body = await request.json();
56
+
57
+ const user = await db.user.update({
58
+ where: { id },
59
+ data: body,
60
+ });
61
+
62
+ return NextResponse.json(user);
63
+ }
64
+
65
+ export async function DELETE(
66
+ request: Request,
67
+ { params }: { params: Promise<{ id: string }> }
68
+ ) {
69
+ const { id } = await params;
70
+ await db.user.delete({ where: { id } });
71
+ return new NextResponse(null, { status: 204 });
72
+ }
73
+ ```
74
+
75
+ ## Request Handling
76
+
77
+ ### Query Parameters
78
+
79
+ ```typescript
80
+ export async function GET(request: Request) {
81
+ const { searchParams } = new URL(request.url);
82
+ const page = parseInt(searchParams.get('page') || '1');
83
+ const limit = parseInt(searchParams.get('limit') || '10');
84
+ const search = searchParams.get('search') || '';
85
+
86
+ const users = await db.user.findMany({
87
+ where: { name: { contains: search } },
88
+ skip: (page - 1) * limit,
89
+ take: limit,
90
+ });
91
+
92
+ const total = await db.user.count({
93
+ where: { name: { contains: search } },
94
+ });
95
+
96
+ return NextResponse.json({
97
+ data: users,
98
+ meta: {
99
+ page,
100
+ limit,
101
+ total,
102
+ totalPages: Math.ceil(total / limit),
103
+ },
104
+ });
105
+ }
106
+ ```
107
+
108
+ ### Headers & Cookies
109
+
110
+ ```typescript
111
+ import { cookies, headers } from 'next/headers';
112
+
113
+ export async function GET() {
114
+ const headersList = await headers();
115
+ const authHeader = headersList.get('authorization');
116
+
117
+ const cookieStore = await cookies();
118
+ const token = cookieStore.get('token');
119
+
120
+ return NextResponse.json({ auth: !!authHeader, hasToken: !!token });
121
+ }
122
+
123
+ export async function POST() {
124
+ const response = NextResponse.json({ success: true });
125
+
126
+ // Set cookie
127
+ response.cookies.set('session', 'value', {
128
+ httpOnly: true,
129
+ secure: process.env.NODE_ENV === 'production',
130
+ sameSite: 'lax',
131
+ maxAge: 60 * 60 * 24 * 7, // 1 week
132
+ });
133
+
134
+ return response;
135
+ }
136
+ ```
137
+
138
+ ## Validation with Zod
139
+
140
+ ```typescript
141
+ // app/api/users/route.ts
142
+ import { NextResponse } from 'next/server';
143
+ import { z } from 'zod';
144
+
145
+ const createUserSchema = z.object({
146
+ email: z.string().email(),
147
+ name: z.string().min(2).max(100),
148
+ age: z.number().min(18).optional(),
149
+ });
150
+
151
+ export async function POST(request: Request) {
152
+ const body = await request.json();
153
+
154
+ const result = createUserSchema.safeParse(body);
155
+
156
+ if (!result.success) {
157
+ return NextResponse.json(
158
+ {
159
+ type: 'validation_error',
160
+ title: 'Validation Error',
161
+ status: 400,
162
+ errors: result.error.issues.map(issue => ({
163
+ field: issue.path.join('.'),
164
+ message: issue.message,
165
+ })),
166
+ },
167
+ { status: 400 }
168
+ );
169
+ }
170
+
171
+ const user = await db.user.create({ data: result.data });
172
+ return NextResponse.json(user, { status: 201 });
173
+ }
174
+ ```
175
+
176
+ ## Error Handling
177
+
178
+ ```typescript
179
+ // lib/api-error.ts
180
+ export class ApiError extends Error {
181
+ constructor(
182
+ public status: number,
183
+ message: string,
184
+ public code?: string
185
+ ) {
186
+ super(message);
187
+ }
188
+ }
189
+
190
+ // app/api/users/[id]/route.ts
191
+ import { ApiError } from '@/lib/api-error';
192
+
193
+ export async function GET(
194
+ request: Request,
195
+ { params }: { params: Promise<{ id: string }> }
196
+ ) {
197
+ try {
198
+ const { id } = await params;
199
+ const user = await db.user.findUnique({ where: { id } });
200
+
201
+ if (!user) {
202
+ throw new ApiError(404, 'User not found', 'USER_NOT_FOUND');
203
+ }
204
+
205
+ return NextResponse.json(user);
206
+ } catch (error) {
207
+ if (error instanceof ApiError) {
208
+ return NextResponse.json(
209
+ {
210
+ type: `https://api.example.com/errors/${error.code}`,
211
+ title: error.message,
212
+ status: error.status,
213
+ },
214
+ { status: error.status }
215
+ );
216
+ }
217
+
218
+ console.error('Unexpected error:', error);
219
+ return NextResponse.json(
220
+ { title: 'Internal Server Error', status: 500 },
221
+ { status: 500 }
222
+ );
223
+ }
224
+ }
225
+ ```
226
+
227
+ ## Authentication
228
+
229
+ ```typescript
230
+ import { auth } from '@/auth';
231
+
232
+ export async function GET() {
233
+ const session = await auth();
234
+
235
+ if (!session) {
236
+ return NextResponse.json(
237
+ { error: 'Unauthorized' },
238
+ { status: 401 }
239
+ );
240
+ }
241
+
242
+ const users = await db.user.findMany({
243
+ where: { organizationId: session.user.organizationId },
244
+ });
245
+
246
+ return NextResponse.json(users);
247
+ }
248
+ ```
249
+
250
+ ## File Upload
251
+
252
+ ```typescript
253
+ // app/api/upload/route.ts
254
+ import { writeFile } from 'fs/promises';
255
+ import { NextResponse } from 'next/server';
256
+
257
+ export async function POST(request: Request) {
258
+ const formData = await request.formData();
259
+ const file = formData.get('file') as File | null;
260
+
261
+ if (!file) {
262
+ return NextResponse.json(
263
+ { error: 'No file uploaded' },
264
+ { status: 400 }
265
+ );
266
+ }
267
+
268
+ // Validate file
269
+ const maxSize = 5 * 1024 * 1024; // 5MB
270
+ if (file.size > maxSize) {
271
+ return NextResponse.json(
272
+ { error: 'File too large' },
273
+ { status: 400 }
274
+ );
275
+ }
276
+
277
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
278
+ if (!allowedTypes.includes(file.type)) {
279
+ return NextResponse.json(
280
+ { error: 'Invalid file type' },
281
+ { status: 400 }
282
+ );
283
+ }
284
+
285
+ const bytes = await file.arrayBuffer();
286
+ const buffer = Buffer.from(bytes);
287
+
288
+ const filename = `${Date.now()}-${file.name}`;
289
+ await writeFile(`./public/uploads/${filename}`, buffer);
290
+
291
+ return NextResponse.json({ url: `/uploads/${filename}` });
292
+ }
293
+ ```
294
+
295
+ ## Streaming Response
296
+
297
+ ```typescript
298
+ export async function GET() {
299
+ const encoder = new TextEncoder();
300
+
301
+ const stream = new ReadableStream({
302
+ async start(controller) {
303
+ for (let i = 0; i < 10; i++) {
304
+ controller.enqueue(encoder.encode(`data: ${i}\n\n`));
305
+ await new Promise(resolve => setTimeout(resolve, 1000));
306
+ }
307
+ controller.close();
308
+ },
309
+ });
310
+
311
+ return new Response(stream, {
312
+ headers: {
313
+ 'Content-Type': 'text/event-stream',
314
+ 'Cache-Control': 'no-cache',
315
+ 'Connection': 'keep-alive',
316
+ },
317
+ });
318
+ }
319
+ ```
320
+
321
+ ## Route Configuration
322
+
323
+ ```typescript
324
+ // Caching
325
+ export const revalidate = 60; // Revalidate every 60 seconds
326
+ export const dynamic = 'force-dynamic'; // Always dynamic
327
+
328
+ // Runtime
329
+ export const runtime = 'edge'; // or 'nodejs'
330
+
331
+ // Max duration (Vercel)
332
+ export const maxDuration = 30;
333
+ ```
334
+
335
+ ## Anti-patterns
336
+
337
+ ```typescript
338
+ // BAD: Not validating input
339
+ export async function POST(request: Request) {
340
+ const body = await request.json();
341
+ await db.user.create({ data: body }); // SQL injection risk!
342
+ }
343
+
344
+ // GOOD: Validate with Zod
345
+ const result = schema.safeParse(body);
346
+ if (!result.success) return errorResponse(result.error);
347
+
348
+ // BAD: Exposing internal errors
349
+ catch (error) {
350
+ return NextResponse.json({ error: error.message }); // Leaks info!
351
+ }
352
+
353
+ // GOOD: Generic error message
354
+ catch (error) {
355
+ console.error(error);
356
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 });
357
+ }
358
+ ```
@@ -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
+ ```