@raftlabs/raftstack 1.7.0 → 1.7.2

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: backend
3
- description: Use when writing serverless functions, API handlers, backend services, or when code has tight coupling to infrastructure, no dependency injection, or mixed concerns
3
+ description: Use when writing Lambda functions, API routes, Hono handlers, Express routes, serverless endpoints, or backend services. Use when creating API validation with Zod, implementing service layers, or structuring handler code.
4
4
  ---
5
5
 
6
6
  # Backend Development
@@ -578,6 +578,135 @@ export const handler = compose(
578
578
  )(baseHandler);
579
579
  ```
580
580
 
581
+ ## Hono.js Patterns
582
+
583
+ Hono is a fast, lightweight framework for serverless and edge. Same patterns apply - layer separation, DI, Zod validation.
584
+
585
+ ### Basic Hono Handler with Zod
586
+
587
+ ```typescript
588
+ import { Hono } from 'hono';
589
+ import { zValidator } from '@hono/zod-validator';
590
+ import { z } from 'zod';
591
+
592
+ const app = new Hono();
593
+
594
+ const CreateUserSchema = z.object({
595
+ email: z.string().email(),
596
+ name: z.string().min(1),
597
+ });
598
+
599
+ // ✅ GOOD: Validation middleware + typed body
600
+ app.post(
601
+ '/users',
602
+ zValidator('json', CreateUserSchema),
603
+ async (c) => {
604
+ const body = c.req.valid('json'); // Typed as { email: string; name: string }
605
+ const user = await userService.createUser(body);
606
+ return c.json(user, 201);
607
+ }
608
+ );
609
+ ```
610
+
611
+ ### Hono with Dependency Injection
612
+
613
+ ```typescript
614
+ // ✅ GOOD: Factory pattern for testable Hono apps
615
+ import { Hono } from 'hono';
616
+
617
+ export function createApp(deps: {
618
+ userService: UserService;
619
+ authService: AuthService;
620
+ }) {
621
+ const app = new Hono();
622
+
623
+ app.post('/users', async (c) => {
624
+ const body = await c.req.json();
625
+ const user = await deps.userService.createUser(body);
626
+ return c.json(user, 201);
627
+ });
628
+
629
+ app.post('/login', async (c) => {
630
+ const { email, password } = await c.req.json();
631
+ const token = await deps.authService.login(email, password);
632
+ return c.json({ token });
633
+ });
634
+
635
+ return app;
636
+ }
637
+
638
+ // Production: inject real services
639
+ const app = createApp({
640
+ userService: createUserService(db),
641
+ authService: createAuthService(db),
642
+ });
643
+
644
+ export default app;
645
+
646
+ // Test: inject mocks
647
+ const testApp = createApp({
648
+ userService: { createUser: vi.fn() },
649
+ authService: { login: vi.fn() },
650
+ });
651
+ ```
652
+
653
+ ### Hono Error Handling Middleware
654
+
655
+ ```typescript
656
+ import { Hono } from 'hono';
657
+ import { HTTPException } from 'hono/http-exception';
658
+
659
+ const app = new Hono();
660
+
661
+ // Global error handler
662
+ app.onError((err, c) => {
663
+ if (err instanceof AppError) {
664
+ return c.json(
665
+ { error: err.message, code: err.code },
666
+ err.statusCode
667
+ );
668
+ }
669
+
670
+ if (err instanceof HTTPException) {
671
+ return c.json({ error: err.message }, err.status);
672
+ }
673
+
674
+ console.error('Unexpected error:', err);
675
+ return c.json({ error: 'Internal server error' }, 500);
676
+ });
677
+
678
+ // Route handlers throw AppError
679
+ app.get('/users/:id', async (c) => {
680
+ const user = await userService.getUser(c.req.param('id'));
681
+ if (!user) {
682
+ throw new NotFoundError('User not found');
683
+ }
684
+ return c.json(user);
685
+ });
686
+ ```
687
+
688
+ ### Hono Middleware Composition
689
+
690
+ ```typescript
691
+ import { Hono } from 'hono';
692
+ import { cors } from 'hono/cors';
693
+ import { logger } from 'hono/logger';
694
+ import { jwt } from 'hono/jwt';
695
+
696
+ const app = new Hono();
697
+
698
+ // Apply middleware in order
699
+ app.use('*', logger());
700
+ app.use('*', cors());
701
+ app.use('/api/*', jwt({ secret: process.env.JWT_SECRET }));
702
+
703
+ // Protected routes
704
+ app.get('/api/me', (c) => {
705
+ const payload = c.get('jwtPayload');
706
+ return c.json({ userId: payload.sub });
707
+ });
708
+ ```
709
+
581
710
  ## Testing Strategy
582
711
 
583
712
  | What to Test | How |
@@ -735,6 +864,7 @@ describe('Handler', () => {
735
864
  ## References
736
865
 
737
866
  - [Zod Documentation](https://zod.dev) - Validation, transforms, error formatting, branded types
867
+ - [Hono Documentation](https://hono.dev) - Lightweight framework for serverless and edge
738
868
  - [Vitest Documentation](https://vitest.dev) - Testing, mocking, vi.fn(), vi.spyOn()
739
869
  - [AWS Lambda Cold Starts](https://aws.amazon.com/blogs/compute/understanding-and-remediating-cold-starts-an-aws-lambda-perspective/) - Official optimization guide
740
870
  - [AWS Lambda Performance](https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/) - Best practices
@@ -742,6 +872,7 @@ describe('Handler', () => {
742
872
  **Version Notes:**
743
873
  - Zod v3.24+: Improved error formatting, discriminated unions, branded types
744
874
  - Zod v4.0+: prefault(), enhanced pipe(), performance improvements
875
+ - Hono v4+: Stable, edge-ready, built-in middleware
745
876
  - Vitest v3+: mockResolvedValue, mockRejectedValue patterns
746
877
  - AWS Lambda: Node.js 20.x has faster cold starts than 18.x
747
878
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: code-quality
3
- description: Use when writing or reviewing code, creating functions, naming variables, structuring files, or when code feels messy, hard to read, or overly complex
3
+ description: Use when refactoring functions, extracting helpers, splitting large files, improving naming conventions, or reducing complexity. Use when functions exceed 30 lines, have too many parameters, or contain magic numbers. NOT for React/backend/database-specific patterns.
4
4
  ---
5
5
 
6
6
  # Code Quality
@@ -141,46 +141,59 @@ if (user.role === 'admin') { }
141
141
 
142
142
  ## Automated Enforcement
143
143
 
144
- ### ESLint Configuration
144
+ ### ESLint Configuration (Flat Config - v9+)
145
145
 
146
- Enforce code quality with automated tools:
146
+ ESLint 9+ uses flat config (`eslint.config.js`). Enforce code quality automatically:
147
147
 
148
148
  ```javascript
