@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,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
+ ```
@@ -0,0 +1,350 @@
1
+ ---
2
+ paths:
3
+ - "**/layout.tsx"
4
+ - "**/page.tsx"
5
+ - "app/sitemap.ts"
6
+ - "app/robots.ts"
7
+ ---
8
+
9
+ # Next.js SEO
10
+
11
+ ## Metadata API
12
+
13
+ ### Static Metadata
14
+
15
+ ```typescript
16
+ // app/page.tsx
17
+ import type { Metadata } from 'next';
18
+
19
+ export const metadata: Metadata = {
20
+ title: 'Home | My App',
21
+ description: 'Welcome to my application',
22
+ keywords: ['next.js', 'react', 'web development'],
23
+ authors: [{ name: 'John Doe' }],
24
+ openGraph: {
25
+ title: 'Home | My App',
26
+ description: 'Welcome to my application',
27
+ url: 'https://example.com',
28
+ siteName: 'My App',
29
+ images: [
30
+ {
31
+ url: 'https://example.com/og-image.png',
32
+ width: 1200,
33
+ height: 630,
34
+ alt: 'My App',
35
+ },
36
+ ],
37
+ locale: 'en_US',
38
+ type: 'website',
39
+ },
40
+ twitter: {
41
+ card: 'summary_large_image',
42
+ title: 'Home | My App',
43
+ description: 'Welcome to my application',
44
+ images: ['https://example.com/og-image.png'],
45
+ },
46
+ robots: {
47
+ index: true,
48
+ follow: true,
49
+ },
50
+ };
51
+ ```
52
+
53
+ ### Dynamic Metadata
54
+
55
+ ```typescript
56
+ // app/blog/[slug]/page.tsx
57
+ import type { Metadata } from 'next';
58
+
59
+ type Props = {
60
+ params: Promise<{ slug: string }>;
61
+ };
62
+
63
+ export async function generateMetadata({ params }: Props): Promise<Metadata> {
64
+ const { slug } = await params;
65
+ const post = await getPost(slug);
66
+
67
+ return {
68
+ title: `${post.title} | Blog`,
69
+ description: post.excerpt,
70
+ openGraph: {
71
+ title: post.title,
72
+ description: post.excerpt,
73
+ type: 'article',
74
+ publishedTime: post.publishedAt,
75
+ authors: [post.author.name],
76
+ images: [
77
+ {
78
+ url: post.featuredImage,
79
+ width: 1200,
80
+ height: 630,
81
+ alt: post.title,
82
+ },
83
+ ],
84
+ },
85
+ };
86
+ }
87
+ ```
88
+
89
+ ### Layout Metadata (Template)
90
+
91
+ ```typescript
92
+ // app/layout.tsx
93
+ import type { Metadata } from 'next';
94
+
95
+ export const metadata: Metadata = {
96
+ metadataBase: new URL('https://example.com'),
97
+ title: {
98
+ template: '%s | My App',
99
+ default: 'My App',
100
+ },
101
+ description: 'My application description',
102
+ };
103
+
104
+ // Child page: title: 'Blog' → renders as 'Blog | My App'
105
+ ```
106
+
107
+ ## Sitemap
108
+
109
+ ```typescript
110
+ // app/sitemap.ts
111
+ import type { MetadataRoute } from 'next';
112
+
113
+ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
114
+ const baseUrl = 'https://example.com';
115
+
116
+ // Static pages
117
+ const staticPages = [
118
+ { url: baseUrl, lastModified: new Date(), changeFrequency: 'daily' as const, priority: 1 },
119
+ { url: `${baseUrl}/about`, lastModified: new Date(), changeFrequency: 'monthly' as const, priority: 0.8 },
120
+ { url: `${baseUrl}/contact`, lastModified: new Date(), changeFrequency: 'monthly' as const, priority: 0.5 },
121
+ ];
122
+
123
+ // Dynamic pages
124
+ const posts = await db.post.findMany({
125
+ select: { slug: true, updatedAt: true },
126
+ });
127
+
128
+ const postPages = posts.map(post => ({
129
+ url: `${baseUrl}/blog/${post.slug}`,
130
+ lastModified: post.updatedAt,
131
+ changeFrequency: 'weekly' as const,
132
+ priority: 0.7,
133
+ }));
134
+
135
+ return [...staticPages, ...postPages];
136
+ }
137
+ ```
138
+
139
+ ### Large Sitemap (Multiple Files)
140
+
141
+ ```typescript
142
+ // app/sitemap/[id]/route.ts
143
+ import { getPostsBatch } from '@/lib/posts';
144
+
145
+ export async function GET(
146
+ request: Request,
147
+ { params }: { params: Promise<{ id: string }> }
148
+ ) {
149
+ const { id } = await params;
150
+ const posts = await getPostsBatch(parseInt(id));
151
+
152
+ const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
153
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
154
+ ${posts.map(post => `
155
+ <url>
156
+ <loc>https://example.com/blog/${post.slug}</loc>
157
+ <lastmod>${post.updatedAt.toISOString()}</lastmod>
158
+ </url>
159
+ `).join('')}
160
+ </urlset>`;
161
+
162
+ return new Response(sitemap, {
163
+ headers: { 'Content-Type': 'application/xml' },
164
+ });
165
+ }
166
+ ```
167
+
168
+ ## Robots.txt
169
+
170
+ ```typescript
171
+ // app/robots.ts
172
+ import type { MetadataRoute } from 'next';
173
+
174
+ export default function robots(): MetadataRoute.Robots {
175
+ return {
176
+ rules: [
177
+ {
178
+ userAgent: '*',
179
+ allow: '/',
180
+ disallow: ['/admin/', '/api/', '/private/'],
181
+ },
182
+ {
183
+ userAgent: 'Googlebot',
184
+ allow: '/',
185
+ },
186
+ ],
187
+ sitemap: 'https://example.com/sitemap.xml',
188
+ };
189
+ }
190
+ ```
191
+
192
+ ## JSON-LD Structured Data
193
+
194
+ ```typescript
195
+ // components/structured-data.tsx
196
+ export function ArticleJsonLd({
197
+ title,
198
+ description,
199
+ publishedTime,
200
+ author,
201
+ image,
202
+ url,
203
+ }: ArticleJsonLdProps) {
204
+ const jsonLd = {
205
+ '@context': 'https://schema.org',
206
+ '@type': 'Article',
207
+ headline: title,
208
+ description,
209
+ image,
210
+ datePublished: publishedTime,
211
+ author: {
212
+ '@type': 'Person',
213
+ name: author,
214
+ },
215
+ publisher: {
216
+ '@type': 'Organization',
217
+ name: 'My App',
218
+ logo: {
219
+ '@type': 'ImageObject',
220
+ url: 'https://example.com/logo.png',
221
+ },
222
+ },
223
+ mainEntityOfPage: url,
224
+ };
225
+
226
+ return (
227
+ <script
228
+ type="application/ld+json"
229
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
230
+ />
231
+ );
232
+ }
233
+
234
+ // Usage in page
235
+ export default function BlogPost({ post }) {
236
+ return (
237
+ <>
238
+ <ArticleJsonLd
239
+ title={post.title}
240
+ description={post.excerpt}
241
+ publishedTime={post.publishedAt}
242
+ author={post.author.name}
243
+ image={post.featuredImage}
244
+ url={`https://example.com/blog/${post.slug}`}
245
+ />
246
+ <article>...</article>
247
+ </>
248
+ );
249
+ }
250
+ ```
251
+
252
+ ### Organization Schema
253
+
254
+ ```typescript
255
+ export function OrganizationJsonLd() {
256
+ const jsonLd = {
257
+ '@context': 'https://schema.org',
258
+ '@type': 'Organization',
259
+ name: 'My Company',
260
+ url: 'https://example.com',
261
+ logo: 'https://example.com/logo.png',
262
+ sameAs: [
263
+ 'https://twitter.com/mycompany',
264
+ 'https://linkedin.com/company/mycompany',
265
+ ],
266
+ contactPoint: {
267
+ '@type': 'ContactPoint',
268
+ telephone: '+1-800-555-1234',
269
+ contactType: 'customer service',
270
+ },
271
+ };
272
+
273
+ return (
274
+ <script
275
+ type="application/ld+json"
276
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
277
+ />
278
+ );
279
+ }
280
+ ```
281
+
282
+ ## Canonical URLs
283
+
284
+ ```typescript
285
+ export const metadata: Metadata = {
286
+ alternates: {
287
+ canonical: 'https://example.com/page',
288
+ languages: {
289
+ 'en-US': 'https://example.com/en-US/page',
290
+ 'fr-FR': 'https://example.com/fr-FR/page',
291
+ },
292
+ },
293
+ };
294
+ ```
295
+
296
+ ## Performance (Core Web Vitals)
297
+
298
+ ```typescript
299
+ // next.config.js
300
+ module.exports = {
301
+ images: {
302
+ formats: ['image/avif', 'image/webp'],
303
+ deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
304
+ },
305
+ experimental: {
306
+ optimizeCss: true,
307
+ },
308
+ };
309
+
310
+ // Image component
311
+ import Image from 'next/image';
312
+
313
+ <Image
314
+ src="/hero.jpg"
315
+ alt="Hero image"
316
+ width={1200}
317
+ height={630}
318
+ priority // For LCP images
319
+ placeholder="blur"
320
+ blurDataURL="data:image/jpeg;base64,..."
321
+ />
322
+ ```
323
+
324
+ ## Anti-patterns
325
+
326
+ ```typescript
327
+ // BAD: Missing metadata
328
+ export default function Page() {
329
+ return <div>Content</div>;
330
+ }
331
+
332
+ // GOOD: Always include metadata
333
+ export const metadata: Metadata = {
334
+ title: 'Page Title',
335
+ description: 'Page description',
336
+ };
337
+
338
+ // BAD: Duplicate content without canonical
339
+ // page-1 and page-2 have same content
340
+
341
+ // GOOD: Set canonical
342
+ export const metadata: Metadata = {
343
+ alternates: { canonical: '/page-1' },
344
+ };
345
+
346
+ // BAD: Blocking indexing of important pages
347
+ export const metadata: Metadata = {
348
+ robots: { index: false }, // Accidentally blocking!
349
+ };
350
+ ```