@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,303 @@
1
+ ---
2
+ paths:
3
+ - "middleware.ts"
4
+ - "src/middleware.ts"
5
+ ---
6
+
7
+ # Next.js Middleware
8
+
9
+ ## Basic Structure
10
+
11
+ ```typescript
12
+ // middleware.ts
13
+ import { NextResponse } from 'next/server';
14
+ import type { NextRequest } from 'next/server';
15
+
16
+ export function middleware(request: NextRequest) {
17
+ // Runs on every matched request
18
+ return NextResponse.next();
19
+ }
20
+
21
+ export const config = {
22
+ matcher: [
23
+ // Match all paths except static files
24
+ '/((?!_next/static|_next/image|favicon.ico).*)',
25
+ ],
26
+ };
27
+ ```
28
+
29
+ ## Common Patterns
30
+
31
+ ### Authentication
32
+
33
+ ```typescript
34
+ import { NextResponse } from 'next/server';
35
+ import type { NextRequest } from 'next/server';
36
+ import { getToken } from 'next-auth/jwt';
37
+
38
+ export async function middleware(request: NextRequest) {
39
+ const token = await getToken({ req: request });
40
+ const { pathname } = request.nextUrl;
41
+
42
+ // Public paths
43
+ const publicPaths = ['/login', '/register', '/api/auth'];
44
+ if (publicPaths.some(path => pathname.startsWith(path))) {
45
+ return NextResponse.next();
46
+ }
47
+
48
+ // Protected paths
49
+ if (!token) {
50
+ const loginUrl = new URL('/login', request.url);
51
+ loginUrl.searchParams.set('callbackUrl', pathname);
52
+ return NextResponse.redirect(loginUrl);
53
+ }
54
+
55
+ return NextResponse.next();
56
+ }
57
+ ```
58
+
59
+ ### Role-Based Access
60
+
61
+ ```typescript
62
+ export async function middleware(request: NextRequest) {
63
+ const token = await getToken({ req: request });
64
+ const { pathname } = request.nextUrl;
65
+
66
+ // Admin routes
67
+ if (pathname.startsWith('/admin')) {
68
+ if (token?.role !== 'admin') {
69
+ return NextResponse.redirect(new URL('/forbidden', request.url));
70
+ }
71
+ }
72
+
73
+ // API admin routes
74
+ if (pathname.startsWith('/api/admin')) {
75
+ if (token?.role !== 'admin') {
76
+ return NextResponse.json(
77
+ { error: 'Forbidden' },
78
+ { status: 403 }
79
+ );
80
+ }
81
+ }
82
+
83
+ return NextResponse.next();
84
+ }
85
+ ```
86
+
87
+ ### Internationalization (i18n)
88
+
89
+ ```typescript
90
+ import { NextResponse } from 'next/server';
91
+ import type { NextRequest } from 'next/server';
92
+ import { match } from '@formatjs/intl-localematcher';
93
+ import Negotiator from 'negotiator';
94
+
95
+ const locales = ['en', 'fr', 'de'];
96
+ const defaultLocale = 'en';
97
+
98
+ function getLocale(request: NextRequest): string {
99
+ const headers = { 'accept-language': request.headers.get('accept-language') || '' };
100
+ const languages = new Negotiator({ headers }).languages();
101
+ return match(languages, locales, defaultLocale);
102
+ }
103
+
104
+ export function middleware(request: NextRequest) {
105
+ const { pathname } = request.nextUrl;
106
+
107
+ // Check if pathname has locale
108
+ const pathnameHasLocale = locales.some(
109
+ locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
110
+ );
111
+
112
+ if (pathnameHasLocale) return NextResponse.next();
113
+
114
+ // Redirect to locale
115
+ const locale = getLocale(request);
116
+ request.nextUrl.pathname = `/${locale}${pathname}`;
117
+ return NextResponse.redirect(request.nextUrl);
118
+ }
119
+
120
+ export const config = {
121
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
122
+ };
123
+ ```
124
+
125
+ ### Rate Limiting
126
+
127
+ ```typescript
128
+ import { NextResponse } from 'next/server';
129
+ import type { NextRequest } from 'next/server';
130
+ import { Ratelimit } from '@upstash/ratelimit';
131
+ import { Redis } from '@upstash/redis';
132
+
133
+ const ratelimit = new Ratelimit({
134
+ redis: Redis.fromEnv(),
135
+ limiter: Ratelimit.slidingWindow(10, '10 s'),
136
+ });
137
+
138
+ export async function middleware(request: NextRequest) {
139
+ if (request.nextUrl.pathname.startsWith('/api')) {
140
+ const ip = request.ip ?? '127.0.0.1';
141
+ const { success, limit, remaining, reset } = await ratelimit.limit(ip);
142
+
143
+ if (!success) {
144
+ return NextResponse.json(
145
+ { error: 'Too many requests' },
146
+ {
147
+ status: 429,
148
+ headers: {
149
+ 'X-RateLimit-Limit': limit.toString(),
150
+ 'X-RateLimit-Remaining': remaining.toString(),
151
+ 'X-RateLimit-Reset': reset.toString(),
152
+ },
153
+ }
154
+ );
155
+ }
156
+ }
157
+
158
+ return NextResponse.next();
159
+ }
160
+ ```
161
+
162
+ ### Geolocation Redirect
163
+
164
+ ```typescript
165
+ export function middleware(request: NextRequest) {
166
+ const country = request.geo?.country || 'US';
167
+
168
+ // Redirect EU users to EU subdomain
169
+ const euCountries = ['DE', 'FR', 'IT', 'ES', 'NL'];
170
+ if (euCountries.includes(country) && !request.nextUrl.hostname.includes('eu.')) {
171
+ return NextResponse.redirect(
172
+ new URL(request.nextUrl.pathname, 'https://eu.example.com')
173
+ );
174
+ }
175
+
176
+ return NextResponse.next();
177
+ }
178
+ ```
179
+
180
+ ### Security Headers
181
+
182
+ ```typescript
183
+ export function middleware(request: NextRequest) {
184
+ const response = NextResponse.next();
185
+
186
+ // Security headers
187
+ response.headers.set('X-Frame-Options', 'DENY');
188
+ response.headers.set('X-Content-Type-Options', 'nosniff');
189
+ response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
190
+ response.headers.set(
191
+ 'Content-Security-Policy',
192
+ "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"
193
+ );
194
+ response.headers.set(
195
+ 'Permissions-Policy',
196
+ 'camera=(), microphone=(), geolocation=()'
197
+ );
198
+
199
+ return response;
200
+ }
201
+ ```
202
+
203
+ ### Request/Response Modification
204
+
205
+ ```typescript
206
+ export function middleware(request: NextRequest) {
207
+ // Add custom header to request
208
+ const requestHeaders = new Headers(request.headers);
209
+ requestHeaders.set('x-request-id', crypto.randomUUID());
210
+
211
+ // Rewrite URL
212
+ if (request.nextUrl.pathname === '/old-path') {
213
+ return NextResponse.rewrite(new URL('/new-path', request.url));
214
+ }
215
+
216
+ // Pass headers to server components
217
+ const response = NextResponse.next({
218
+ request: {
219
+ headers: requestHeaders,
220
+ },
221
+ });
222
+
223
+ // Add header to response
224
+ response.headers.set('x-request-id', requestHeaders.get('x-request-id')!);
225
+
226
+ return response;
227
+ }
228
+ ```
229
+
230
+ ### A/B Testing
231
+
232
+ ```typescript
233
+ export function middleware(request: NextRequest) {
234
+ const bucket = request.cookies.get('ab-bucket')?.value;
235
+
236
+ if (!bucket) {
237
+ // Assign to bucket
238
+ const newBucket = Math.random() > 0.5 ? 'a' : 'b';
239
+ const response = NextResponse.next();
240
+ response.cookies.set('ab-bucket', newBucket, {
241
+ httpOnly: true,
242
+ maxAge: 60 * 60 * 24 * 30, // 30 days
243
+ });
244
+ return response;
245
+ }
246
+
247
+ // Rewrite based on bucket
248
+ if (request.nextUrl.pathname === '/landing') {
249
+ return NextResponse.rewrite(
250
+ new URL(`/landing-${bucket}`, request.url)
251
+ );
252
+ }
253
+
254
+ return NextResponse.next();
255
+ }
256
+ ```
257
+
258
+ ## Matcher Patterns
259
+
260
+ ```typescript
261
+ export const config = {
262
+ matcher: [
263
+ // Match all paths except static
264
+ '/((?!_next/static|_next/image|favicon.ico).*)',
265
+
266
+ // Match specific paths
267
+ '/dashboard/:path*',
268
+ '/api/:path*',
269
+
270
+ // Match with regex
271
+ '/(api|admin)/:path*',
272
+
273
+ // Exclude specific paths
274
+ '/((?!api/public)api/:path*)',
275
+ ],
276
+ };
277
+ ```
278
+
279
+ ## Anti-patterns
280
+
281
+ ```typescript
282
+ // BAD: Heavy computation in middleware (runs on every request)
283
+ export async function middleware(request: NextRequest) {
284
+ await heavyDatabaseQuery(); // Blocks all requests!
285
+ }
286
+
287
+ // GOOD: Keep middleware lightweight
288
+ export async function middleware(request: NextRequest) {
289
+ // Only quick checks, use Edge-compatible code
290
+ }
291
+
292
+ // BAD: Using Node.js APIs (middleware runs on Edge)
293
+ import fs from 'fs'; // Won't work!
294
+
295
+ // GOOD: Use Edge-compatible APIs
296
+ import { Redis } from '@upstash/redis'; // Edge-compatible
297
+
298
+ // BAD: Modifying response body
299
+ return new Response('Modified body'); // Loses Next.js features
300
+
301
+ // GOOD: Use rewrite or redirect
302
+ return NextResponse.rewrite(new URL('/new-path', request.url));
303
+ ```
@@ -0,0 +1,324 @@
1
+ ---
2
+ paths:
3
+ - "apps/**/app/**/*.tsx"
4
+ - "app/**/*.tsx"
5
+ ---
6
+
7
+ # Next.js App Router Patterns
8
+
9
+ ## Route Structure
10
+
11
+ ```
12
+ app/
13
+ ├── page.tsx # / (home)
14
+ ├── layout.tsx # Root layout
15
+ ├── loading.tsx # Root loading
16
+ ├── error.tsx # Root error boundary
17
+ ├── not-found.tsx # 404 page
18
+
19
+ ├── (marketing)/ # Route group (not in URL)
20
+ │ ├── layout.tsx # Shared marketing layout
21
+ │ ├── about/page.tsx # /about
22
+ │ └── pricing/page.tsx # /pricing
23
+
24
+ ├── (app)/ # Route group for app
25
+ │ ├── layout.tsx # App layout (with auth)
26
+ │ ├── dashboard/page.tsx # /dashboard
27
+ │ └── settings/page.tsx # /settings
28
+
29
+ ├── blog/
30
+ │ ├── page.tsx # /blog
31
+ │ └── [slug]/ # Dynamic segment
32
+ │ ├── page.tsx # /blog/my-post
33
+ │ └── opengraph-image.tsx # OG image generation
34
+
35
+ ├── docs/
36
+ │ └── [...slug]/ # Catch-all segment
37
+ │ └── page.tsx # /docs/a/b/c
38
+
39
+ └── api/ # API routes
40
+ └── webhooks/
41
+ └── stripe/route.ts # /api/webhooks/stripe
42
+ ```
43
+
44
+ ## Dynamic Routes
45
+
46
+ ```tsx
47
+ // app/blog/[slug]/page.tsx
48
+ interface Props {
49
+ params: Promise<{ slug: string }>;
50
+ }
51
+
52
+ export default async function BlogPost({ params }: Props) {
53
+ const { slug } = await params;
54
+ const post = await getPost(slug);
55
+
56
+ if (!post) {
57
+ notFound();
58
+ }
59
+
60
+ return <article>{post.content}</article>;
61
+ }
62
+
63
+ // Generate static params for SSG
64
+ export async function generateStaticParams() {
65
+ const posts = await getPosts();
66
+ return posts.map((post) => ({ slug: post.slug }));
67
+ }
68
+ ```
69
+
70
+ ## Catch-All Routes
71
+
72
+ ```tsx
73
+ // app/docs/[...slug]/page.tsx
74
+ interface Props {
75
+ params: Promise<{ slug: string[] }>;
76
+ }
77
+
78
+ export default async function DocsPage({ params }: Props) {
79
+ const { slug } = await params;
80
+ // slug = ['guide', 'getting-started'] for /docs/guide/getting-started
81
+
82
+ const path = slug.join('/');
83
+ const doc = await getDoc(path);
84
+
85
+ return <DocContent doc={doc} />;
86
+ }
87
+ ```
88
+
89
+ ## Route Groups
90
+
91
+ ```tsx
92
+ // (marketing) and (app) don't appear in URL
93
+ // but allow different layouts
94
+
95
+ // app/(marketing)/layout.tsx
96
+ export default function MarketingLayout({ children }) {
97
+ return (
98
+ <>
99
+ <MarketingNav />
100
+ <main>{children}</main>
101
+ <Footer />
102
+ </>
103
+ );
104
+ }
105
+
106
+ // app/(app)/layout.tsx
107
+ export default function AppLayout({ children }) {
108
+ return (
109
+ <>
110
+ <Sidebar />
111
+ <main>{children}</main>
112
+ </>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ## Parallel Routes
118
+
119
+ ```tsx
120
+ // app/layout.tsx with slots
121
+ export default function Layout({
122
+ children,
123
+ modal, // @modal slot
124
+ analytics, // @analytics slot
125
+ }: {
126
+ children: React.ReactNode;
127
+ modal: React.ReactNode;
128
+ analytics: React.ReactNode;
129
+ }) {
130
+ return (
131
+ <html>
132
+ <body>
133
+ {children}
134
+ {modal}
135
+ {analytics}
136
+ </body>
137
+ </html>
138
+ );
139
+ }
140
+
141
+ // app/@modal/login/page.tsx - Intercepted modal
142
+ // app/@analytics/page.tsx - Analytics slot
143
+ ```
144
+
145
+ ## Intercepting Routes
146
+
147
+ ```tsx
148
+ // Intercept /photo/123 when navigating from gallery
149
+ // app/gallery/@modal/(.)photo/[id]/page.tsx
150
+
151
+ // (.) - Same level
152
+ // (..) - One level up
153
+ // (..)(..) - Two levels up
154
+ // (...) - Root
155
+
156
+ export default function PhotoModal({ params }: { params: Promise<{ id: string }> }) {
157
+ return (
158
+ <Modal>
159
+ <Photo id={(await params).id} />
160
+ </Modal>
161
+ );
162
+ }
163
+ ```
164
+
165
+ ## Navigation
166
+
167
+ ```tsx
168
+ import Link from 'next/link';
169
+ import { useRouter, usePathname, useSearchParams } from 'next/navigation';
170
+
171
+ // Declarative navigation
172
+ <Link href="/blog/my-post">Read Post</Link>
173
+ <Link href={{ pathname: '/blog', query: { sort: 'date' } }}>Blog</Link>
174
+
175
+ // Prefetch on hover (default)
176
+ <Link href="/dashboard" prefetch={true}>Dashboard</Link>
177
+
178
+ // No prefetch
179
+ <Link href="/admin" prefetch={false}>Admin</Link>
180
+
181
+ // Programmatic navigation
182
+ function NavigationExample() {
183
+ const router = useRouter();
184
+ const pathname = usePathname();
185
+ const searchParams = useSearchParams();
186
+
187
+ function navigate() {
188
+ router.push('/dashboard');
189
+ router.replace('/login'); // No history entry
190
+ router.back();
191
+ router.forward();
192
+ router.refresh(); // Re-fetch server components
193
+ }
194
+
195
+ // Current URL info
196
+ console.log(pathname); // /blog
197
+ console.log(searchParams.get('page')); // 1
198
+ }
199
+ ```
200
+
201
+ ## Middleware
202
+
203
+ ```typescript
204
+ // middleware.ts (root level)
205
+ import { NextResponse } from 'next/server';
206
+ import type { NextRequest } from 'next/server';
207
+
208
+ export function middleware(request: NextRequest) {
209
+ const { pathname } = request.nextUrl;
210
+
211
+ // Auth check
212
+ const token = request.cookies.get('token');
213
+ if (pathname.startsWith('/dashboard') && !token) {
214
+ return NextResponse.redirect(new URL('/login', request.url));
215
+ }
216
+
217
+ // Rewrite
218
+ if (pathname.startsWith('/api/v1')) {
219
+ return NextResponse.rewrite(new URL('/api/v2' + pathname.slice(7), request.url));
220
+ }
221
+
222
+ // Add headers
223
+ const response = NextResponse.next();
224
+ response.headers.set('x-custom-header', 'value');
225
+ return response;
226
+ }
227
+
228
+ export const config = {
229
+ matcher: ['/dashboard/:path*', '/api/:path*'],
230
+ };
231
+ ```
232
+
233
+ ## Metadata
234
+
235
+ ```tsx
236
+ // Static metadata
237
+ export const metadata = {
238
+ title: 'My Blog',
239
+ description: 'A blog about things',
240
+ };
241
+
242
+ // Dynamic metadata
243
+ export async function generateMetadata({ params }: Props): Promise<Metadata> {
244
+ const { slug } = await params;
245
+ const post = await getPost(slug);
246
+
247
+ return {
248
+ title: post.title,
249
+ description: post.excerpt,
250
+ openGraph: {
251
+ title: post.title,
252
+ images: [post.image],
253
+ },
254
+ };
255
+ }
256
+ ```
257
+
258
+ ## Route Handlers (API)
259
+
260
+ ```typescript
261
+ // app/api/users/route.ts
262
+ import { NextRequest, NextResponse } from 'next/server';
263
+
264
+ export async function GET(request: NextRequest) {
265
+ const searchParams = request.nextUrl.searchParams;
266
+ const page = searchParams.get('page') || '1';
267
+
268
+ const users = await getUsers({ page: parseInt(page) });
269
+ return NextResponse.json(users);
270
+ }
271
+
272
+ export async function POST(request: NextRequest) {
273
+ const body = await request.json();
274
+ const user = await createUser(body);
275
+ return NextResponse.json(user, { status: 201 });
276
+ }
277
+
278
+ // app/api/users/[id]/route.ts
279
+ export async function GET(
280
+ request: NextRequest,
281
+ { params }: { params: Promise<{ id: string }> }
282
+ ) {
283
+ const { id } = await params;
284
+ const user = await getUser(id);
285
+
286
+ if (!user) {
287
+ return NextResponse.json({ error: 'Not found' }, { status: 404 });
288
+ }
289
+
290
+ return NextResponse.json(user);
291
+ }
292
+ ```
293
+
294
+ ## Loading & Error Boundaries
295
+
296
+ ```tsx
297
+ // app/dashboard/loading.tsx
298
+ export default function Loading() {
299
+ return <DashboardSkeleton />;
300
+ }
301
+
302
+ // app/dashboard/error.tsx
303
+ 'use client';
304
+
305
+ export default function Error({
306
+ error,
307
+ reset,
308
+ }: {
309
+ error: Error & { digest?: string };
310
+ reset: () => void;
311
+ }) {
312
+ return (
313
+ <div>
314
+ <h2>Something went wrong!</h2>
315
+ <button onClick={reset}>Try again</button>
316
+ </div>
317
+ );
318
+ }
319
+
320
+ // app/dashboard/not-found.tsx
321
+ export default function NotFound() {
322
+ return <div>Dashboard not found</div>;
323
+ }
324
+ ```