149
- // .eslintrc.js
150
- module.exports = {
151
- rules: {
152
- // Max function length
153
- 'max-lines-per-function': ['error', {
154
- max: 30,
155
- skipBlankLines: true,
156
- skipComments: true,
157
- }],
158
-
159
- // Cyclomatic complexity
160
- 'complexity': ['error', { max: 10 }],
161
-
162
- // Max file length
163
- 'max-lines': ['error', {
164
- max: 300,
165
- skipBlankLines: true,
166
- skipComments: true,
167
- }],
168
-
169
- // Max function params
170
- 'max-params': ['error', 3],
171
-
172
- // Max nested callbacks
173
- 'max-nested-callbacks': ['error', 2],
174
-
175
- // No magic numbers
176
- 'no-magic-numbers': ['error', {
177
- ignore: [0, 1, -1],
178
- ignoreArrayIndexes: true,
179
- }],
149
+ // eslint.config.js (ESLint v9+ flat config)
150
+ import js from '@eslint/js';
151
+ import tseslint from 'typescript-eslint';
152
+
153
+ export default tseslint.config(
154
+ js.configs.recommended,
155
+ ...tseslint.configs.recommended,
156
+ {
157
+ rules: {
158
+ // Max function length
159
+ 'max-lines-per-function': ['error', {
160
+ max: 30,
161
+ skipBlankLines: true,
162
+ skipComments: true,
163
+ }],
164
+
165
+ // Cyclomatic complexity
166
+ 'complexity': ['error', { max: 10 }],
167
+
168
+ // Max file length
169
+ 'max-lines': ['error', {
170
+ max: 300,
171
+ skipBlankLines: true,
172
+ skipComments: true,
173
+ }],
174
+
175
+ // Max function params
176
+ 'max-params': ['error', 3],
177
+
178
+ // Max nested callbacks
179
+ 'max-nested-callbacks': ['error', 2],
180
+
181
+ // No magic numbers
182
+ 'no-magic-numbers': ['error', {
183
+ ignore: [0, 1, -1],
184
+ ignoreArrayIndexes: true,
185
+ }],
186
+ },
180
187
  },
181
- };
188
+ {
189
+ // Ignore patterns (replaces .eslintignore)
190
+ ignores: ['node_modules/', 'dist/', '*.config.js'],
191
+ }
192
+ );
182
193
  ```
183
194
 
195
+ **Legacy config?** ESLint 9+ still supports `.eslintrc.js` via `ESLINT_USE_FLAT_CONFIG=false`, but flat config is the future.
196
+
184
197
  ### TypeScript-Specific Patterns
185
198
 
186
199
  ```typescript
@@ -294,14 +307,16 @@ When someone says "just make it work fast":
294
307
 
295
308
  ## References
296
309
 
310
+ - [ESLint Flat Config](https://eslint.org/docs/latest/use/configure/configuration-files-new) - ESLint 9+ configuration
297
311
  - [ESLint Complexity Rule](https://eslint.org/docs/latest/rules/complexity) - Cyclomatic complexity enforcement
298
- - [ESLintCC](https://eslintcc.github.io/) - Complexity measurement tool
312
+ - [typescript-eslint](https://typescript-eslint.io/) - TypeScript ESLint integration
299
313
  - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/) - Advanced type patterns
300
314
  - [Clean Code (Martin)](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) - Function length rationale
301
315
 
302
316
  **Version Notes:**
303
- - ESLint 9+: Flat config format, enhanced rule options
304
- - TypeScript 5+: Improved discriminated union narrowing
317
+ - ESLint 9+: Flat config (`eslint.config.js`), replaces `.eslintrc.*`
318
+ - TypeScript 5+: Improved discriminated union narrowing, const type parameters
319
+ - typescript-eslint 8+: Native flat config support
305
320
  - Cyclomatic complexity: Default threshold 20, recommended 10
306
321
 
307
322
  ## Common Mistakes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: database
3
- description: Use when designing database schemas, writing Drizzle ORM code, creating tables, choosing indexes, or when queries are slow and indexes might be missing
3
+ description: Use when writing Drizzle ORM schemas, creating database tables, adding indexes, writing SQL migrations, defining relations, or optimizing PostgreSQL queries. Use for foreign key indexes, JSONB columns, composite indexes, or prepared statements.
4
4
  ---
5
5
 
6
6
  # Database Development
@@ -125,20 +125,66 @@ export const users = pgTable("users", {
125
125
  | Strategy | Use When | Pros | Cons |
126
126
  |----------|----------|------|------|
127
127
  | **UUID** | Distributed systems, public IDs | No collisions, hide growth | Larger, slower indexes |
128
- | **Serial/bigserial** | Single DB, internal IDs | Compact, fast | Exposes growth, sequence issues |
128
+ | **Identity** | Single DB, internal IDs (2025+) | Compact, fast, SQL standard | Exposes growth |
129
129
  | **ULID/NanoID** | Need sortability + randomness | Sortable, compact | Custom generation |
130
130
 
131
131
  ```typescript
132
- // UUID (default recommendation)
132
+ // UUID (default for distributed/public IDs)
133
133
  id: uuid("id").defaultRandom().primaryKey(),
134
134
 
135
- // Serial for internal/analytics tables
136
- id: serial("id").primaryKey(),
135
+ // GOOD: Identity columns (PostgreSQL 10+, preferred over serial)
136
+ id: integer("id").primaryKey().generatedAlwaysAsIdentity(),
137
+
138
+ // For bigint IDs
139
+ id: bigint("id", { mode: "number" }).primaryKey().generatedAlwaysAsIdentity(),
140
+
141
+ // ❌ AVOID: Serial (legacy, has sequence ownership issues)
142
+ id: serial("id").primaryKey(), // Use identity instead
137
143
 
138
144
  // ULID for sortable + random
139
145
  id: varchar("id", { length: 26 }).primaryKey().$default(() => ulid()),
140
146
  ```
141
147
 
148
+ **Why Identity over Serial:**
149
+ - SQL standard (serial is PostgreSQL-specific)
150
+ - No sequence ownership issues during migrations
151
+ - Better `COPY` and `pg_dump` behavior
152
+ - Cannot be overwritten accidentally (`GENERATED ALWAYS`)
153
+
154
+ ### 7. Full-Text Search with GIN
155
+
156
+ For searchable text columns, use PostgreSQL's built-in full-text search:
157
+
158
+ ```typescript
159
+ import { sql } from "drizzle-orm";
160
+ import { index, pgTable, text, tsvector } from "drizzle-orm/pg-core";
161
+
162
+ export const products = pgTable("products", {
163
+ id: uuid("id").defaultRandom().primaryKey(),
164
+ name: varchar("name", { length: 255 }).notNull(),
165
+ description: text("description"),
166
+ // Generated tsvector column for search
167
+ searchVector: tsvector("search_vector").generatedAlwaysAs(
168
+ sql`to_tsvector('english', coalesce(${products.name}, '') || ' ' || coalesce(${products.description}, ''))`
169
+ ),
170
+ }, (table) => ({
171
+ // GIN index for fast full-text search
172
+ searchIdx: index("products_search_idx").using("gin", table.searchVector),
173
+ }));
174
+
175
+ // Query with full-text search
176
+ const results = await db
177
+ .select()
178
+ .from(products)
179
+ .where(sql`${products.searchVector} @@ plainto_tsquery('english', ${query})`);
180
+ ```
181
+
182
+ **Key patterns:**
183
+ - Use `tsvector` for searchable content
184
+ - `generatedAlwaysAs` auto-updates on row changes
185
+ - GIN index makes searches fast (vs sequential scan)
186
+ - `plainto_tsquery` for simple user queries
187
+
142
188
  ## Migration Strategy
143
189
 
144
190
  ### Generate vs Push
@@ -429,11 +475,14 @@ if (duration > 100) {
429
475
 
430
476
  - [Drizzle ORM Documentation](https://orm.drizzle.team/docs/overview) - Relations, prepared statements, migrations
431
477
  - [PostgreSQL Indexes](https://www.postgresql.org/docs/current/indexes.html) - Index types and usage
478
+ - [PostgreSQL Full-Text Search](https://www.postgresql.org/docs/current/textsearch.html) - tsvector, GIN indexes
432
479
  - [Drizzle Kit](https://orm.drizzle.team/docs/kit-overview) - Migration management
433
480
 
434
481
  **Version Notes:**
435
- - Drizzle ORM 0.30+: Improved relations API
482
+ - Drizzle ORM 0.30+: Improved relations API, identity column support
436
483
  - Drizzle Kit 0.20+: Enhanced migration generation
484
+ - PostgreSQL 10+: Identity columns (prefer over serial)
485
+ - PostgreSQL 15+: Improved JSON and full-text performance
437
486
 
438
487
  ## Red Flags - STOP and Add Indexes
439
488
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: react
3
- description: Use when writing React components, using hooks, handling state, working with Next.js/Remix/Vite/Astro, or when components feel bloated, have unnecessary re-renders, or violate single responsibility
3
+ description: Use when writing React components, creating hooks, using useState/useEffect/useReducer, building Next.js pages, implementing Server Components or Client Components, working with Remix loaders, Astro islands, or Vite SPA. Use for React performance issues, re-render problems, or component refactoring.
4
4
  ---
5
5
 
6
6
  # React Development
@@ -229,6 +229,60 @@ export function SignupForm() {
229
229
 
230
230
  ## React 19 Features
231
231
 
232
+ ### React Compiler (Auto-Memoization)
233
+
234
+ React Compiler automatically adds memoization - no more manual `useMemo`, `useCallback`, or `React.memo`.
235
+
236
+ ```typescript
237
+ // ❌ OLD: Manual memoization everywhere
238
+ const MemoizedComponent = React.memo(({ user }) => {
239
+ const formattedName = useMemo(() => formatName(user), [user]);
240
+ const handleClick = useCallback(() => saveUser(user), [user]);
241
+ return <button onClick={handleClick}>{formattedName}</button>;
242
+ });
243
+
244
+ // ✅ NEW: React Compiler handles it automatically
245
+ function UserButton({ user }) {
246
+ const formattedName = formatName(user);
247
+ const handleClick = () => saveUser(user);
248
+ return <button onClick={handleClick}>{formattedName}</button>;
249
+ }
250
+ // Compiler adds memoization during build - 25-40% fewer re-renders
251
+ ```
252
+
253
+ **Key benefits:**
254
+ - No manual memoization needed
255
+ - Reduces bundle size (no memo wrappers)
256
+ - 25-40% fewer re-renders in typical apps
257
+ - Works with existing React 19 codebases
258
+
259
+ **Enable in Next.js 15+:**
260
+ ```javascript
261
+ // next.config.js
262
+ module.exports = {
263
+ experimental: {
264
+ reactCompiler: true,
265
+ },
266
+ };
267
+ ```
268
+
269
+ ### Server/Client Component Decision Tree
270
+
271
+ ```
272
+ Need this in the component?
273
+ ├── Event handlers (onClick, onChange) → 'use client'
274
+ ├── useState, useEffect, useReducer → 'use client'
275
+ ├── Browser APIs (localStorage, window) → 'use client'
276
+ ├── Third-party client libs (charts, maps) → 'use client'
277
+
278
+ ├── Data fetching from DB/API → Server Component ✅
279
+ ├── Access backend resources → Server Component ✅
280
+ ├── Keep sensitive data (tokens, keys) → Server Component ✅
281
+ └── Reduce client bundle → Server Component ✅
282
+ ```
283
+
284
+ **Rule of thumb:** Start with Server Components. Only add 'use client' when you hit a boundary that requires it.
285
+
232
286
  ### use() Hook - Read Promises/Context
233
287
 
234
288
  ```typescript
@@ -279,6 +333,59 @@ function TodoList({ todos }: { todos: Todo[] }) {
279
333
 
280
334
  **Auto-reverts on error** - no manual rollback needed.
281
335
 
336
+ ### Partial Pre-rendering (PPR) - Next.js 15+
337
+
338
+ Combine static shell with dynamic content in a single request:
339
+
340
+ ```typescript
341
+ // app/products/[id]/page.tsx
342
+ import { Suspense } from 'react';
343
+
344
+ // Static shell - pre-rendered at build time
345
+ export default async function ProductPage({ params }) {
346
+ const product = await getProduct(params.id); // Cached/static
347
+
348
+ return (
349
+ <div>
350
+ {/* Static content - instant load */}
351
+ <h1>{product.name}</h1>
352
+ <p>{product.description}</p>
353
+
354
+ {/* Dynamic content - streams in */}
355
+ <Suspense fallback={<PriceSkeleton />}>
356
+ <DynamicPrice productId={params.id} />
357
+ </Suspense>
358
+
359
+ <Suspense fallback={<InventorySkeleton />}>
360
+ <InventoryStatus productId={params.id} />
361
+ </Suspense>
362
+ </div>
363
+ );
364
+ }
365
+
366
+ // Dynamic component - fetches real-time data
367
+ async function DynamicPrice({ productId }) {
368
+ const price = await getCurrentPrice(productId); // Always fresh
369
+ return <span className="price">${price}</span>;
370
+ }
371
+ ```
372
+
373
+ **Enable PPR:**
374
+ ```javascript
375
+ // next.config.js
376
+ module.exports = {
377
+ experimental: {
378
+ ppr: true,
379
+ },
380
+ };
381
+ ```
382
+
383
+ **Benefits:**
384
+ - Static shell loads instantly (like SSG)
385
+ - Dynamic parts stream in (like SSR)
386
+ - Best of both worlds in one request
387
+ - No full-page waterfall
388
+
282
389
  ## Streaming & Suspense
283
390
 
284
391
  ### Full Page Streaming with loading.tsx
@@ -384,12 +491,15 @@ test('shows fallback then content', async () => {
384
491
 
385
492
  - [Next.js Documentation](https://nextjs.org/docs) - App Router, Server Components, Server Actions
386
493
  - [React 19 Release](https://react.dev/blog/2024/12/05/react-19) - use(), useActionState, useOptimistic
494
+ - [React Compiler](https://react.dev/learn/react-compiler) - Auto-memoization setup and usage
387
495
  - [React Server Components](https://react.dev/reference/rsc/server-components) - Official RSC guide
388
496
 
389
497
  **Version Notes:**
390
498
  - Next.js 14+: App Router stable, Server Actions stable
391
- - Next.js 15: Enhanced caching, Turbopack, React 19 support
499
+ - Next.js 15: Turbopack stable, React Compiler support, PPR experimental
392
500
  - React 19: use(), Actions, useOptimistic, useActionState
501
+ - React 19.2+: Partial Pre-rendering (PPR), enhanced Suspense
502
+ - React Compiler: Auto-memoization, 25-40% fewer re-renders
393
503
 
394
504
  ## Red Flags - STOP and Restructure
395
505
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: seo
3
- description: Use when creating web pages, adding metadata, optimizing Core Web Vitals, implementing structured data, or when pages aren't ranking or showing rich snippets in Google
3
+ description: Use when adding page metadata, implementing OpenGraph tags, creating JSON-LD structured data, generating sitemaps, optimizing LCP/INP/CLS, or configuring robots.txt. Use for Next.js Metadata API, Article/Product/FAQ schemas, or image optimization with next/image.
4
4
  ---
5
5
 
6
6
  # SEO Optimization
@@ -128,6 +128,35 @@ input.addEventListener('input', (e) => {
128
128
 
129
129
  ### 2. Use Next.js Metadata API Correctly
130
130
 
131
+ #### Root Layout: metadataBase + title.template
132
+
133
+ **Critical:** Set `metadataBase` in your root layout. Without it, relative OG image URLs break.
134
+
135
+ ```typescript
136
+ // app/layout.tsx
137
+ import { Metadata } from 'next';
138
+
139
+ export const metadata: Metadata = {
140
+ metadataBase: new URL('https://site.com'), // REQUIRED for OG images
141
+ title: {
142
+ default: 'Brand Name',
143
+ template: '%s | Brand Name', // Auto-appends to all pages
144
+ },
145
+ description: 'Default site description',
146
+ openGraph: {
147
+ siteName: 'Brand Name',
148
+ locale: 'en_US',
149
+ type: 'website',
150
+ },
151
+ };
152
+ ```
153
+
154
+ **Why metadataBase matters:**
155
+ - Without it: `images: ['/og.png']` → broken URL
156
+ - With it: `images: ['/og.png']` → `https://site.com/og.png`
157
+
158
+ #### Page-Level Metadata
159
+
131
160
  ```typescript
132
161
  // app/products/[slug]/page.tsx
133
162
  import { Metadata } from 'next';
@@ -136,11 +165,11 @@ export async function generateMetadata({ params }): Promise<Metadata> {
136
165
  const product = await getProduct(params.slug);
137
166
 
138
167
  return {
139
- title: `${product.name} | Brand - $${product.price}`,
168
+ title: product.name, // Becomes "Product Name | Brand Name" via template
140
169
  description: truncate(product.description, 155), // Max 155 chars
141
170
 
142
171
  alternates: {
143
- canonical: `https://site.com/products/${product.slug}`,
172
+ canonical: `/products/${product.slug}`, // Relative OK with metadataBase
144
173
  },
145
174
 
146
175
  // OpenGraph - use 'product' for e-commerce
@@ -278,6 +307,87 @@ const faqJsonLd = {
278
307
  };
279
308
  ```
280
309
 
310
+ #### BreadcrumbList Schema
311
+
312
+ Shows breadcrumb trail in search results:
313
+
314
+ ```typescript
315
+ const breadcrumbJsonLd = {
316
+ '@context': 'https://schema.org',
317
+ '@type': 'BreadcrumbList',
318
+ itemListElement: [
319
+ {
320
+ '@type': 'ListItem',
321
+ position: 1,
322
+ name: 'Home',
323
+ item: 'https://site.com',
324
+ },
325
+ {
326
+ '@type': 'ListItem',
327
+ position: 2,
328
+ name: 'Products',
329
+ item: 'https://site.com/products',
330
+ },
331
+ {
332
+ '@type': 'ListItem',
333
+ position: 3,
334
+ name: product.name,
335
+ item: `https://site.com/products/${product.slug}`,
336
+ },
337
+ ],
338
+ };
339
+ ```
340
+
341
+ ### 6. Answer Engine Optimization (AEO)
342
+
343
+ AI search engines (ChatGPT, Perplexity, Claude) are becoming traffic sources. Optimize for them:
344
+
345
+ **Key AEO strategies:**
346
+
347
+ | Strategy | Implementation |
348
+ |----------|----------------|
349
+ | **FAQ sections** | Add FAQPage schema - AI pulls from structured Q&A |
350
+ | **Direct answers** | Start content with clear, factual statements |
351
+ | **Structured data** | Schema.org markup helps AI understand content |
352
+ | **Topic authority** | Comprehensive coverage on topic clusters |
353
+ | **Citation-friendly** | Include stats, dates, sources that AI can cite |
354
+
355
+ ```typescript
356
+ // ✅ GOOD: Content structure for AI search
357
+ function ProductPage({ product }) {
358
+ return (
359
+ <>
360
+ {/* Direct answer for AI to extract */}
361
+ <p className="lead">
362
+ The {product.name} is a {product.category} that {product.keyBenefit}.
363
+ Priced at ${product.price}, it's ideal for {product.targetAudience}.
364
+ </p>
365
+
366
+ {/* FAQ section with schema */}
367
+ <section>
368
+ <h2>Frequently Asked Questions</h2>
369
+ {product.faqs.map(faq => (
370
+ <details key={faq.id}>
371
+ <summary>{faq.question}</summary>
372
+ <p>{faq.answer}</p>
373
+ </details>
374
+ ))}
375
+ </section>
376
+
377
+ {/* Structured data for both Google and AI */}
378
+ <JsonLd data={productJsonLd} />
379
+ <JsonLd data={faqJsonLd} />
380
+ </>
381
+ );
382
+ }
383
+ ```
384
+
385
+ **Why AEO matters:**
386
+ - 40% of Gen Z uses TikTok/AI for search over Google
387
+ - AI search engines cite well-structured content
388
+ - FAQ sections appear in AI answers
389
+ - Structured data = machine-readable = AI-friendly
390
+
281
391
  ## Sitemap & Robots
282
392
 
283
393
  ### Dynamic Sitemap Generation
@@ -408,13 +518,15 @@ new PerformanceObserver((list) => {
408
518
  - [Core Web Vitals INP Guide](https://web.dev/articles/inp) - Official INP optimization patterns
409
519
  - [Optimize INP](https://web.dev/articles/optimize-inp) - Three-phase optimization approach
410
520
  - [Next.js Image Component](https://nextjs.org/docs/app/api-reference/components/image) - priority, placeholder, sizes
411
- - [Next.js Metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) - generateMetadata patterns
521
+ - [Next.js Metadata API](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) - generateMetadata, metadataBase
412
522
  - [Schema.org](https://schema.org/) - Structured data vocabulary
523
+ - [Rich Results Test](https://search.google.com/test/rich-results) - Validate structured data
413
524
 
414
525
  **Version Notes:**
415
526
  - INP replaced FID as Core Web Vital (March 2024)
416
- - Next.js 15: Enhanced Image component with automatic blur
527
+ - Next.js 15: Enhanced Image component, metadataBase required for OG
417
528
  - Good INP: < 200ms (improving from 500ms → 200ms = 22% engagement boost)
529
+ - AEO emerging: AI search engines (ChatGPT, Perplexity) use structured data
418
530
 
419
531
  ## Red Flags - STOP and Fix
420
532