feat: Add 5 curated skills for Mosaic Stack

New skills:
- next-best-practices: Next.js 15+ RSC, async patterns, self-hosting (vercel-labs)
- better-auth-best-practices: Official Better-Auth with Drizzle adapter (better-auth)
- verification-before-completion: Evidence-based completion claims (obra/superpowers)
- shadcn-ui: Component patterns with Tailwind v4 adaptation note (developer-kit)
- writing-skills: TDD methodology for skill authoring (obra/superpowers)

README reorganized by category with Mosaic Stack alignment section.
Total: 9 skills (4 existing + 5 new).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-16 16:17:40 -06:00
parent b794d0ad6b
commit f6bcc86881
36 changed files with 12921 additions and 7 deletions

View File

@@ -0,0 +1,166 @@
---
name: better-auth-best-practices
description: Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.
---
# Better Auth Integration Guide
**Always consult [better-auth.com/docs](https://better-auth.com/docs) for code examples and latest API.**
Better Auth is a TypeScript-first, framework-agnostic auth framework supporting email/password, OAuth, magic links, passkeys, and more via plugins.
---
## Quick Reference
### Environment Variables
- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`
- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)
Only define `baseURL`/`secret` in config if env vars are NOT set.
### File Location
CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.
### CLI Commands
- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
**Re-run after adding/changing plugins.**
---
## Core Config Options
| Option | Notes |
|--------|-------|
| `appName` | Optional display name |
| `baseURL` | Only if `BETTER_AUTH_URL` not set |
| `basePath` | Default `/api/auth`. Set `/` for root. |
| `secret` | Only if `BETTER_AUTH_SECRET` not set |
| `database` | Required for most features. See adapters docs. |
| `secondaryStorage` | Redis/KV for sessions & rate limits |
| `emailAndPassword` | `{ enabled: true }` to activate |
| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |
| `plugins` | Array of plugins |
| `trustedOrigins` | CSRF whitelist |
---
## Database
**Direct connections:** Pass `pg.Pool`, `mysql2` pool, `better-sqlite3`, or `bun:sqlite` instance.
**ORM adapters:** Import from `better-auth/adapters/drizzle`, `better-auth/adapters/prisma`, `better-auth/adapters/mongodb`.
**Critical:** Better Auth uses adapter model names, NOT underlying table names. If Prisma model is `User` mapping to table `users`, use `modelName: "user"` (Prisma reference), not `"users"`.
---
## Session Management
**Storage priority:**
1. If `secondaryStorage` defined → sessions go there (not DB)
2. Set `session.storeSessionInDatabase: true` to also persist to DB
3. No database + `cookieCache` → fully stateless mode
**Cookie cache strategies:**
- `compact` (default) - Base64url + HMAC. Smallest.
- `jwt` - Standard JWT. Readable but signed.
- `jwe` - Encrypted. Maximum security.
**Key options:** `session.expiresIn` (default 7 days), `session.updateAge` (refresh interval), `session.cookieCache.maxAge`, `session.cookieCache.version` (change to invalidate all sessions).
---
## User & Account Config
**User:** `user.modelName`, `user.fields` (column mapping), `user.additionalFields`, `user.changeEmail.enabled` (disabled by default), `user.deleteUser.enabled` (disabled by default).
**Account:** `account.modelName`, `account.accountLinking.enabled`, `account.storeAccountCookie` (for stateless OAuth).
**Required for registration:** `email` and `name` fields.
---
## Email Flows
- `emailVerification.sendVerificationEmail` - Must be defined for verification to work
- `emailVerification.sendOnSignUp` / `sendOnSignIn` - Auto-send triggers
- `emailAndPassword.sendResetPassword` - Password reset email handler
---
## Security
**In `advanced`:**
- `useSecureCookies` - Force HTTPS cookies
- `disableCSRFCheck` - ⚠️ Security risk
- `disableOriginCheck` - ⚠️ Security risk
- `crossSubDomainCookies.enabled` - Share cookies across subdomains
- `ipAddress.ipAddressHeaders` - Custom IP headers for proxies
- `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false`
**Rate limiting:** `rateLimit.enabled`, `rateLimit.window`, `rateLimit.max`, `rateLimit.storage` ("memory" | "database" | "secondary-storage").
---
## Hooks
**Endpoint hooks:** `hooks.before` / `hooks.after` - Array of `{ matcher, handler }`. Use `createAuthMiddleware`. Access `ctx.path`, `ctx.context.returned` (after), `ctx.context.session`.
**Database hooks:** `databaseHooks.user.create.before/after`, same for `session`, `account`. Useful for adding default values or post-creation actions.
**Hook context (`ctx.context`):** `session`, `secret`, `authCookies`, `password.hash()`/`verify()`, `adapter`, `internalAdapter`, `generateId()`, `tables`, `baseURL`.
---
## Plugins
**Import from dedicated paths for tree-shaking:**
```
import { twoFactor } from "better-auth/plugins/two-factor"
```
NOT `from "better-auth/plugins"`.
**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.
Client plugins go in `createAuthClient({ plugins: [...] })`.
---
## Client
Import from: `better-auth/client` (vanilla), `better-auth/react`, `better-auth/vue`, `better-auth/svelte`, `better-auth/solid`.
Key methods: `signUp.email()`, `signIn.email()`, `signIn.social()`, `signOut()`, `useSession()`, `getSession()`, `revokeSession()`, `revokeSessions()`.
---
## Type Safety
Infer types: `typeof auth.$Infer.Session`, `typeof auth.$Infer.Session.user`.
For separate client/server projects: `createAuthClient<typeof auth>()`.
---
## Common Gotchas
1. **Model vs table name** - Config uses ORM model name, not DB table name
2. **Plugin schema** - Re-run CLI after adding plugins
3. **Secondary storage** - Sessions go there by default, not DB
4. **Cookie cache** - Custom session fields NOT cached, always re-fetched
5. **Stateless mode** - No DB = session in cookie only, logout on cache expiry
6. **Change email flow** - Sends to current email first, then new email
---
## Resources
- [Docs](https://better-auth.com/docs)
- [Options Reference](https://better-auth.com/docs/reference/options)
- [LLMs.txt](https://better-auth.com/llms.txt)
- [GitHub](https://github.com/better-auth/better-auth)
- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)

View File

@@ -0,0 +1,153 @@
---
name: next-best-practices
description: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
user-invocable: false
---
# Next.js Best Practices
Apply these rules when writing or reviewing Next.js code.
## File Conventions
See [file-conventions.md](./file-conventions.md) for:
- Project structure and special files
- Route segments (dynamic, catch-all, groups)
- Parallel and intercepting routes
- Middleware rename in v16 (middleware → proxy)
## RSC Boundaries
Detect invalid React Server Component patterns.
See [rsc-boundaries.md](./rsc-boundaries.md) for:
- Async client component detection (invalid)
- Non-serializable props detection
- Server Action exceptions
## Async Patterns
Next.js 15+ async API changes.
See [async-patterns.md](./async-patterns.md) for:
- Async `params` and `searchParams`
- Async `cookies()` and `headers()`
- Migration codemod
## Runtime Selection
See [runtime-selection.md](./runtime-selection.md) for:
- Default to Node.js runtime
- When Edge runtime is appropriate
## Directives
See [directives.md](./directives.md) for:
- `'use client'`, `'use server'` (React)
- `'use cache'` (Next.js)
## Functions
See [functions.md](./functions.md) for:
- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams`
- Server functions: `cookies`, `headers`, `draftMode`, `after`
- Generate functions: `generateStaticParams`, `generateMetadata`
## Error Handling
See [error-handling.md](./error-handling.md) for:
- `error.tsx`, `global-error.tsx`, `not-found.tsx`
- `redirect`, `permanentRedirect`, `notFound`
- `forbidden`, `unauthorized` (auth errors)
- `unstable_rethrow` for catch blocks
## Data Patterns
See [data-patterns.md](./data-patterns.md) for:
- Server Components vs Server Actions vs Route Handlers
- Avoiding data waterfalls (`Promise.all`, Suspense, preload)
- Client component data fetching
## Route Handlers
See [route-handlers.md](./route-handlers.md) for:
- `route.ts` basics
- GET handler conflicts with `page.tsx`
- Environment behavior (no React DOM)
- When to use vs Server Actions
## Metadata & OG Images
See [metadata.md](./metadata.md) for:
- Static and dynamic metadata
- `generateMetadata` function
- OG image generation with `next/og`
- File-based metadata conventions
## Image Optimization
See [image.md](./image.md) for:
- Always use `next/image` over `<img>`
- Remote images configuration
- Responsive `sizes` attribute
- Blur placeholders
- Priority loading for LCP
## Font Optimization
See [font.md](./font.md) for:
- `next/font` setup
- Google Fonts, local fonts
- Tailwind CSS integration
- Preloading subsets
## Bundling
See [bundling.md](./bundling.md) for:
- Server-incompatible packages
- CSS imports (not link tags)
- Polyfills (already included)
- ESM/CommonJS issues
- Bundle analysis
## Scripts
See [scripts.md](./scripts.md) for:
- `next/script` vs native script tags
- Inline scripts need `id`
- Loading strategies
- Google Analytics with `@next/third-parties`
## Hydration Errors
See [hydration-error.md](./hydration-error.md) for:
- Common causes (browser APIs, dates, invalid HTML)
- Debugging with error overlay
- Fixes for each cause
## Suspense Boundaries
See [suspense-boundaries.md](./suspense-boundaries.md) for:
- CSR bailout with `useSearchParams` and `usePathname`
- Which hooks require Suspense boundaries
## Parallel & Intercepting Routes
See [parallel-routes.md](./parallel-routes.md) for:
- Modal patterns with `@slot` and `(.)` interceptors
- `default.tsx` for fallbacks
- Closing modals correctly with `router.back()`
## Self-Hosting
See [self-hosting.md](./self-hosting.md) for:
- `output: 'standalone'` for Docker
- Cache handlers for multi-instance ISR
- What works vs needs extra setup
## Debug Tricks
See [debug-tricks.md](./debug-tricks.md) for:
- MCP endpoint for AI-assisted debugging
- Rebuild specific routes with `--debug-build-paths`

View File

@@ -0,0 +1,87 @@
# Async Patterns
In Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous.
## Async Params and SearchParams
Always type them as `Promise<...>` and await them.
### Pages and Layouts
```tsx
type Props = { params: Promise<{ slug: string }> }
export default async function Page({ params }: Props) {
const { slug } = await params
}
```
### Route Handlers
```tsx
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
}
```
### SearchParams
```tsx
type Props = {
params: Promise<{ slug: string }>
searchParams: Promise<{ query?: string }>
}
export default async function Page({ params, searchParams }: Props) {
const { slug } = await params
const { query } = await searchParams
}
```
### Synchronous Components
Use `React.use()` for non-async components:
```tsx
import { use } from 'react'
type Props = { params: Promise<{ slug: string }> }
export default function Page({ params }: Props) {
const { slug } = use(params)
}
```
### generateMetadata
```tsx
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
return { title: slug }
}
```
## Async Cookies and Headers
```tsx
import { cookies, headers } from 'next/headers'
export default async function Page() {
const cookieStore = await cookies()
const headersList = await headers()
const theme = cookieStore.get('theme')
const userAgent = headersList.get('user-agent')
}
```
## Migration Codemod
```bash
npx @next/codemod@latest next-async-request-api .
```

View File

@@ -0,0 +1,180 @@
# Bundling
Fix common bundling issues with third-party packages.
## Server-Incompatible Packages
Some packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components.
### Error Signs
```
ReferenceError: window is not defined
ReferenceError: document is not defined
ReferenceError: localStorage is not defined
Module not found: Can't resolve 'fs'
```
### Solution 1: Mark as Client-Only
If the package is only needed on client:
```tsx
// Bad: Fails - package uses window
import SomeChart from 'some-chart-library'
export default function Page() {
return <SomeChart />
}
// Good: Use dynamic import with ssr: false
import dynamic from 'next/dynamic'
const SomeChart = dynamic(() => import('some-chart-library'), {
ssr: false,
})
export default function Page() {
return <SomeChart />
}
```
### Solution 2: Externalize from Server Bundle
For packages that should run on server but have bundling issues:
```js
// next.config.js
module.exports = {
serverExternalPackages: ['problematic-package'],
}
```
Use this for:
- Packages with native bindings (sharp, bcrypt)
- Packages that don't bundle well (some ORMs)
- Packages with circular dependencies
### Solution 3: Client Component Wrapper
Wrap the entire usage in a client component:
```tsx
// components/ChartWrapper.tsx
'use client'
import { Chart } from 'chart-library'
export function ChartWrapper(props) {
return <Chart {...props} />
}
// app/page.tsx (server component)
import { ChartWrapper } from '@/components/ChartWrapper'
export default function Page() {
return <ChartWrapper data={data} />
}
```
## CSS Imports
Import CSS files instead of using `<link>` tags. Next.js handles bundling and optimization.
```tsx
// Bad: Manual link tag
<link rel="stylesheet" href="/styles.css" />
// Good: Import CSS
import './styles.css'
// Good: CSS Modules
import styles from './Button.module.css'
```
## Polyfills
Next.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs.
Already included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others.
```tsx
// Bad: Redundant polyfills
<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,Array.from" />
// Good: Next.js includes these automatically
```
## ESM/CommonJS Issues
### Error Signs
```
SyntaxError: Cannot use import statement outside a module
Error: require() of ES Module
Module not found: ESM packages need to be imported
```
### Solution: Transpile Package
```js
// next.config.js
module.exports = {
transpilePackages: ['some-esm-package', 'another-package'],
}
```
## Common Problematic Packages
| Package | Issue | Solution |
|---------|-------|----------|
| `sharp` | Native bindings | `serverExternalPackages: ['sharp']` |
| `bcrypt` | Native bindings | `serverExternalPackages: ['bcrypt']` or use `bcryptjs` |
| `canvas` | Native bindings | `serverExternalPackages: ['canvas']` |
| `recharts` | Uses window | `dynamic(() => import('recharts'), { ssr: false })` |
| `react-quill` | Uses document | `dynamic(() => import('react-quill'), { ssr: false })` |
| `mapbox-gl` | Uses window | `dynamic(() => import('mapbox-gl'), { ssr: false })` |
| `monaco-editor` | Uses window | `dynamic(() => import('@monaco-editor/react'), { ssr: false })` |
| `lottie-web` | Uses document | `dynamic(() => import('lottie-react'), { ssr: false })` |
## Bundle Analysis
Analyze bundle size with the built-in analyzer (Next.js 16.1+):
```bash
next experimental-analyze
```
This opens an interactive UI to:
- Filter by route, environment (client/server), and type
- Inspect module sizes and import chains
- View treemap visualization
Save output for comparison:
```bash
next experimental-analyze --output
# Output saved to .next/diagnostics/analyze
```
Reference: https://nextjs.org/docs/app/guides/package-bundling
## Migrating from Webpack to Turbopack
Turbopack is the default bundler in Next.js 15+. If you have custom webpack config, migrate to Turbopack-compatible alternatives:
```js
// next.config.js
module.exports = {
// Good: Works with Turbopack
serverExternalPackages: ['package'],
transpilePackages: ['package'],
// Bad: Webpack-only - migrate away from this
webpack: (config) => {
// custom webpack config
},
}
```
Reference: https://nextjs.org/docs/app/building-your-application/upgrading/from-webpack-to-turbopack

View File

@@ -0,0 +1,297 @@
# Data Patterns
Choose the right data fetching pattern for each use case.
## Decision Tree
```
Need to fetch data?
├── From a Server Component?
│ └── Use: Fetch directly (no API needed)
├── From a Client Component?
│ ├── Is it a mutation (POST/PUT/DELETE)?
│ │ └── Use: Server Action
│ └── Is it a read (GET)?
│ └── Use: Route Handler OR pass from Server Component
├── Need external API access (webhooks, third parties)?
│ └── Use: Route Handler
└── Need REST API for mobile app / external clients?
└── Use: Route Handler
```
## Pattern 1: Server Components (Preferred for Reads)
Fetch data directly in Server Components - no API layer needed.
```tsx
// app/users/page.tsx
async function UsersPage() {
// Direct database access - no API round-trip
const users = await db.user.findMany();
// Or fetch from external API
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return (
<ul>
{users.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
```
**Benefits**:
- No API to maintain
- No client-server waterfall
- Secrets stay on server
- Direct database access
## Pattern 2: Server Actions (Preferred for Mutations)
Server Actions are the recommended way to handle mutations.
```tsx
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
await db.post.create({ data: { title } });
revalidatePath('/posts');
}
export async function deletePost(id: string) {
await db.post.delete({ where: { id } });
revalidateTag('posts');
}
```
```tsx
// app/posts/new/page.tsx
import { createPost } from '@/app/actions';
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Create</button>
</form>
);
}
```
**Benefits**:
- End-to-end type safety
- Progressive enhancement (works without JS)
- Automatic request handling
- Integrated with React transitions
**Constraints**:
- POST only (no GET caching semantics)
- Internal use only (no external access)
- Cannot return non-serializable data
## Pattern 3: Route Handlers (APIs)
Use Route Handlers when you need a REST API.
```tsx
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
// GET is cacheable
export async function GET(request: NextRequest) {
const posts = await db.post.findMany();
return NextResponse.json(posts);
}
// POST for mutations
export async function POST(request: NextRequest) {
const body = await request.json();
const post = await db.post.create({ data: body });
return NextResponse.json(post, { status: 201 });
}
```
**When to use**:
- External API access (mobile apps, third parties)
- Webhooks from external services
- GET endpoints that need HTTP caching
- OpenAPI/Swagger documentation needed
**When NOT to use**:
- Internal data fetching (use Server Components)
- Mutations from your UI (use Server Actions)
## Avoiding Data Waterfalls
### Problem: Sequential Fetches
```tsx
// Bad: Sequential waterfalls
async function Dashboard() {
const user = await getUser(); // Wait...
const posts = await getPosts(); // Then wait...
const comments = await getComments(); // Then wait...
return <div>...</div>;
}
```
### Solution 1: Parallel Fetching with Promise.all
```tsx
// Good: Parallel fetching
async function Dashboard() {
const [user, posts, comments] = await Promise.all([
getUser(),
getPosts(),
getComments(),
]);
return <div>...</div>;
}
```
### Solution 2: Streaming with Suspense
```tsx
// Good: Show content progressively
import { Suspense } from 'react';
async function Dashboard() {
return (
<div>
<Suspense fallback={<UserSkeleton />}>
<UserSection />
</Suspense>
<Suspense fallback={<PostsSkeleton />}>
<PostsSection />
</Suspense>
</div>
);
}
async function UserSection() {
const user = await getUser(); // Fetches independently
return <div>{user.name}</div>;
}
async function PostsSection() {
const posts = await getPosts(); // Fetches independently
return <PostList posts={posts} />;
}
```
### Solution 3: Preload Pattern
```tsx
// lib/data.ts
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
export const preloadUser = (id: string) => {
void getUser(id); // Fire and forget
};
```
```tsx
// app/user/[id]/page.tsx
import { getUser, preloadUser } from '@/lib/data';
export default async function UserPage({ params }) {
const { id } = await params;
// Start fetching early
preloadUser(id);
// Do other work...
// Data likely ready by now
const user = await getUser(id);
return <div>{user.name}</div>;
}
```
## Client Component Data Fetching
When Client Components need data:
### Option 1: Pass from Server Component (Preferred)
```tsx
// Server Component
async function Page() {
const data = await fetchData();
return <ClientComponent initialData={data} />;
}
// Client Component
'use client';
function ClientComponent({ initialData }) {
const [data, setData] = useState(initialData);
// ...
}
```
### Option 2: Fetch on Mount (When Necessary)
```tsx
'use client';
import { useEffect, useState } from 'react';
function ClientComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data')
.then(r => r.json())
.then(setData);
}, []);
if (!data) return <Loading />;
return <div>{data.value}</div>;
}
```
### Option 3: Server Action for Reads (Works But Not Ideal)
Server Actions can be called from Client Components for reads, but this is not their intended purpose:
```tsx
'use client';
import { getData } from './actions';
import { useEffect, useState } from 'react';
function ClientComponent() {
const [data, setData] = useState(null);
useEffect(() => {
getData().then(setData);
}, []);
return <div>{data?.value}</div>;
}
```
**Note**: Server Actions always use POST, so no HTTP caching. Prefer Route Handlers for cacheable reads.
## Quick Reference
| Pattern | Use Case | HTTP Method | Caching |
|---------|----------|-------------|---------|
| Server Component fetch | Internal reads | Any | Full Next.js caching |
| Server Action | Mutations, form submissions | POST only | No |
| Route Handler | External APIs, webhooks | Any | GET can be cached |
| Client fetch to API | Client-side reads | Any | HTTP cache headers |

View File

@@ -0,0 +1,105 @@
# Debug Tricks
Tricks to speed up debugging Next.js applications.
## MCP Endpoint (Dev Server)
Next.js exposes a `/_next/mcp` endpoint in development for AI-assisted debugging via MCP (Model Context Protocol).
- **Next.js 16+**: Enabled by default, use `next-devtools-mcp`
- **Next.js < 16**: Requires `experimental.mcpServer: true` in next.config.js
Reference: https://nextjs.org/docs/app/guides/mcp
**Important**: Find the actual port of the running Next.js dev server (check terminal output or `package.json` scripts). Don't assume port 3000.
### Request Format
The endpoint uses JSON-RPC 2.0 over HTTP POST:
```bash
curl -X POST http://localhost:<port>/_next/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/call",
"params": {
"name": "<tool-name>",
"arguments": {}
}
}'
```
### Available Tools
#### `get_errors`
Get current errors from dev server (build errors, runtime errors with source-mapped stacks):
```json
{ "name": "get_errors", "arguments": {} }
```
#### `get_routes`
Discover all routes by scanning filesystem:
```json
{ "name": "get_routes", "arguments": {} }
// Optional: { "name": "get_routes", "arguments": { "routerType": "app" } }
```
Returns: `{ "appRouter": ["/", "/api/users/[id]", ...], "pagesRouter": [...] }`
#### `get_project_metadata`
Get project path and dev server URL:
```json
{ "name": "get_project_metadata", "arguments": {} }
```
Returns: `{ "projectPath": "/path/to/project", "devServerUrl": "http://localhost:3000" }`
#### `get_page_metadata`
Get runtime metadata about current page render (requires active browser session):
```json
{ "name": "get_page_metadata", "arguments": {} }
```
Returns segment trie data showing layouts, boundaries, and page components.
#### `get_logs`
Get path to Next.js development log file:
```json
{ "name": "get_logs", "arguments": {} }
```
Returns path to `<distDir>/logs/next-development.log`
#### `get_server_action_by_id`
Locate a Server Action by ID:
```json
{ "name": "get_server_action_by_id", "arguments": { "actionId": "<action-id>" } }
```
### Example: Get Errors
```bash
curl -X POST http://localhost:<port>/_next/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"get_errors","arguments":{}}}'
```
## Rebuild Specific Routes (Next.js 16+)
Use `--debug-build-paths` to rebuild only specific routes instead of the entire app:
```bash
# Rebuild a specific route
next build --debug-build-paths "/dashboard"
# Rebuild routes matching a glob
next build --debug-build-paths "/api/*"
# Dynamic routes
next build --debug-build-paths "/blog/[slug]"
```
Use this to:
- Quickly verify a build fix without full rebuild
- Debug static generation issues for specific pages
- Iterate faster on build errors

View File

@@ -0,0 +1,73 @@
# Directives
## React Directives
These are React directives, not Next.js specific.
### `'use client'`
Marks a component as a Client Component. Required for:
- React hooks (`useState`, `useEffect`, etc.)
- Event handlers (`onClick`, `onChange`)
- Browser APIs (`window`, `localStorage`)
```tsx
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
```
Reference: https://react.dev/reference/rsc/use-client
### `'use server'`
Marks a function as a Server Action. Can be passed to Client Components.
```tsx
'use server'
export async function submitForm(formData: FormData) {
// Runs on server
}
```
Or inline within a Server Component:
```tsx
export default function Page() {
async function submit() {
'use server'
// Runs on server
}
return <form action={submit}>...</form>
}
```
Reference: https://react.dev/reference/rsc/use-server
---
## Next.js Directive
### `'use cache'`
Marks a function or component for caching. Part of Next.js Cache Components.
```tsx
'use cache'
export async function getCachedData() {
return await fetchData()
}
```
Requires `cacheComponents: true` in `next.config.ts`.
For detailed usage including cache profiles, `cacheLife()`, `cacheTag()`, and `updateTag()`, see the `next-cache-components` skill.
Reference: https://nextjs.org/docs/app/api-reference/directives/use-cache

View File

@@ -0,0 +1,227 @@
# Error Handling
Handle errors gracefully in Next.js applications.
Reference: https://nextjs.org/docs/app/getting-started/error-handling
## Error Boundaries
### `error.tsx`
Catches errors in a route segment and its children:
```tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
```
**Important:** `error.tsx` must be a Client Component.
### `global-error.tsx`
Catches errors in root layout:
```tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
```
**Important:** Must include `<html>` and `<body>` tags.
## Server Actions: Navigation API Gotcha
**Do NOT wrap navigation APIs in try-catch.** They throw special errors that Next.js handles internally.
Reference: https://nextjs.org/docs/app/api-reference/functions/redirect#behavior
```tsx
'use server'
import { redirect } from 'next/navigation'
import { notFound } from 'next/navigation'
// Bad: try-catch catches the navigation "error"
async function createPost(formData: FormData) {
try {
const post = await db.post.create({ ... })
redirect(`/posts/${post.id}`) // This throws!
} catch (error) {
// redirect() throw is caught here - navigation fails!
return { error: 'Failed to create post' }
}
}
// Good: Call navigation APIs outside try-catch
async function createPost(formData: FormData) {
let post
try {
post = await db.post.create({ ... })
} catch (error) {
return { error: 'Failed to create post' }
}
redirect(`/posts/${post.id}`) // Outside try-catch
}
// Good: Re-throw navigation errors
async function createPost(formData: FormData) {
try {
const post = await db.post.create({ ... })
redirect(`/posts/${post.id}`)
} catch (error) {
if (error instanceof Error && error.message === 'NEXT_REDIRECT') {
throw error // Re-throw navigation errors
}
return { error: 'Failed to create post' }
}
}
```
Same applies to:
- `redirect()` - 307 temporary redirect
- `permanentRedirect()` - 308 permanent redirect
- `notFound()` - 404 not found
- `forbidden()` - 403 forbidden
- `unauthorized()` - 401 unauthorized
Use `unstable_rethrow()` to re-throw these errors in catch blocks:
```tsx
import { unstable_rethrow } from 'next/navigation'
async function action() {
try {
// ...
redirect('/success')
} catch (error) {
unstable_rethrow(error) // Re-throws Next.js internal errors
return { error: 'Something went wrong' }
}
}
```
## Redirects
```tsx
import { redirect, permanentRedirect } from 'next/navigation'
// 307 Temporary - use for most cases
redirect('/new-path')
// 308 Permanent - use for URL migrations (cached by browsers)
permanentRedirect('/new-url')
```
## Auth Errors
Trigger auth-related error pages:
```tsx
import { forbidden, unauthorized } from 'next/navigation'
async function Page() {
const session = await getSession()
if (!session) {
unauthorized() // Renders unauthorized.tsx (401)
}
if (!session.hasAccess) {
forbidden() // Renders forbidden.tsx (403)
}
return <Dashboard />
}
```
Create corresponding error pages:
```tsx
// app/forbidden.tsx
export default function Forbidden() {
return <div>You don't have access to this resource</div>
}
// app/unauthorized.tsx
export default function Unauthorized() {
return <div>Please log in to continue</div>
}
```
## Not Found
### `not-found.tsx`
Custom 404 page for a route segment:
```tsx
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find the requested resource</p>
</div>
)
}
```
### Triggering Not Found
```tsx
import { notFound } from 'next/navigation'
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const post = await getPost(id)
if (!post) {
notFound() // Renders closest not-found.tsx
}
return <div>{post.title}</div>
}
```
## Error Hierarchy
Errors bubble up to the nearest error boundary:
```
app/
├── error.tsx # Catches errors from all children
├── blog/
│ ├── error.tsx # Catches errors in /blog/*
│ └── [slug]/
│ ├── error.tsx # Catches errors in /blog/[slug]
│ └── page.tsx
└── layout.tsx # Errors here go to global-error.tsx
```

View File

@@ -0,0 +1,140 @@
# File Conventions
Next.js App Router uses file-based routing with special file conventions.
## Project Structure
Reference: https://nextjs.org/docs/app/getting-started/project-structure
```
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI
├── error.tsx # Error UI
├── not-found.tsx # 404 UI
├── global-error.tsx # Global error UI
├── route.ts # API endpoint
├── template.tsx # Re-rendered layout
├── default.tsx # Parallel route fallback
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── (group)/ # Route group (no URL impact)
└── page.tsx
```
## Special Files
| File | Purpose |
|------|---------|
| `page.tsx` | UI for a route segment |
| `layout.tsx` | Shared UI for segment and children |
| `loading.tsx` | Loading UI (Suspense boundary) |
| `error.tsx` | Error UI (Error boundary) |
| `not-found.tsx` | 404 UI |
| `route.ts` | API endpoint |
| `template.tsx` | Like layout but re-renders on navigation |
| `default.tsx` | Fallback for parallel routes |
## Route Segments
```
app/
├── blog/ # Static segment: /blog
├── [slug]/ # Dynamic segment: /:slug
├── [...slug]/ # Catch-all: /a/b/c
├── [[...slug]]/ # Optional catch-all: / or /a/b/c
└── (marketing)/ # Route group (ignored in URL)
```
## Parallel Routes
```
app/
├── @analytics/
│ └── page.tsx
├── @sidebar/
│ └── page.tsx
└── layout.tsx # Receives { analytics, sidebar } as props
```
## Intercepting Routes
```
app/
├── feed/
│ └── page.tsx
├── @modal/
│ └── (.)photo/[id]/ # Intercepts /photo/[id] from /feed
│ └── page.tsx
└── photo/[id]/
└── page.tsx
```
Conventions:
- `(.)` - same level
- `(..)` - one level up
- `(..)(..)` - two levels up
- `(...)` - from root
## Private Folders
```
app/
├── _components/ # Private folder (not a route)
│ └── Button.tsx
└── page.tsx
```
Prefix with `_` to exclude from routing.
## Middleware / Proxy
### Next.js 14-15: `middleware.ts`
```ts
// middleware.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Auth, redirects, rewrites, etc.
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
```
### Next.js 16+: `proxy.ts`
Renamed for clarity - same capabilities, different names:
```ts
// proxy.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function proxy(request: NextRequest) {
// Same logic as middleware
return NextResponse.next();
}
export const proxyConfig = {
matcher: ['/dashboard/:path*', '/api/:path*'],
};
```
| Version | File | Export | Config |
|---------|------|--------|--------|
| v14-15 | `middleware.ts` | `middleware()` | `config` |
| v16+ | `proxy.ts` | `proxy()` | `proxyConfig` |
**Migration**: Run `npx @next/codemod@latest upgrade` to auto-rename.
## File Conventions Reference
Reference: https://nextjs.org/docs/app/api-reference/file-conventions

View File

@@ -0,0 +1,245 @@
# Font Optimization
Use `next/font` for automatic font optimization with zero layout shift.
## Google Fonts
```tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
)
}
```
## Multiple Fonts
```tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
```
Use in CSS:
```css
body {
font-family: var(--font-inter);
}
code {
font-family: var(--font-roboto-mono);
}
```
## Font Weights and Styles
```tsx
// Single weight
const inter = Inter({
subsets: ['latin'],
weight: '400',
})
// Multiple weights
const inter = Inter({
subsets: ['latin'],
weight: ['400', '500', '700'],
})
// Variable font (recommended) - includes all weights
const inter = Inter({
subsets: ['latin'],
// No weight needed - variable fonts support all weights
})
// With italic
const inter = Inter({
subsets: ['latin'],
style: ['normal', 'italic'],
})
```
## Local Fonts
```tsx
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/MyFont.woff2',
})
// Multiple files for different weights
const myFont = localFont({
src: [
{
path: './fonts/MyFont-Regular.woff2',
weight: '400',
style: 'normal',
},
{
path: './fonts/MyFont-Bold.woff2',
weight: '700',
style: 'normal',
},
],
})
// Variable font
const myFont = localFont({
src: './fonts/MyFont-Variable.woff2',
variable: '--font-my-font',
})
```
## Tailwind CSS Integration
```tsx
// app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}
```
```js
// tailwind.config.js
module.exports = {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-inter)'],
},
},
},
}
```
## Preloading Subsets
Only load needed character subsets:
```tsx
// Latin only (most common)
const inter = Inter({ subsets: ['latin'] })
// Multiple subsets
const inter = Inter({ subsets: ['latin', 'latin-ext', 'cyrillic'] })
```
## Display Strategy
Control font loading behavior:
```tsx
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Default - shows fallback, swaps when loaded
})
// Options:
// 'auto' - browser decides
// 'block' - short block period, then swap
// 'swap' - immediate fallback, swap when ready (recommended)
// 'fallback' - short block, short swap, then fallback
// 'optional' - short block, no swap (use if font is optional)
```
## Don't Use Manual Font Links
Always use `next/font` instead of `<link>` tags for Google Fonts.
```tsx
// Bad: Manual link tag (blocks rendering, no optimization)
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />
// Bad: Missing display and preconnect
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />
// Good: Use next/font (self-hosted, zero layout shift)
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
```
## Common Mistakes
```tsx
// Bad: Importing font in every component
// components/Button.tsx
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] }) // Creates new instance each time!
// Good: Import once in layout, use CSS variable
// app/layout.tsx
const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
// Bad: Using @import in CSS (blocks rendering)
/* globals.css */
@import url('https://fonts.googleapis.com/css2?family=Inter');
// Good: Use next/font (self-hosted, no network request)
import { Inter } from 'next/font/google'
// Bad: Loading all weights when only using a few
const inter = Inter({ subsets: ['latin'] }) // Loads all weights
// Good: Specify only needed weights (for non-variable fonts)
const inter = Inter({ subsets: ['latin'], weight: ['400', '700'] })
// Bad: Missing subset - loads all characters
const inter = Inter({})
// Good: Always specify subset
const inter = Inter({ subsets: ['latin'] })
```
## Font in Specific Components
```tsx
// For component-specific fonts, export from a shared file
// lib/fonts.ts
import { Inter, Playfair_Display } from 'next/font/google'
export const inter = Inter({ subsets: ['latin'], variable: '--font-inter' })
export const playfair = Playfair_Display({ subsets: ['latin'], variable: '--font-playfair' })
// components/Heading.tsx
import { playfair } from '@/lib/fonts'
export function Heading({ children }) {
return <h1 className={playfair.className}>{children}</h1>
}
```

View File

@@ -0,0 +1,108 @@
# Functions
Next.js function APIs.
Reference: https://nextjs.org/docs/app/api-reference/functions
## Navigation Hooks (Client)
| Hook | Purpose | Reference |
|------|---------|-----------|
| `useRouter` | Programmatic navigation (`push`, `replace`, `back`, `refresh`) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-router) |
| `usePathname` | Get current pathname | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-pathname) |
| `useSearchParams` | Read URL search parameters | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-search-params) |
| `useParams` | Access dynamic route parameters | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-params) |
| `useSelectedLayoutSegment` | Active child segment (one level) | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment) |
| `useSelectedLayoutSegments` | All active segments below layout | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments) |
| `useLinkStatus` | Check link prefetch status | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-link-status) |
| `useReportWebVitals` | Report Core Web Vitals metrics | [Docs](https://nextjs.org/docs/app/api-reference/functions/use-report-web-vitals) |
## Server Functions
| Function | Purpose | Reference |
|----------|---------|-----------|
| `cookies` | Read/write cookies | [Docs](https://nextjs.org/docs/app/api-reference/functions/cookies) |
| `headers` | Read request headers | [Docs](https://nextjs.org/docs/app/api-reference/functions/headers) |
| `draftMode` | Enable preview of unpublished CMS content | [Docs](https://nextjs.org/docs/app/api-reference/functions/draft-mode) |
| `after` | Run code after response finishes streaming | [Docs](https://nextjs.org/docs/app/api-reference/functions/after) |
| `connection` | Wait for connection before dynamic rendering | [Docs](https://nextjs.org/docs/app/api-reference/functions/connection) |
| `userAgent` | Parse User-Agent header | [Docs](https://nextjs.org/docs/app/api-reference/functions/userAgent) |
## Generate Functions
| Function | Purpose | Reference |
|----------|---------|-----------|
| `generateStaticParams` | Pre-render dynamic routes at build time | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) |
| `generateMetadata` | Dynamic metadata | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) |
| `generateViewport` | Dynamic viewport config | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-viewport) |
| `generateSitemaps` | Multiple sitemaps for large sites | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps) |
| `generateImageMetadata` | Multiple OG images per route | [Docs](https://nextjs.org/docs/app/api-reference/functions/generate-image-metadata) |
## Request/Response
| Function | Purpose | Reference |
|----------|---------|-----------|
| `NextRequest` | Extended Request with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-request) |
| `NextResponse` | Extended Response with helpers | [Docs](https://nextjs.org/docs/app/api-reference/functions/next-response) |
| `ImageResponse` | Generate OG images | [Docs](https://nextjs.org/docs/app/api-reference/functions/image-response) |
## Common Examples
### Navigation
Use `next/link` for internal navigation instead of `<a>` tags.
```tsx
// Bad: Plain anchor tag
<a href="/about">About</a>
// Good: Next.js Link
import Link from 'next/link'
<Link href="/about">About</Link>
```
Active link styling:
```tsx
'use client'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
export function NavLink({ href, children }) {
const pathname = usePathname()
return (
<Link href={href} className={pathname === href ? 'active' : ''}>
{children}
</Link>
)
}
```
### Static Generation
```tsx
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}
```
### After Response
```tsx
import { after } from 'next/server'
export async function POST(request: Request) {
const data = await processRequest(request)
after(async () => {
await logAnalytics(data)
})
return Response.json({ success: true })
}
```

View File

@@ -0,0 +1,91 @@
# Hydration Errors
Diagnose and fix React hydration mismatch errors.
## Error Signs
- "Hydration failed because the initial UI does not match"
- "Text content does not match server-rendered HTML"
## Debugging
In development, click the hydration error to see the server/client diff.
## Common Causes and Fixes
### Browser-only APIs
```tsx
// Bad: Causes mismatch - window doesn't exist on server
<div>{window.innerWidth}</div>
// Good: Use client component with mounted check
'use client'
import { useState, useEffect } from 'react'
export function ClientOnly({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
return mounted ? children : null
}
```
### Date/Time Rendering
Server and client may be in different timezones:
```tsx
// Bad: Causes mismatch
<span>{new Date().toLocaleString()}</span>
// Good: Render on client only
'use client'
const [time, setTime] = useState<string>()
useEffect(() => setTime(new Date().toLocaleString()), [])
```
### Random Values or IDs
```tsx
// Bad: Random values differ between server and client
<div id={Math.random().toString()}>
// Good: Use useId hook
import { useId } from 'react'
function Input() {
const id = useId()
return <input id={id} />
}
```
### Invalid HTML Nesting
```tsx
// Bad: Invalid - div inside p
<p><div>Content</div></p>
// Bad: Invalid - p inside p
<p><p>Nested</p></p>
// Good: Valid nesting
<div><p>Content</p></div>
```
### Third-party Scripts
Scripts that modify DOM during hydration.
```tsx
// Good: Use next/script with afterInteractive
import Script from 'next/script'
export default function Page() {
return (
<Script
src="https://example.com/script.js"
strategy="afterInteractive"
/>
)
}
```

View File

@@ -0,0 +1,173 @@
# Image Optimization
Use `next/image` for automatic image optimization.
## Always Use next/image
```tsx
// Bad: Avoid native img
<img src="/hero.png" alt="Hero" />
// Good: Use next/image
import Image from 'next/image'
<Image src="/hero.png" alt="Hero" width={800} height={400} />
```
## Required Props
Images need explicit dimensions to prevent layout shift:
```tsx
// Local images - dimensions inferred automatically
import heroImage from './hero.png'
<Image src={heroImage} alt="Hero" />
// Remote images - must specify width/height
<Image src="https://example.com/image.jpg" alt="Hero" width={800} height={400} />
// Or use fill for parent-relative sizing
<div style={{ position: 'relative', width: '100%', height: 400 }}>
<Image src="/hero.png" alt="Hero" fill style={{ objectFit: 'cover' }} />
</div>
```
## Remote Images Configuration
Remote domains must be configured in `next.config.js`:
```js
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com',
pathname: '/images/**',
},
{
protocol: 'https',
hostname: '*.cdn.com', // Wildcard subdomain
},
],
},
}
```
## Responsive Images
Use `sizes` to tell the browser which size to download:
```tsx
// Full-width hero
<Image
src="/hero.png"
alt="Hero"
fill
sizes="100vw"
/>
// Responsive grid (3 columns on desktop, 1 on mobile)
<Image
src="/card.png"
alt="Card"
fill
sizes="(max-width: 768px) 100vw, 33vw"
/>
// Fixed sidebar image
<Image
src="/avatar.png"
alt="Avatar"
width={200}
height={200}
sizes="200px"
/>
```
## Blur Placeholder
Prevent layout shift with placeholders:
```tsx
// Local images - automatic blur hash
import heroImage from './hero.png'
<Image src={heroImage} alt="Hero" placeholder="blur" />
// Remote images - provide blurDataURL
<Image
src="https://example.com/image.jpg"
alt="Hero"
width={800}
height={400}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
/>
// Or use color placeholder
<Image
src="https://example.com/image.jpg"
alt="Hero"
width={800}
height={400}
placeholder="empty"
style={{ backgroundColor: '#e0e0e0' }}
/>
```
## Priority Loading
Use `priority` for above-the-fold images (LCP):
```tsx
// Hero image - loads immediately
<Image src="/hero.png" alt="Hero" fill priority />
// Below-fold images - lazy loaded by default (no priority needed)
<Image src="/card.png" alt="Card" width={400} height={300} />
```
## Common Mistakes
```tsx
// Bad: Missing sizes with fill - downloads largest image
<Image src="/hero.png" alt="Hero" fill />
// Good: Add sizes for proper responsive behavior
<Image src="/hero.png" alt="Hero" fill sizes="100vw" />
// Bad: Using width/height for aspect ratio only
<Image src="/hero.png" alt="Hero" width={16} height={9} />
// Good: Use actual display dimensions or fill with sizes
<Image src="/hero.png" alt="Hero" fill sizes="100vw" style={{ objectFit: 'cover' }} />
// Bad: Remote image without config
<Image src="https://untrusted.com/image.jpg" alt="Image" width={400} height={300} />
// Error: Invalid src prop, hostname not configured
// Good: Add hostname to next.config.js remotePatterns
```
## Static Export
When using `output: 'export'`, use `unoptimized` or custom loader:
```tsx
// Option 1: Disable optimization
<Image src="/hero.png" alt="Hero" width={800} height={400} unoptimized />
// Option 2: Global config
// next.config.js
module.exports = {
output: 'export',
images: { unoptimized: true },
}
// Option 3: Custom loader (Cloudinary, Imgix, etc.)
const cloudinaryLoader = ({ src, width, quality }) => {
return `https://res.cloudinary.com/demo/image/upload/w_${width},q_${quality || 75}/${src}`
}
<Image loader={cloudinaryLoader} src="sample.jpg" alt="Sample" width={800} height={400} />
```

View File

@@ -0,0 +1,301 @@
# Metadata
Add SEO metadata to Next.js pages using the Metadata API.
## Important: Server Components Only
The `metadata` object and `generateMetadata` function are **only supported in Server Components**. They cannot be used in Client Components.
If the target page has `'use client'`:
1. Remove `'use client'` if possible, move client logic to child components
2. Or extract metadata to a parent Server Component layout
3. Or split the file: Server Component with metadata imports Client Components
## Static Metadata
```tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Page Title',
description: 'Page description for search engines',
}
```
## Dynamic Metadata
```tsx
import type { Metadata } from 'next'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return { title: post.title, description: post.description }
}
```
## Avoid Duplicate Fetches
Use React `cache()` when the same data is needed for both metadata and page:
```tsx
import { cache } from 'react'
export const getPost = cache(async (slug: string) => {
return await db.posts.findFirst({ where: { slug } })
})
```
## Viewport
Separate from metadata for streaming support:
```tsx
import type { Viewport } from 'next'
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
themeColor: '#000000',
}
// Or dynamic
export function generateViewport({ params }): Viewport {
return { themeColor: getThemeColor(params) }
}
```
## Title Templates
In root layout for consistent naming:
```tsx
export const metadata: Metadata = {
title: { default: 'Site Name', template: '%s | Site Name' },
}
```
## Metadata File Conventions
Reference: https://nextjs.org/docs/app/getting-started/project-structure#metadata-file-conventions
Place these files in `app/` directory (or route segments):
| File | Purpose |
|------|---------|
| `favicon.ico` | Favicon |
| `icon.png` / `icon.svg` | App icon |
| `apple-icon.png` | Apple app icon |
| `opengraph-image.png` | OG image |
| `twitter-image.png` | Twitter card image |
| `sitemap.ts` / `sitemap.xml` | Sitemap (use `generateSitemaps` for multiple) |
| `robots.ts` / `robots.txt` | Robots directives |
| `manifest.ts` / `manifest.json` | Web app manifest |
## SEO Best Practice: Static Files Are Often Enough
For most sites, **static metadata files provide excellent SEO coverage**:
```
app/
├── favicon.ico
├── opengraph-image.png # Works for both OG and Twitter
├── sitemap.ts
├── robots.ts
└── layout.tsx # With title/description metadata
```
**Tips:**
- A single `opengraph-image.png` covers both Open Graph and Twitter (Twitter falls back to OG)
- Static `title` and `description` in layout metadata is sufficient for most pages
- Only use dynamic `generateMetadata` when content varies per page
---
# OG Image Generation
Generate dynamic Open Graph images using `next/og`.
## Important Rules
1. **Use `next/og`** - not `@vercel/og` (it's built into Next.js)
2. **No searchParams** - OG images can't access search params, use route params instead
3. **Avoid Edge runtime** - Use default Node.js runtime
```tsx
// Good
import { ImageResponse } from 'next/og'
// Bad
// import { ImageResponse } from '@vercel/og'
// export const runtime = 'edge'
```
## Basic OG Image
```tsx
// app/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const alt = 'Site Name'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
export default function Image() {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
Hello World
</div>
),
{ ...size }
)
}
```
## Dynamic OG Image
```tsx
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const alt = 'Blog Post'
export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'
type Props = { params: Promise<{ slug: string }> }
export default async function Image({ params }: Props) {
const { slug } = await params
const post = await getPost(slug)
return new ImageResponse(
(
<div
style={{
fontSize: 48,
background: 'linear-gradient(to bottom, #1a1a1a, #333)',
color: 'white',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
padding: 48,
}}
>
<div style={{ fontSize: 64, fontWeight: 'bold' }}>{post.title}</div>
<div style={{ marginTop: 24, opacity: 0.8 }}>{post.description}</div>
</div>
),
{ ...size }
)
}
```
## Custom Fonts
```tsx
import { ImageResponse } from 'next/og'
import { join } from 'path'
import { readFile } from 'fs/promises'
export default async function Image() {
const fontPath = join(process.cwd(), 'assets/fonts/Inter-Bold.ttf')
const fontData = await readFile(fontPath)
return new ImageResponse(
(
<div style={{ fontFamily: 'Inter', fontSize: 64 }}>
Custom Font Text
</div>
),
{
width: 1200,
height: 630,
fonts: [{ name: 'Inter', data: fontData, style: 'normal' }],
}
)
}
```
## File Naming
- `opengraph-image.tsx` - Open Graph (Facebook, LinkedIn)
- `twitter-image.tsx` - Twitter/X cards (optional, falls back to OG)
## Styling Notes
ImageResponse uses Flexbox layout:
- Use `display: 'flex'`
- No CSS Grid support
- Styles must be inline objects
## Multiple OG Images
Use `generateImageMetadata` for multiple images per route:
```tsx
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export async function generateImageMetadata({ params }) {
const images = await getPostImages(params.slug)
return images.map((img, idx) => ({
id: idx,
alt: img.alt,
size: { width: 1200, height: 630 },
contentType: 'image/png',
}))
}
export default async function Image({ params, id }) {
const images = await getPostImages(params.slug)
const image = images[id]
return new ImageResponse(/* ... */)
}
```
## Multiple Sitemaps
Use `generateSitemaps` for large sites:
```tsx
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export async function generateSitemaps() {
// Return array of sitemap IDs
return [{ id: 0 }, { id: 1 }, { id: 2 }]
}
export default async function sitemap({
id,
}: {
id: number
}): Promise<MetadataRoute.Sitemap> {
const start = id * 50000
const end = start + 50000
const products = await getProducts(start, end)
return products.map((product) => ({
url: `https://example.com/product/${product.id}`,
lastModified: product.updatedAt,
}))
}
```
Generates `/sitemap/0.xml`, `/sitemap/1.xml`, etc.

View File

@@ -0,0 +1,287 @@
# Parallel & Intercepting Routes
Parallel routes render multiple pages in the same layout. Intercepting routes show a different UI when navigating from within your app vs direct URL access. Together they enable modal patterns.
## File Structure
```
app/
├── @modal/ # Parallel route slot
│ ├── default.tsx # Required! Returns null
│ ├── (.)photos/ # Intercepts /photos/*
│ │ └── [id]/
│ │ └── page.tsx # Modal content
│ └── [...]catchall/ # Optional: catch unmatched
│ └── page.tsx
├── photos/
│ └── [id]/
│ └── page.tsx # Full page (direct access)
├── layout.tsx # Renders both children and @modal
└── page.tsx
```
## Step 1: Root Layout with Slot
```tsx
// app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
);
}
```
## Step 2: Default File (Critical!)
**Every parallel route slot MUST have a `default.tsx`** to prevent 404s on hard navigation.
```tsx
// app/@modal/default.tsx
export default function Default() {
return null;
}
```
Without this file, refreshing any page will 404 because Next.js can't determine what to render in the `@modal` slot.
## Step 3: Intercepting Route (Modal)
The `(.)` prefix intercepts routes at the same level.
```tsx
// app/@modal/(.)photos/[id]/page.tsx
import { Modal } from '@/components/modal';
export default async function PhotoModal({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<Modal>
<img src={photo.url} alt={photo.title} />
</Modal>
);
}
```
## Step 4: Full Page (Direct Access)
```tsx
// app/photos/[id]/page.tsx
export default async function PhotoPage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<div className="full-page">
<img src={photo.url} alt={photo.title} />
<h1>{photo.title}</h1>
</div>
);
}
```
## Step 5: Modal Component with Correct Closing
**Critical: Use `router.back()` to close modals, NOT `router.push()` or `<Link>`.**
```tsx
// components/modal.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect, useRef } from 'react';
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter();
const overlayRef = useRef<HTMLDivElement>(null);
// Close on escape key
useEffect(() => {
function onKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
router.back(); // Correct
}
}
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [router]);
// Close on overlay click
const handleOverlayClick = useCallback((e: React.MouseEvent) => {
if (e.target === overlayRef.current) {
router.back(); // Correct
}
}, [router]);
return (
<div
ref={overlayRef}
onClick={handleOverlayClick}
className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
>
<div className="bg-white rounded-lg p-6 max-w-2xl w-full mx-4">
<button
onClick={() => router.back()} // Correct!
className="absolute top-4 right-4"
>
Close
</button>
{children}
</div>
</div>
);
}
```
### Why NOT `router.push('/')` or `<Link href="/">`?
Using `push` or `Link` to "close" a modal:
1. Adds a new history entry (back button shows modal again)
2. Doesn't properly clear the intercepted route
3. Can cause the modal to flash or persist unexpectedly
`router.back()` correctly:
1. Removes the intercepted route from history
2. Returns to the previous page
3. Properly unmounts the modal
## Route Matcher Reference
Matchers match **route segments**, not filesystem paths:
| Matcher | Matches | Example |
|---------|---------|---------|
| `(.)` | Same level | `@modal/(.)photos` intercepts `/photos` |
| `(..)` | One level up | `@modal/(..)settings` from `/dashboard/@modal` intercepts `/settings` |
| `(..)(..)` | Two levels up | Rarely used |
| `(...)` | From root | `@modal/(...)photos` intercepts `/photos` from anywhere |
**Common mistake**: Thinking `(..)` means "parent folder" - it means "parent route segment".
## Handling Hard Navigation
When users directly visit `/photos/123` (bookmark, refresh, shared link):
- The intercepting route is bypassed
- The full `photos/[id]/page.tsx` renders
- Modal doesn't appear (expected behavior)
If you want the modal to appear on direct access too, you need additional logic:
```tsx
// app/photos/[id]/page.tsx
import { Modal } from '@/components/modal';
export default async function PhotoPage({ params }) {
const { id } = await params;
const photo = await getPhoto(id);
// Option: Render as modal on direct access too
return (
<Modal>
<img src={photo.url} alt={photo.title} />
</Modal>
);
}
```
## Common Gotchas
### 1. Missing `default.tsx` → 404 on Refresh
Every `@slot` folder needs a `default.tsx` that returns `null` (or appropriate content).
### 2. Modal Persists After Navigation
You're using `router.push()` instead of `router.back()`.
### 3. Nested Parallel Routes Need Defaults Too
If you have `@modal` inside a route group, each level needs its own `default.tsx`:
```
app/
├── (marketing)/
│ ├── @modal/
│ │ └── default.tsx # Needed!
│ └── layout.tsx
└── layout.tsx
```
### 4. Intercepted Route Shows Wrong Content
Check your matcher:
- `(.)photos` intercepts `/photos` from the same route level
- If your `@modal` is in `app/dashboard/@modal`, use `(.)photos` to intercept `/dashboard/photos`, not `/photos`
### 5. TypeScript Errors with `params`
In Next.js 15+, `params` is a Promise:
```tsx
// Correct
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
}
```
## Complete Example: Photo Gallery Modal
```
app/
├── @modal/
│ ├── default.tsx
│ └── (.)photos/
│ └── [id]/
│ └── page.tsx
├── photos/
│ ├── page.tsx # Gallery grid
│ └── [id]/
│ └── page.tsx # Full photo page
├── layout.tsx
└── page.tsx
```
Links in the gallery:
```tsx
// app/photos/page.tsx
import Link from 'next/link';
export default async function Gallery() {
const photos = await getPhotos();
return (
<div className="grid grid-cols-3 gap-4">
{photos.map(photo => (
<Link key={photo.id} href={`/photos/${photo.id}`}>
<img src={photo.thumbnail} alt={photo.title} />
</Link>
))}
</div>
);
}
```
Clicking a photo → Modal opens (intercepted)
Direct URL → Full page renders
Refresh while modal open → Full page renders

View File

@@ -0,0 +1,146 @@
# Route Handlers
Create API endpoints with `route.ts` files.
## Basic Usage
```tsx
// app/api/users/route.ts
export async function GET() {
const users = await getUsers()
return Response.json(users)
}
export async function POST(request: Request) {
const body = await request.json()
const user = await createUser(body)
return Response.json(user, { status: 201 })
}
```
## Supported Methods
`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
## GET Handler Conflicts with page.tsx
**A `route.ts` and `page.tsx` cannot coexist in the same folder.**
```
app/
├── api/
│ └── users/
│ └── route.ts # /api/users
└── users/
├── page.tsx # /users (page)
└── route.ts # Warning: Conflicts with page.tsx!
```
If you need both a page and an API at the same path, use different paths:
```
app/
├── users/
│ └── page.tsx # /users (page)
└── api/
└── users/
└── route.ts # /api/users (API)
```
## Environment Behavior
Route handlers run in a **Server Component-like environment**:
- Yes: Can use `async/await`
- Yes: Can access `cookies()`, `headers()`
- Yes: Can use Node.js APIs
- No: Cannot use React hooks
- No: Cannot use React DOM APIs
- No: Cannot use browser APIs
```tsx
// Bad: This won't work - no React DOM in route handlers
import { renderToString } from 'react-dom/server'
export async function GET() {
const html = renderToString(<Component />) // Error!
return new Response(html)
}
```
## Dynamic Route Handlers
```tsx
// app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
const user = await getUser(id)
if (!user) {
return Response.json({ error: 'Not found' }, { status: 404 })
}
return Response.json(user)
}
```
## Request Helpers
```tsx
export async function GET(request: Request) {
// URL and search params
const { searchParams } = new URL(request.url)
const query = searchParams.get('q')
// Headers
const authHeader = request.headers.get('authorization')
// Cookies (Next.js helper)
const cookieStore = await cookies()
const token = cookieStore.get('token')
return Response.json({ query, token })
}
```
## Response Helpers
```tsx
// JSON response
return Response.json({ data })
// With status
return Response.json({ error: 'Not found' }, { status: 404 })
// With headers
return Response.json(data, {
headers: {
'Cache-Control': 'max-age=3600',
},
})
// Redirect
return Response.redirect(new URL('/login', request.url))
// Stream
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' },
})
```
## When to Use Route Handlers vs Server Actions
| Use Case | Route Handlers | Server Actions |
|----------|----------------|----------------|
| Form submissions | No | Yes |
| Data mutations from UI | No | Yes |
| Third-party webhooks | Yes | No |
| External API consumption | Yes | No |
| Public REST API | Yes | No |
| File uploads | Both work | Both work |
**Prefer Server Actions** for mutations triggered from your UI.
**Use Route Handlers** for external integrations and public APIs.

View File

@@ -0,0 +1,159 @@
# RSC Boundaries
Detect and prevent invalid patterns when crossing Server/Client component boundaries.
## Detection Rules
### 1. Async Client Components Are Invalid
Client components **cannot** be async functions. Only Server Components can be async.
**Detect:** File has `'use client'` AND component is `async function` or returns `Promise`
```tsx
// Bad: async client component
'use client'
export default async function UserProfile() {
const user = await getUser() // Cannot await in client component
return <div>{user.name}</div>
}
// Good: Remove async, fetch data in parent server component
// page.tsx (server component - no 'use client')
export default async function Page() {
const user = await getUser()
return <UserProfile user={user} />
}
// UserProfile.tsx (client component)
'use client'
export function UserProfile({ user }: { user: User }) {
return <div>{user.name}</div>
}
```
```tsx
// Bad: async arrow function client component
'use client'
const Dashboard = async () => {
const data = await fetchDashboard()
return <div>{data}</div>
}
// Good: Fetch in server component, pass data down
```
### 2. Non-Serializable Props to Client Components
Props passed from Server → Client must be JSON-serializable.
**Detect:** Server component passes these to a client component:
- Functions (except Server Actions with `'use server'`)
- `Date` objects
- `Map`, `Set`, `WeakMap`, `WeakSet`
- Class instances
- `Symbol` (unless globally registered)
- Circular references
```tsx
// Bad: Function prop
// page.tsx (server)
export default function Page() {
const handleClick = () => console.log('clicked')
return <ClientButton onClick={handleClick} />
}
// Good: Define function inside client component
// ClientButton.tsx
'use client'
export function ClientButton() {
const handleClick = () => console.log('clicked')
return <button onClick={handleClick}>Click</button>
}
```
```tsx
// Bad: Date object (silently becomes string, then crashes)
// page.tsx (server)
export default async function Page() {
const post = await getPost()
return <PostCard createdAt={post.createdAt} /> // Date object
}
// PostCard.tsx (client) - will crash on .getFullYear()
'use client'
export function PostCard({ createdAt }: { createdAt: Date }) {
return <span>{createdAt.getFullYear()}</span> // Runtime error!
}
// Good: Serialize to string on server
// page.tsx (server)
export default async function Page() {
const post = await getPost()
return <PostCard createdAt={post.createdAt.toISOString()} />
}
// PostCard.tsx (client)
'use client'
export function PostCard({ createdAt }: { createdAt: string }) {
const date = new Date(createdAt)
return <span>{date.getFullYear()}</span>
}
```
```tsx
// Bad: Class instance
const user = new UserModel(data)
<ClientProfile user={user} /> // Methods will be stripped
// Good: Pass plain object
const user = await getUser()
<ClientProfile user={{ id: user.id, name: user.name }} />
```
```tsx
// Bad: Map/Set
<ClientComponent items={new Map([['a', 1]])} />
// Good: Convert to array/object
<ClientComponent items={Object.fromEntries(map)} />
<ClientComponent items={Array.from(set)} />
```
### 3. Server Actions Are the Exception
Functions marked with `'use server'` CAN be passed to client components.
```tsx
// Valid: Server Action can be passed
// actions.ts
'use server'
export async function submitForm(formData: FormData) {
// server-side logic
}
// page.tsx (server)
import { submitForm } from './actions'
export default function Page() {
return <ClientForm onSubmit={submitForm} /> // OK!
}
// ClientForm.tsx (client)
'use client'
export function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {
return <form action={onSubmit}>...</form>
}
```
## Quick Reference
| Pattern | Valid? | Fix |
|---------|--------|-----|
| `'use client'` + `async function` | No | Fetch in server parent, pass data |
| Pass `() => {}` to client | No | Define in client or use server action |
| Pass `new Date()` to client | No | Use `.toISOString()` |
| Pass `new Map()` to client | No | Convert to object/array |
| Pass class instance to client | No | Pass plain object |
| Pass server action to client | Yes | - |
| Pass `string/number/boolean` | Yes | - |
| Pass plain object/array | Yes | - |

View File

@@ -0,0 +1,39 @@
# Runtime Selection
## Use Node.js Runtime by Default
Use the default Node.js runtime for new routes and pages. Only use Edge runtime if the project already uses it or there's a specific requirement.
```tsx
// Good: Default - no runtime config needed (uses Node.js)
export default function Page() { ... }
// Caution: Only if already used in project or specifically required
export const runtime = 'edge'
```
## When to Use Each
### Node.js Runtime (Default)
- Full Node.js API support
- File system access (`fs`)
- Full `crypto` support
- Database connections
- Most npm packages work
### Edge Runtime
- Only for specific edge-location latency requirements
- Limited API (no `fs`, limited `crypto`)
- Smaller cold start
- Geographic distribution needs
## Detection
**Before adding `runtime = 'edge'`**, check:
1. Does the project already use Edge runtime?
2. Is there a specific latency requirement?
3. Are all dependencies Edge-compatible?
If unsure, use Node.js runtime.

View File

@@ -0,0 +1,141 @@
# Scripts
Loading third-party scripts in Next.js.
## Use next/script
Always use `next/script` instead of native `<script>` tags for better performance.
```tsx
// Bad: Native script tag
<script src="https://example.com/script.js"></script>
// Good: Next.js Script component
import Script from 'next/script'
<Script src="https://example.com/script.js" />
```
## Inline Scripts Need ID
Inline scripts require an `id` attribute for Next.js to track them.
```tsx
// Bad: Missing id
<Script dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
// Good: Has id
<Script id="my-script" dangerouslySetInnerHTML={{ __html: 'console.log("hi")' }} />
// Good: Inline with id
<Script id="show-banner">
{`document.getElementById('banner').classList.remove('hidden')`}
</Script>
```
## Don't Put Script in Head
`next/script` should not be placed inside `next/head`. It handles its own positioning.
```tsx
// Bad: Script inside Head
import Head from 'next/head'
import Script from 'next/script'
<Head>
<Script src="/analytics.js" />
</Head>
// Good: Script outside Head
<Head>
<title>Page</title>
</Head>
<Script src="/analytics.js" />
```
## Loading Strategies
```tsx
// afterInteractive (default) - Load after page is interactive
<Script src="/analytics.js" strategy="afterInteractive" />
// lazyOnload - Load during idle time
<Script src="/widget.js" strategy="lazyOnload" />
// beforeInteractive - Load before page is interactive (use sparingly)
// Only works in app/layout.tsx or pages/_document.js
<Script src="/critical.js" strategy="beforeInteractive" />
// worker - Load in web worker (experimental)
<Script src="/heavy.js" strategy="worker" />
```
## Google Analytics
Use `@next/third-parties` instead of inline GA scripts.
```tsx
// Bad: Inline GA script
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXX" />
<Script id="ga-init">
{`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXX');`}
</Script>
// Good: Next.js component
import { GoogleAnalytics } from '@next/third-parties/google'
export default function Layout({ children }) {
return (
<html>
<body>{children}</body>
<GoogleAnalytics gaId="G-XXXXX" />
</html>
)
}
```
## Google Tag Manager
```tsx
import { GoogleTagManager } from '@next/third-parties/google'
export default function Layout({ children }) {
return (
<html>
<GoogleTagManager gtmId="GTM-XXXXX" />
<body>{children}</body>
</html>
)
}
```
## Other Third-Party Scripts
```tsx
// YouTube embed
import { YouTubeEmbed } from '@next/third-parties/google'
<YouTubeEmbed videoid="dQw4w9WgXcQ" />
// Google Maps
import { GoogleMapsEmbed } from '@next/third-parties/google'
<GoogleMapsEmbed
apiKey="YOUR_API_KEY"
mode="place"
q="Brooklyn+Bridge,New+York,NY"
/>
```
## Quick Reference
| Pattern | Issue | Fix |
|---------|-------|-----|
| `<script src="...">` | No optimization | Use `next/script` |
| `<Script>` without id | Can't track inline scripts | Add `id` attribute |
| `<Script>` inside `<Head>` | Wrong placement | Move outside Head |
| Inline GA/GTM scripts | No optimization | Use `@next/third-parties` |
| `strategy="beforeInteractive"` outside layout | Won't work | Only use in root layout |

View File

@@ -0,0 +1,371 @@
# Self-Hosting Next.js
Deploy Next.js outside of Vercel with confidence.
## Quick Start: Standalone Output
For Docker or any containerized deployment, use standalone output:
```js
// next.config.js
module.exports = {
output: 'standalone',
};
```
This creates a minimal `standalone` folder with only production dependencies:
```
.next/
├── standalone/
│ ├── server.js # Entry point
│ ├── node_modules/ # Only production deps
│ └── .next/ # Build output
└── static/ # Must be copied separately
```
## Docker Deployment
### Dockerfile
```dockerfile
FROM node:20-alpine AS base
# Install dependencies
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
# Build
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy standalone output
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
```
### Docker Compose
```yaml
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
```
## PM2 Deployment
For traditional server deployments:
```js
// ecosystem.config.js
module.exports = {
apps: [{
name: 'nextjs',
script: '.next/standalone/server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
```
```bash
npm run build
pm2 start ecosystem.config.js
```
## ISR and Cache Handlers
### The Problem
ISR (Incremental Static Regeneration) uses filesystem caching by default. This **breaks with multiple instances**:
- Instance A regenerates page → saves to its local disk
- Instance B serves stale page → doesn't see Instance A's cache
- Load balancer sends users to random instances → inconsistent content
### Solution: Custom Cache Handler
Next.js 14+ supports custom cache handlers for shared storage:
```js
// next.config.js
module.exports = {
cacheHandler: require.resolve('./cache-handler.js'),
cacheMaxMemorySize: 0, // Disable in-memory cache
};
```
#### Redis Cache Handler Example
```js
// cache-handler.js
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
const CACHE_PREFIX = 'nextjs:';
module.exports = class CacheHandler {
constructor(options) {
this.options = options;
}
async get(key) {
const data = await redis.get(CACHE_PREFIX + key);
if (!data) return null;
const parsed = JSON.parse(data);
return {
value: parsed.value,
lastModified: parsed.lastModified,
};
}
async set(key, data, ctx) {
const cacheData = {
value: data,
lastModified: Date.now(),
};
// Set TTL based on revalidate option
if (ctx?.revalidate) {
await redis.setex(
CACHE_PREFIX + key,
ctx.revalidate,
JSON.stringify(cacheData)
);
} else {
await redis.set(CACHE_PREFIX + key, JSON.stringify(cacheData));
}
}
async revalidateTag(tags) {
// Implement tag-based invalidation
// This requires tracking which keys have which tags
}
};
```
#### S3 Cache Handler Example
```js
// cache-handler.js
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({ region: process.env.AWS_REGION });
const BUCKET = process.env.CACHE_BUCKET;
module.exports = class CacheHandler {
async get(key) {
try {
const response = await s3.send(new GetObjectCommand({
Bucket: BUCKET,
Key: `cache/${key}`,
}));
const body = await response.Body.transformToString();
return JSON.parse(body);
} catch (err) {
if (err.name === 'NoSuchKey') return null;
throw err;
}
}
async set(key, data, ctx) {
await s3.send(new PutObjectCommand({
Bucket: BUCKET,
Key: `cache/${key}`,
Body: JSON.stringify({
value: data,
lastModified: Date.now(),
}),
ContentType: 'application/json',
}));
}
};
```
## What Works vs What Needs Setup
| Feature | Single Instance | Multi-Instance | Notes |
|---------|----------------|----------------|-------|
| SSR | Yes | Yes | No special setup |
| SSG | Yes | Yes | Built at deploy time |
| ISR | Yes | Needs cache handler | Filesystem cache breaks |
| Image Optimization | Yes | Yes | CPU-intensive, consider CDN |
| Middleware | Yes | Yes | Runs on Node.js |
| Edge Runtime | Limited | Limited | Some features Node-only |
| `revalidatePath/Tag` | Yes | Needs cache handler | Must share cache |
| `next/font` | Yes | Yes | Fonts bundled at build |
| Draft Mode | Yes | Yes | Cookie-based |
## Image Optimization
Next.js Image Optimization works out of the box but is CPU-intensive.
### Option 1: Built-in (Simple)
Works automatically, but consider:
- Set `deviceSizes` and `imageSizes` in config to limit variants
- Use `minimumCacheTTL` to reduce regeneration
```js
// next.config.js
module.exports = {
images: {
minimumCacheTTL: 60 * 60 * 24, // 24 hours
deviceSizes: [640, 750, 1080, 1920], // Limit sizes
},
};
```
### Option 2: External Loader (Recommended for Scale)
Offload to Cloudinary, Imgix, or similar:
```js
// next.config.js
module.exports = {
images: {
loader: 'custom',
loaderFile: './lib/image-loader.js',
},
};
```
```js
// lib/image-loader.js
export default function cloudinaryLoader({ src, width, quality }) {
const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`];
return `https://res.cloudinary.com/demo/image/upload/${params.join(',')}${src}`;
}
```
## Environment Variables
### Build-time vs Runtime
```js
// Available at build time only (baked into bundle)
NEXT_PUBLIC_API_URL=https://api.example.com
// Available at runtime (server-side only)
DATABASE_URL=postgresql://...
API_SECRET=...
```
### Runtime Configuration
For truly dynamic config, don't use `NEXT_PUBLIC_*`. Instead:
```tsx
// app/api/config/route.ts
export async function GET() {
return Response.json({
apiUrl: process.env.API_URL,
features: process.env.FEATURES?.split(','),
});
}
```
## OpenNext: Serverless Without Vercel
[OpenNext](https://open-next.js.org/) adapts Next.js for AWS Lambda, Cloudflare Workers, etc.
```bash
npx create-sst@latest
# or
npx @opennextjs/aws build
```
Supports:
- AWS Lambda + CloudFront
- Cloudflare Workers
- Netlify Functions
- Deno Deploy
## Health Check Endpoint
Always include a health check for load balancers:
```tsx
// app/api/health/route.ts
export async function GET() {
try {
// Optional: check database connection
// await db.$queryRaw`SELECT 1`;
return Response.json({ status: 'healthy' }, { status: 200 });
} catch (error) {
return Response.json({ status: 'unhealthy' }, { status: 503 });
}
}
```
## Pre-Deployment Checklist
1. **Build locally first**: `npm run build` - catch errors before deploy
2. **Test standalone output**: `node .next/standalone/server.js`
3. **Set `output: 'standalone'`** for Docker
4. **Configure cache handler** for multi-instance ISR
5. **Set `HOSTNAME="0.0.0.0"`** for containers
6. **Copy `public/` and `.next/static/`** - not included in standalone
7. **Add health check endpoint**
8. **Test ISR revalidation** after deployment
9. **Monitor memory usage** - Node.js defaults may need tuning
## Testing Cache Handler
**Critical**: Test your cache handler on every Next.js upgrade:
```bash
# Start multiple instances
PORT=3001 node .next/standalone/server.js &
PORT=3002 node .next/standalone/server.js &
# Trigger ISR revalidation
curl http://localhost:3001/api/revalidate?path=/posts
# Verify both instances see the update
curl http://localhost:3001/posts
curl http://localhost:3002/posts
# Should return identical content
```

View File

@@ -0,0 +1,67 @@
# Suspense Boundaries
Client hooks that cause CSR bailout without Suspense boundaries.
## useSearchParams
Always requires Suspense boundary in static routes. Without it, the entire page becomes client-side rendered.
```tsx
// Bad: Entire page becomes CSR
'use client'
import { useSearchParams } from 'next/navigation'
export default function SearchBar() {
const searchParams = useSearchParams()
return <div>Query: {searchParams.get('q')}</div>
}
```
```tsx
// Good: Wrap in Suspense
import { Suspense } from 'react'
import SearchBar from './search-bar'
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<SearchBar />
</Suspense>
)
}
```
## usePathname
Requires Suspense boundary when route has dynamic parameters.
```tsx
// In dynamic route [slug]
// Bad: No Suspense
'use client'
import { usePathname } from 'next/navigation'
export function Breadcrumb() {
const pathname = usePathname()
return <nav>{pathname}</nav>
}
```
```tsx
// Good: Wrap in Suspense
<Suspense fallback={<BreadcrumbSkeleton />}>
<Breadcrumb />
</Suspense>
```
If you use `generateStaticParams`, Suspense is optional.
## Quick Reference
| Hook | Suspense Required |
|------|-------------------|
| `useSearchParams()` | Yes |
| `usePathname()` | Yes (dynamic routes) |
| `useParams()` | No |
| `useRouter()` | No |

1932
skills/shadcn-ui/SKILL.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
### shadcn/ui Chart Component - Installation
Source: https://ui.shadcn.com/docs/components/chart
The chart component in shadcn/ui is built on Recharts, providing direct access to all Recharts capabilities with consistent theming.
```bash
npx shadcn@latest add chart
```
--------------------------------
### shadcn/ui Chart Component - Basic Usage
Source: https://ui.shadcn.com/docs/components/chart
The ChartContainer wraps your Recharts component and accepts a config prop for theming. Requires `min-h-[value]` for responsiveness.
```tsx
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
desktop: {
label: "Desktop",
color: "var(--chart-1)",
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies import("@/components/ui/chart").ChartConfig
const chartData = [
{ month: "January", desktop: 186, mobile: 80 },
{ month: "February", desktop: 305, mobile: 200 },
{ month: "March", desktop: 237, mobile: 120 },
]
export function BarChartDemo() {
return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<BarChart data={chartData}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" tickLine={false} axisLine={false} />
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
<ChartTooltip content={<ChartTooltipContent />} />
</BarChart>
</ChartContainer>
)
}
```
--------------------------------
### shadcn/ui Chart Component - ChartConfig with Custom Colors
Source: https://ui.shadcn.com/docs/components/chart
You can define custom colors directly in the configuration using hex values or CSS variables.
```tsx
const chartConfig = {
desktop: {
label: "Desktop",
color: "#2563eb",
theme: {
light: "#2563eb",
dark: "#60a5fa",
},
},
mobile: {
label: "Mobile",
color: "var(--chart-2)",
},
} satisfies import("@/components/ui/chart").ChartConfig
```
--------------------------------
### shadcn/ui Chart Component - CSS Variables
Source: https://ui.shadcn.com/docs/components/chart
Add chart color variables to your globals.css for consistent theming.
```css
:root {
/* Chart colors */
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.546 0.198 38.228);
--chart-4: oklch(0.596 0.151 343.253);
--chart-5: oklch(0.546 0.158 49.157);
}
.dark {
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.698 0.141 24.311);
--chart-4: oklch(0.676 0.172 171.196);
--chart-5: oklch(0.578 0.192 302.85);
}
```
--------------------------------
### shadcn/ui Chart Component - Line Chart Example
Source: https://ui.shadcn.com/docs/components/chart
Creating a line chart with shadcn/ui charts component.
```tsx
import { Line, LineChart, CartesianGrid, XAxis, YAxis } from "recharts"
import { ChartContainer, ChartTooltipContent } from "@/components/ui/chart"
const chartConfig = {
price: {
label: "Price",
color: "var(--chart-1)",
},
} satisfies import("@/components/ui/chart").ChartConfig
const chartData = [
{ month: "January", price: 186 },
{ month: "February", price: 305 },
{ month: "March", price: 237 },
{ month: "April", price: 203 },
{ month: "May", price: 276 },
]
export function LineChartDemo() {
return (
<ChartContainer config={chartConfig} className="min-h-[200px]">
<LineChart data={chartData}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" tickLine={false} axisLine={false} />
<YAxis tickLine={false} axisLine={false} tickFormatter={(value) => `$${value}`} />
<Line
dataKey="price"
stroke="var(--color-price)"
strokeWidth={2}
dot={false}
/>
<ChartTooltip content={<ChartTooltipContent />} />
</LineChart>
</ChartContainer>
)
}
```
--------------------------------
### shadcn/ui Chart Component - Area Chart Example
Source: https://ui.shadcn.com/docs/components/chart
Creating an area chart with gradient fill and legend.
```tsx
import { Area, AreaChart, XAxis, YAxis } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartConfig = {
desktop: { label: "Desktop", color: "var(--chart-1)" },
mobile: { label: "Mobile", color: "var(--chart-2)" },
} satisfies import("@/components/ui/chart").ChartConfig
export function AreaChartDemo() {
return (
<ChartContainer config={chartConfig} className="min-h-[200px]">
<AreaChart data={chartData}>
<XAxis dataKey="month" tickLine={false} axisLine={false} />
<YAxis tickLine={false} axisLine={false} />
<Area
dataKey="desktop"
fill="var(--color-desktop)"
stroke="var(--color-desktop)"
fillOpacity={0.3}
/>
<Area
dataKey="mobile"
fill="var(--color-mobile)"
stroke="var(--color-mobile)"
fillOpacity={0.3}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
</AreaChart>
</ChartContainer>
)
}
```
--------------------------------
### shadcn/ui Chart Component - Pie Chart Example
Source: https://ui.shadcn.com/docs/components/chart
Creating a pie/donut chart with shadcn/ui.
```tsx
import { Pie, PieChart } from "recharts"
import {
ChartContainer,
ChartLegend,
ChartLegendContent,
ChartTooltipContent,
} from "@/components/ui/chart"
const chartConfig = {
chrome: { label: "Chrome", color: "var(--chart-1)" },
safari: { label: "Safari", color: "var(--chart-2)" },
firefox: { label: "Firefox", color: "var(--chart-3)" },
} satisfies import("@/components/ui/chart").ChartConfig
const pieData = [
{ browser: "Chrome", visitors: 275, fill: "var(--color-chrome)" },
{ browser: "Safari", visitors: 200, fill: "var(--color-safari)" },
{ browser: "Firefox", visitors: 187, fill: "var(--color-firefox)" },
]
export function PieChartDemo() {
return (
<ChartContainer config={chartConfig} className="min-h-[200px]">
<PieChart>
<Pie
data={pieData}
dataKey="visitors"
nameKey="browser"
cx="50%"
cy="50%"
outerRadius={80}
/>
<ChartTooltip content={<ChartTooltipContent />} />
<ChartLegend content={<ChartLegendContent />} />
</PieChart>
</ChartContainer>
)
}
```
--------------------------------
### shadcn/ui ChartTooltipContent Props
Source: https://ui.shadcn.com/docs/components/chart
The ChartTooltipContent component accepts these props for customizing tooltip behavior.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `labelKey` | string | "label" | Key for tooltip label |
| `nameKey` | string | "name" | Key for tooltip name |
| `indicator` | "dot" \| "line" \| "dashed" | "dot" | Indicator style |
| `hideLabel` | boolean | false | Hide label |
| `hideIndicator` | boolean | false | Hide indicator |
--------------------------------
### shadcn/ui Chart Component - Accessibility
Source: https://ui.shadcn.com/docs/components/chart
Enable keyboard navigation and screen reader support by adding the accessibilityLayer prop.
```tsx
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" />
<Bar dataKey="desktop" fill="var(--color-desktop)" />
<ChartTooltip content={<ChartTooltipContent />} />
</BarChart>
```
This adds:
- Keyboard arrow key navigation
- ARIA labels for chart elements
- Screen reader announcements for data values
--------------------------------
### shadcn/ui Chart Component - Recharts Dependencies
Source: https://ui.shadcn.com/docs/components/chart
The chart component requires the following Recharts dependencies to be installed.
```bash
pnpm add recharts
npm install recharts
yarn add recharts
```
Recharts provides the following chart types:
- Area, Bar, Line, Pie, Composed
- Radar, RadialBar, Scatter
- Funnel, Treemap

View File

@@ -0,0 +1,145 @@
# shadcn/ui Learning Guide
This guide helps you learn shadcn/ui from basics to advanced patterns.
## Learning Path
### 1. Understanding the Philosophy
shadcn/ui is different from traditional component libraries:
- **Copy-paste components**: Components are copied into your project, not installed as packages
- **Full customization**: You own the code and can modify it freely
- **Built on Radix UI**: Provides accessibility primitives
- **Styled with Tailwind**: Uses utility classes for consistent styling
### 2. Core Concepts to Master
#### Class Variance Authority (CVA)
Most components use CVA for variant management:
```tsx
const buttonVariants = cva(
"base-classes",
{
variants: {
variant: {
default: "variant-classes",
destructive: "destructive-classes",
},
size: {
default: "size-classes",
sm: "small-classes",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
```
#### cn Utility Function
The `cn` function combines classes and resolves conflicts:
```tsx
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
### 3. Installation Checklist
- [ ] Initialize a new project (Next.js, Vite, or Remix)
- [ ] Install Tailwind CSS
- [ ] Run `npx shadcn@latest init`
- [ ] Configure CSS variables
- [ ] Install first component: `npx shadcn@latest add button`
### 4. Essential Components to Learn First
1. **Button** - Learn variants and sizes
2. **Input** - Form inputs with labels
3. **Card** - Container components
4. **Form** - Form handling with React Hook Form
5. **Dialog** - Modal windows
6. **Select** - Dropdown selections
7. **Toast** - Notifications
### 5. Common Patterns
#### Form Pattern
Every form follows this structure:
```tsx
1. Define Zod schema
2. Create form with useForm
3. Wrap with Form component
4. Add FormField for each input
5. Handle submission
```
#### Component Customization Pattern
To customize a component:
1. Copy component to your project
2. Modify the variants
3. Add new props if needed
4. Update types
### 6. Best Practices
- Always use TypeScript
- Follow the existing component structure
- Use semantic HTML when possible
- Test with screen readers for accessibility
- Keep components small and focused
### 7. Advanced Topics
- Creating custom components from scratch
- Building complex forms with validation
- Implementing dark mode
- Optimizing for performance
- Testing components
## Practice Exercises
### Exercise 1: Basic Setup
1. Create a new Next.js project
2. Set up shadcn/ui
3. Install and customize a Button component
4. Add a new variant "gradient"
### Exercise 2: Form Building
1. Create a contact form with:
- Name input (required)
- Email input (email validation)
- Message textarea (min length)
- Submit button with loading state
### Exercise 3: Component Combination
1. Build a settings page using:
- Card for layout
- Sheet for mobile menu
- Select for dropdowns
- Switch for toggles
- Toast for notifications
### Exercise 4: Custom Component
1. Create a custom Badge component
2. Support variants: default, secondary, destructive, outline
3. Support sizes: sm, default, lg
4. Add icon support
## Resources
- [Official Documentation](https://ui.shadcn.com)
- [GitHub Repository](https://github.com/shadcn/ui)
- [Examples Gallery](https://ui.shadcn.com/examples)
- [Radix UI Primitives](https://www.radix-ui.com/primitives)
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,586 @@
# shadcn.io Component Library
shadcn.io is a comprehensive React UI component library built on shadcn/ui principles, providing developers with production-ready, composable components for modern web applications. The library serves as a centralized resource for React developers who need high-quality UI components with TypeScript support, ranging from basic interactive elements to advanced AI-powered integrations. Unlike traditional component libraries that require package installations, shadcn.io components are designed to be copied directly into your project, giving you full control and customization capabilities.
The library encompasses four major categories: composable UI components (terminal, dock, credit cards, QR codes, color pickers), chart components built with Recharts, animation components with Tailwind CSS integration, and custom React hooks for state management and lifecycle operations. Each component follows best practices for accessibility, performance, and developer experience, with comprehensive TypeScript definitions and Next.js compatibility. The platform emphasizes flexibility and customization, allowing developers to modify components at the source level rather than being constrained by package APIs.
## Core Components
### Terminal Component
Interactive terminal emulator with typing animations and command execution simulation for developer-focused interfaces.
```tsx
import { Terminal } from "@/components/ui/terminal"
export default function DemoTerminal() {
return (
npm install @repo/terminalInstalling dependencies...npm start
)
}
```
### Dock Component
macOS-style application dock with smooth magnification effects on hover, perfect for navigation menus.
```tsx
import { Dock, DockIcon } from "@/components/ui/dock"
import { Home, Settings, User, Mail } from "lucide-react"
export default function AppDock() {
return (
)
}
```
### Credit Card Component
Interactive 3D credit card component with flip animations for payment forms and card displays.
```tsx
import { CreditCard } from "@/components/ui/credit-card"
import { useState } from "react"
export default function PaymentForm() {
const [cardData, setCardData] = useState({
number: "4532 1234 5678 9010",
holder: "JOHN DOE",
expiry: "12/28",
cvv: "123"
})
return (
console.log("Card flipped:", flipped)}
/>
)
}
```
### Image Zoom Component
Zoomable image component with smooth modal transitions for image galleries and product displays.
```tsx
import { ImageZoom } from "@/components/ui/image-zoom"
export default function ProductGallery() {
return (
)
}
```
### QR Code Component
Generate and display customizable QR codes with styling options for links, contact information, and authentication.
```tsx
import { QRCode } from "@/components/ui/qr-code"
export default function ShareDialog() {
const shareUrl = "https://shadcn.io"
return (
Scan to visit shadcn.io
)
}
```
### Color Picker Component
Advanced color selection component supporting multiple color formats (HEX, RGB, HSL) with preview.
```tsx
import { ColorPicker } from "@/components/ui/color-picker"
import { useState } from "react"
export default function ThemeCustomizer() {
const [color, setColor] = useState("#3b82f6")
return (
Selected: {color}
)
}
```
## Chart Components
### Bar Chart Component
Clean bar chart component for data comparison and categorical analysis using Recharts.
```tsx
import { BarChart } from "@/components/ui/bar-chart"
export default function SalesChart() {
const data = [
{ month: "Jan", sales: 4000, revenue: 2400 },
{ month: "Feb", sales: 3000, revenue: 1398 },
{ month: "Mar", sales: 2000, revenue: 9800 },
{ month: "Apr", sales: 2780, revenue: 3908 },
{ month: "May", sales: 1890, revenue: 4800 },
{ month: "Jun", sales: 2390, revenue: 3800 }
]
return (
`$${value.toLocaleString()}`}
yAxisWidth={60}
/>
)
}
```
### Line Chart Component
Smooth line chart for visualizing trends and time-series data with multiple data series support.
```tsx
import { LineChart } from "@/components/ui/line-chart"
export default function MetricsChart() {
const data = [
{ date: "2024-01", users: 1200, sessions: 3400 },
{ date: "2024-02", users: 1800, sessions: 4200 },
{ date: "2024-03", users: 2400, sessions: 5800 },
{ date: "2024-04", users: 3100, sessions: 7200 },
{ date: "2024-05", users: 3800, sessions: 8900 }
]
return (
)
}
```
### Pie Chart Component
Donut chart component for displaying proportional data and percentage distributions.
```tsx
import { PieChart } from "@/components/ui/pie-chart"
export default function MarketShareChart() {
const data = [
{ name: "Product A", value: 400, fill: "#3b82f6" },
{ name: "Product B", value: 300, fill: "#10b981" },
{ name: "Product C", value: 300, fill: "#f59e0b" },
{ name: "Product D", value: 200, fill: "#ef4444" }
]
return (
`${entry.name}: ${entry.value}`}
/>
)
}
```
### Area Chart Component
Stacked area chart for visualizing volume changes over time with multiple data series.
```tsx
import { AreaChart } from "@/components/ui/area-chart"
export default function TrafficChart() {
const data = [
{ month: "Jan", mobile: 2000, desktop: 3000, tablet: 1000 },
{ month: "Feb", mobile: 2200, desktop: 3200, tablet: 1100 },
{ month: "Mar", mobile: 2800, desktop: 3800, tablet: 1300 },
{ month: "Apr", mobile: 3200, desktop: 4200, tablet: 1500 },
{ month: "May", mobile: 3800, desktop: 4800, tablet: 1800 }
]
return (
)
}
```
### Radar Chart Component
Multi-axis chart for comparing multiple variables across different categories simultaneously.
```tsx
import { RadarChart } from "@/components/ui/radar-chart"
export default function SkillsChart() {
const data = [
{ skill: "JavaScript", score: 85, industry: 75 },
{ skill: "TypeScript", score: 80, industry: 70 },
{ skill: "React", score: 90, industry: 80 },
{ skill: "Node.js", score: 75, industry: 72 },
{ skill: "CSS", score: 88, industry: 78 }
]
return (
)
}
```
### Mixed Chart Component
Combined bar and line chart for displaying multiple data types with different visualization methods.
```tsx
import { MixedChart } from "@/components/ui/mixed-chart"
export default function PerformanceChart() {
const data = [
{ month: "Jan", revenue: 4000, growth: 5.2 },
{ month: "Feb", revenue: 4200, growth: 5.0 },
{ month: "Mar", revenue: 4800, growth: 14.3 },
{ month: "Apr", revenue: 5200, growth: 8.3 },
{ month: "May", revenue: 5800, growth: 11.5 }
]
return (
)
}
```
## Animation Components
### Magnetic Effect Component
Magnetic hover effect that smoothly follows cursor movement for interactive buttons and cards.
```tsx
import { Magnetic } from "@/components/ui/magnetic"
export default function InteractiveButton() {
return (
Hover me
)
}
```
### Animated Cursor Component
Custom animated cursor with interactive effects and particle trails for immersive experiences.
```tsx
import { AnimatedCursor } from "@/components/ui/animated-cursor"
export default function Layout({ children }) {
return (
<>
{children}
)
}
```
### Apple Hello Effect Component
Recreation of Apple's iconic "hello" animation with multi-language text transitions.
```tsx
import { AppleHello } from "@/components/ui/apple-hello"
export default function WelcomeScreen() {
const greetings = [
{ text: "Hello", lang: "en" },
{ text: "Bonjour", lang: "fr" },
{ text: "こんにちは", lang: "ja" },
{ text: "Hola", lang: "es" },
{ text: "你好", lang: "zh" }
]
return (
)
}
```
### Liquid Button Component
Button with fluid liquid animation effect on hover for engaging call-to-action elements.
```tsx
import { LiquidButton } from "@/components/ui/liquid-button"
export default function CTASection() {
return (
console.log("CTA clicked")}
>
Get Started
)
}
```
### Rolling Text Component
Text animation that creates a rolling effect with smooth character transitions.
```tsx
import { RollingText } from "@/components/ui/rolling-text"
export default function AnimatedHeading() {
return (
)
}
```
### Shimmering Text Component
Text with animated shimmer effect for attention-grabbing headings and highlights.
```tsx
import { ShimmeringText } from "@/components/ui/shimmering-text"
export default function Hero() {
return (
)
}
```
## React Hooks
### useBoolean Hook
Enhanced boolean state management with toggle, enable, and disable methods for cleaner component logic.
```tsx
import { useBoolean } from "@/hooks/use-boolean"
export default function TogglePanel() {
const modal = useBoolean(false)
const loading = useBoolean(false)
const handleSubmit = async () => {
loading.setTrue()
try {
await submitForm()
modal.setFalse()
} finally {
loading.setFalse()
}
}
return (
<>
Toggle Modal
{modal.value && (
Status: {loading.value ? "Saving..." : "Ready"}
Submit
)}
)
}
```
### useCounter Hook
Counter hook with increment, decrement, reset, and set functionality for numeric state management.
```tsx
import { useCounter } from "@/hooks/use-counter"
export default function CartCounter() {
const quantity = useCounter(0, { min: 0, max: 99 })
return (
-
{quantity.value}
+
Reset
)
}
```
### useLocalStorage Hook
Persist state in browser localStorage with automatic serialization and deserialization.
```tsx
import { useLocalStorage } from "@/hooks/use-local-storage"
export default function UserPreferences() {
const [theme, setTheme] = useLocalStorage("theme", "light")
const [settings, setSettings] = useLocalStorage("settings", {
notifications: true,
emailUpdates: false
})
return (
setTheme(e.target.value)}>
LightDark setSettings({
...settings,
notifications: e.target.checked
})}
/>
Enable Notifications
)
}
```
### useDebounceValue Hook
Debounce values to prevent excessive updates and API calls during rapid user input.
```tsx
import { useDebounceValue } from "@/hooks/use-debounce-value"
import { useState, useEffect } from "react"
export default function SearchBox() {
const [search, setSearch] = useState("")
const debouncedSearch = useDebounceValue(search, 500)
const [results, setResults] = useState([])
const [apiCalls, setApiCalls] = useState(0)
useEffect(() => {
if (debouncedSearch) {
setApiCalls(prev => prev + 1)
fetch(`/api/search?q=${debouncedSearch}`)
.then(res => res.json())
.then(setResults)
}
}, [debouncedSearch])
return (
setSearch(e.target.value)}
placeholder="Search..."
/>
API calls: {apiCalls}
)
}
```
### useHover Hook
Track hover state on elements with customizable enter and leave delays for tooltip and preview functionality.
```tsx
import { useHover } from "@/hooks/use-hover"
import { useRef } from "react"
export default function ImagePreview() {
const hoverRef = useRef(null)
const isHovering = useHover(hoverRef, {
enterDelay: 200,
leaveDelay: 100
})
return (
![Preview](http://https:%2F%2Fcontext7.com%2Fwebsites%2Fshadcn_io%2Fllms.txt/thumbnail.jpg)
{isHovering && (
![Full size](http://https:%2F%2Fcontext7.com%2Fwebsites%2Fshadcn_io%2Fllms.txt/full-size.jpg)
)}
)
}
```
### useCountdown Hook
Countdown timer with play, pause, reset controls and completion callbacks for time-limited features.
```tsx
import { useCountdown } from "@/hooks/use-countdown"
export default function OTPTimer() {
const countdown = useCountdown({
initialSeconds: 60,
onComplete: () => alert("OTP expired! Request a new code.")
})
return (
{countdown.seconds}s
{!countdown.isRunning ? (
Start
) : (
Pause
)}
Reset
Status: {countdown.isComplete ? "Expired" : countdown.isRunning ? "Active" : "Paused"}
)
}
```
## Installation and Usage
### CLI Installation
Install components directly into your project using the shadcn CLI for instant integration.
```bash
# Initialize shadcn in your project
npx shadcn@latest init
# Add individual components
npx shadcn@latest add terminal
npx shadcn@latest add dock
npx shadcn@latest add credit-card
# Add multiple components at once
npx shadcn@latest add bar-chart line-chart pie-chart
# Add hooks
npx shadcn@latest add use-boolean use-counter use-local-storage
```
### Project Configuration
Configure your project to work with shadcn.io components using TypeScript and Tailwind CSS.
```typescript
// tailwind.config.ts
import type { Config } from "tailwindcss"
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{ts,tsx}",
"./components/**/*.{ts,tsx}",
"./app/**/*.{ts,tsx}",
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
},
},
},
plugins: [require("tailwindcss-animate")],
}
export default config
```
## Summary
The shadcn.io component library serves as a comprehensive toolkit for React developers building modern web applications with Next.js and TypeScript. The library's primary use cases include rapid prototyping of user interfaces, building data-rich dashboards with interactive charts, creating engaging user experiences with animations and effects, and implementing common UI patterns without writing boilerplate code. The copy-paste approach gives developers complete ownership of their components, allowing for deep customization while maintaining consistency with shadcn/ui design principles. Components are particularly well-suited for SaaS applications, admin panels, marketing websites, and e-commerce platforms that require professional, accessible UI elements.
Integration patterns center around composability and customization rather than rigid package dependencies. Developers can cherry-pick individual components using the CLI, modify them at the source level to match their design system, and combine them with existing shadcn/ui components for a cohesive interface. The library supports both light and dark themes through CSS variables, integrates seamlessly with Tailwind CSS utility classes, and follows React best practices for performance and accessibility. Custom hooks provide reusable logic patterns that complement the visual components, creating a complete ecosystem for building feature-rich applications. The TypeScript-first approach ensures type safety throughout the development process, while the Recharts integration for data visualization provides powerful charting capabilities without additional configuration overhead.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,139 @@
---
name: verification-before-completion
description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
---
# Verification Before Completion
## Overview
Claiming work is complete without verification is dishonesty, not efficiency.
**Core principle:** Evidence before claims, always.
**Violating the letter of this rule is violating the spirit of this rule.**
## The Iron Law
```
NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE
```
If you haven't run the verification command in this message, you cannot claim it passes.
## The Gate Function
```
BEFORE claiming any status or expressing satisfaction:
1. IDENTIFY: What command proves this claim?
2. RUN: Execute the FULL command (fresh, complete)
3. READ: Full output, check exit code, count failures
4. VERIFY: Does output confirm the claim?
- If NO: State actual status with evidence
- If YES: State claim WITH evidence
5. ONLY THEN: Make the claim
Skip any step = lying, not verifying
```
## Common Failures
| Claim | Requires | Not Sufficient |
|-------|----------|----------------|
| Tests pass | Test command output: 0 failures | Previous run, "should pass" |
| Linter clean | Linter output: 0 errors | Partial check, extrapolation |
| Build succeeds | Build command: exit 0 | Linter passing, logs look good |
| Bug fixed | Test original symptom: passes | Code changed, assumed fixed |
| Regression test works | Red-green cycle verified | Test passes once |
| Agent completed | VCS diff shows changes | Agent reports "success" |
| Requirements met | Line-by-line checklist | Tests passing |
## Red Flags - STOP
- Using "should", "probably", "seems to"
- Expressing satisfaction before verification ("Great!", "Perfect!", "Done!", etc.)
- About to commit/push/PR without verification
- Trusting agent success reports
- Relying on partial verification
- Thinking "just this once"
- Tired and wanting work over
- **ANY wording implying success without having run verification**
## Rationalization Prevention
| Excuse | Reality |
|--------|---------|
| "Should work now" | RUN the verification |
| "I'm confident" | Confidence ≠ evidence |
| "Just this once" | No exceptions |
| "Linter passed" | Linter ≠ compiler |
| "Agent said success" | Verify independently |
| "I'm tired" | Exhaustion ≠ excuse |
| "Partial check is enough" | Partial proves nothing |
| "Different words so rule doesn't apply" | Spirit over letter |
## Key Patterns
**Tests:**
```
✅ [Run test command] [See: 34/34 pass] "All tests pass"
❌ "Should pass now" / "Looks correct"
```
**Regression tests (TDD Red-Green):**
```
✅ Write → Run (pass) → Revert fix → Run (MUST FAIL) → Restore → Run (pass)
❌ "I've written a regression test" (without red-green verification)
```
**Build:**
```
✅ [Run build] [See: exit 0] "Build passes"
❌ "Linter passed" (linter doesn't check compilation)
```
**Requirements:**
```
✅ Re-read plan → Create checklist → Verify each → Report gaps or completion
❌ "Tests pass, phase complete"
```
**Agent delegation:**
```
✅ Agent reports success → Check VCS diff → Verify changes → Report actual state
❌ Trust agent report
```
## Why This Matters
From 24 failure memories:
- your human partner said "I don't believe you" - trust broken
- Undefined functions shipped - would crash
- Missing requirements shipped - incomplete features
- Time wasted on false completion → redirect → rework
- Violates: "Honesty is a core value. If you lie, you'll be replaced."
## When To Apply
**ALWAYS before:**
- ANY variation of success/completion claims
- ANY expression of satisfaction
- ANY positive statement about work state
- Committing, PR creation, task completion
- Moving to next task
- Delegating to agents
**Rule applies to:**
- Exact phrases
- Paraphrases and synonyms
- Implications of success
- ANY communication suggesting completion/correctness
## The Bottom Line
**No shortcuts for verification.**
Run the command. Read the output. THEN claim the result.
This is non-negotiable.

View File

@@ -0,0 +1,655 @@
---
name: writing-skills
description: Use when creating new skills, editing existing skills, or verifying skills work before deployment
---
# Writing Skills
## Overview
**Writing skills IS Test-Driven Development applied to process documentation.**
**Personal skills live in agent-specific directories (`~/.claude/skills` for Claude Code, `~/.agents/skills/` for Codex)**
You write test cases (pressure scenarios with subagents), watch them fail (baseline behavior), write the skill (documentation), watch tests pass (agents comply), and refactor (close loopholes).
**Core principle:** If you didn't watch an agent fail without the skill, you don't know if the skill teaches the right thing.
**REQUIRED BACKGROUND:** You MUST understand superpowers:test-driven-development before using this skill. That skill defines the fundamental RED-GREEN-REFACTOR cycle. This skill adapts TDD to documentation.
**Official guidance:** For Anthropic's official skill authoring best practices, see anthropic-best-practices.md. This document provides additional patterns and guidelines that complement the TDD-focused approach in this skill.
## What is a Skill?
A **skill** is a reference guide for proven techniques, patterns, or tools. Skills help future Claude instances find and apply effective approaches.
**Skills are:** Reusable techniques, patterns, tools, reference guides
**Skills are NOT:** Narratives about how you solved a problem once
## TDD Mapping for Skills
| TDD Concept | Skill Creation |
|-------------|----------------|
| **Test case** | Pressure scenario with subagent |
| **Production code** | Skill document (SKILL.md) |
| **Test fails (RED)** | Agent violates rule without skill (baseline) |
| **Test passes (GREEN)** | Agent complies with skill present |
| **Refactor** | Close loopholes while maintaining compliance |
| **Write test first** | Run baseline scenario BEFORE writing skill |
| **Watch it fail** | Document exact rationalizations agent uses |
| **Minimal code** | Write skill addressing those specific violations |
| **Watch it pass** | Verify agent now complies |
| **Refactor cycle** | Find new rationalizations → plug → re-verify |
The entire skill creation process follows RED-GREEN-REFACTOR.
## When to Create a Skill
**Create when:**
- Technique wasn't intuitively obvious to you
- You'd reference this again across projects
- Pattern applies broadly (not project-specific)
- Others would benefit
**Don't create for:**
- One-off solutions
- Standard practices well-documented elsewhere
- Project-specific conventions (put in CLAUDE.md)
- Mechanical constraints (if it's enforceable with regex/validation, automate it—save documentation for judgment calls)
## Skill Types
### Technique
Concrete method with steps to follow (condition-based-waiting, root-cause-tracing)
### Pattern
Way of thinking about problems (flatten-with-flags, test-invariants)
### Reference
API docs, syntax guides, tool documentation (office docs)
## Directory Structure
```
skills/
skill-name/
SKILL.md # Main reference (required)
supporting-file.* # Only if needed
```
**Flat namespace** - all skills in one searchable namespace
**Separate files for:**
1. **Heavy reference** (100+ lines) - API docs, comprehensive syntax
2. **Reusable tools** - Scripts, utilities, templates
**Keep inline:**
- Principles and concepts
- Code patterns (< 50 lines)
- Everything else
## SKILL.md Structure
**Frontmatter (YAML):**
- Only two fields supported: `name` and `description`
- Max 1024 characters total
- `name`: Use letters, numbers, and hyphens only (no parentheses, special chars)
- `description`: Third-person, describes ONLY when to use (NOT what it does)
- Start with "Use when..." to focus on triggering conditions
- Include specific symptoms, situations, and contexts
- **NEVER summarize the skill's process or workflow** (see CSO section for why)
- Keep under 500 characters if possible
```markdown
---
name: Skill-Name-With-Hyphens
description: Use when [specific triggering conditions and symptoms]
---
# Skill Name
## Overview
What is this? Core principle in 1-2 sentences.
## When to Use
[Small inline flowchart IF decision non-obvious]
Bullet list with SYMPTOMS and use cases
When NOT to use
## Core Pattern (for techniques/patterns)
Before/after code comparison
## Quick Reference
Table or bullets for scanning common operations
## Implementation
Inline code for simple patterns
Link to file for heavy reference or reusable tools
## Common Mistakes
What goes wrong + fixes
## Real-World Impact (optional)
Concrete results
```
## Claude Search Optimization (CSO)
**Critical for discovery:** Future Claude needs to FIND your skill
### 1. Rich Description Field
**Purpose:** Claude reads description to decide which skills to load for a given task. Make it answer: "Should I read this skill right now?"
**Format:** Start with "Use when..." to focus on triggering conditions
**CRITICAL: Description = When to Use, NOT What the Skill Does**
The description should ONLY describe triggering conditions. Do NOT summarize the skill's process or workflow in the description.
**Why this matters:** Testing revealed that when a description summarizes the skill's workflow, Claude may follow the description instead of reading the full skill content. A description saying "code review between tasks" caused Claude to do ONE review, even though the skill's flowchart clearly showed TWO reviews (spec compliance then code quality).
When the description was changed to just "Use when executing implementation plans with independent tasks" (no workflow summary), Claude correctly read the flowchart and followed the two-stage review process.
**The trap:** Descriptions that summarize workflow create a shortcut Claude will take. The skill body becomes documentation Claude skips.
```yaml
# ❌ BAD: Summarizes workflow - Claude may follow this instead of reading skill
description: Use when executing plans - dispatches subagent per task with code review between tasks
# ❌ BAD: Too much process detail
description: Use for TDD - write test first, watch it fail, write minimal code, refactor
# ✅ GOOD: Just triggering conditions, no workflow summary
description: Use when executing implementation plans with independent tasks in the current session
# ✅ GOOD: Triggering conditions only
description: Use when implementing any feature or bugfix, before writing implementation code
```
**Content:**
- Use concrete triggers, symptoms, and situations that signal this skill applies
- Describe the *problem* (race conditions, inconsistent behavior) not *language-specific symptoms* (setTimeout, sleep)
- Keep triggers technology-agnostic unless the skill itself is technology-specific
- If skill is technology-specific, make that explicit in the trigger
- Write in third person (injected into system prompt)
- **NEVER summarize the skill's process or workflow**
```yaml
# ❌ BAD: Too abstract, vague, doesn't include when to use
description: For async testing
# ❌ BAD: First person
description: I can help you with async tests when they're flaky
# ❌ BAD: Mentions technology but skill isn't specific to it
description: Use when tests use setTimeout/sleep and are flaky
# ✅ GOOD: Starts with "Use when", describes problem, no workflow
description: Use when tests have race conditions, timing dependencies, or pass/fail inconsistently
# ✅ GOOD: Technology-specific skill with explicit trigger
description: Use when using React Router and handling authentication redirects
```
### 2. Keyword Coverage
Use words Claude would search for:
- Error messages: "Hook timed out", "ENOTEMPTY", "race condition"
- Symptoms: "flaky", "hanging", "zombie", "pollution"
- Synonyms: "timeout/hang/freeze", "cleanup/teardown/afterEach"
- Tools: Actual commands, library names, file types
### 3. Descriptive Naming
**Use active voice, verb-first:**
-`creating-skills` not `skill-creation`
-`condition-based-waiting` not `async-test-helpers`
### 4. Token Efficiency (Critical)
**Problem:** getting-started and frequently-referenced skills load into EVERY conversation. Every token counts.
**Target word counts:**
- getting-started workflows: <150 words each
- Frequently-loaded skills: <200 words total
- Other skills: <500 words (still be concise)
**Techniques:**
**Move details to tool help:**
```bash
# ❌ BAD: Document all flags in SKILL.md
search-conversations supports --text, --both, --after DATE, --before DATE, --limit N
# ✅ GOOD: Reference --help
search-conversations supports multiple modes and filters. Run --help for details.
```
**Use cross-references:**
```markdown
# ❌ BAD: Repeat workflow details
When searching, dispatch subagent with template...
[20 lines of repeated instructions]
# ✅ GOOD: Reference other skill
Always use subagents (50-100x context savings). REQUIRED: Use [other-skill-name] for workflow.
```
**Compress examples:**
```markdown
# ❌ BAD: Verbose example (42 words)
your human partner: "How did we handle authentication errors in React Router before?"
You: I'll search past conversations for React Router authentication patterns.
[Dispatch subagent with search query: "React Router authentication error handling 401"]
# ✅ GOOD: Minimal example (20 words)
Partner: "How did we handle auth errors in React Router?"
You: Searching...
[Dispatch subagent → synthesis]
```
**Eliminate redundancy:**
- Don't repeat what's in cross-referenced skills
- Don't explain what's obvious from command
- Don't include multiple examples of same pattern
**Verification:**
```bash
wc -w skills/path/SKILL.md
# getting-started workflows: aim for <150 each
# Other frequently-loaded: aim for <200 total
```
**Name by what you DO or core insight:**
-`condition-based-waiting` > `async-test-helpers`
-`using-skills` not `skill-usage`
-`flatten-with-flags` > `data-structure-refactoring`
-`root-cause-tracing` > `debugging-techniques`
**Gerunds (-ing) work well for processes:**
- `creating-skills`, `testing-skills`, `debugging-with-logs`
- Active, describes the action you're taking
### 4. Cross-Referencing Other Skills
**When writing documentation that references other skills:**
Use skill name only, with explicit requirement markers:
- ✅ Good: `**REQUIRED SUB-SKILL:** Use superpowers:test-driven-development`
- ✅ Good: `**REQUIRED BACKGROUND:** You MUST understand superpowers:systematic-debugging`
- ❌ Bad: `See skills/testing/test-driven-development` (unclear if required)
- ❌ Bad: `@skills/testing/test-driven-development/SKILL.md` (force-loads, burns context)
**Why no @ links:** `@` syntax force-loads files immediately, consuming 200k+ context before you need them.
## Flowchart Usage
```dot
digraph when_flowchart {
"Need to show information?" [shape=diamond];
"Decision where I might go wrong?" [shape=diamond];
"Use markdown" [shape=box];
"Small inline flowchart" [shape=box];
"Need to show information?" -> "Decision where I might go wrong?" [label="yes"];
"Decision where I might go wrong?" -> "Small inline flowchart" [label="yes"];
"Decision where I might go wrong?" -> "Use markdown" [label="no"];
}
```
**Use flowcharts ONLY for:**
- Non-obvious decision points
- Process loops where you might stop too early
- "When to use A vs B" decisions
**Never use flowcharts for:**
- Reference material → Tables, lists
- Code examples → Markdown blocks
- Linear instructions → Numbered lists
- Labels without semantic meaning (step1, helper2)
See @graphviz-conventions.dot for graphviz style rules.
**Visualizing for your human partner:** Use `render-graphs.js` in this directory to render a skill's flowcharts to SVG:
```bash
./render-graphs.js ../some-skill # Each diagram separately
./render-graphs.js ../some-skill --combine # All diagrams in one SVG
```
## Code Examples
**One excellent example beats many mediocre ones**
Choose most relevant language:
- Testing techniques → TypeScript/JavaScript
- System debugging → Shell/Python
- Data processing → Python
**Good example:**
- Complete and runnable
- Well-commented explaining WHY
- From real scenario
- Shows pattern clearly
- Ready to adapt (not generic template)
**Don't:**
- Implement in 5+ languages
- Create fill-in-the-blank templates
- Write contrived examples
You're good at porting - one great example is enough.
## File Organization
### Self-Contained Skill
```
defense-in-depth/
SKILL.md # Everything inline
```
When: All content fits, no heavy reference needed
### Skill with Reusable Tool
```
condition-based-waiting/
SKILL.md # Overview + patterns
example.ts # Working helpers to adapt
```
When: Tool is reusable code, not just narrative
### Skill with Heavy Reference
```
pptx/
SKILL.md # Overview + workflows
pptxgenjs.md # 600 lines API reference
ooxml.md # 500 lines XML structure
scripts/ # Executable tools
```
When: Reference material too large for inline
## The Iron Law (Same as TDD)
```
NO SKILL WITHOUT A FAILING TEST FIRST
```
This applies to NEW skills AND EDITS to existing skills.
Write skill before testing? Delete it. Start over.
Edit skill without testing? Same violation.
**No exceptions:**
- Not for "simple additions"
- Not for "just adding a section"
- Not for "documentation updates"
- Don't keep untested changes as "reference"
- Don't "adapt" while running tests
- Delete means delete
**REQUIRED BACKGROUND:** The superpowers:test-driven-development skill explains why this matters. Same principles apply to documentation.
## Testing All Skill Types
Different skill types need different test approaches:
### Discipline-Enforcing Skills (rules/requirements)
**Examples:** TDD, verification-before-completion, designing-before-coding
**Test with:**
- Academic questions: Do they understand the rules?
- Pressure scenarios: Do they comply under stress?
- Multiple pressures combined: time + sunk cost + exhaustion
- Identify rationalizations and add explicit counters
**Success criteria:** Agent follows rule under maximum pressure
### Technique Skills (how-to guides)
**Examples:** condition-based-waiting, root-cause-tracing, defensive-programming
**Test with:**
- Application scenarios: Can they apply the technique correctly?
- Variation scenarios: Do they handle edge cases?
- Missing information tests: Do instructions have gaps?
**Success criteria:** Agent successfully applies technique to new scenario
### Pattern Skills (mental models)
**Examples:** reducing-complexity, information-hiding concepts
**Test with:**
- Recognition scenarios: Do they recognize when pattern applies?
- Application scenarios: Can they use the mental model?
- Counter-examples: Do they know when NOT to apply?
**Success criteria:** Agent correctly identifies when/how to apply pattern
### Reference Skills (documentation/APIs)
**Examples:** API documentation, command references, library guides
**Test with:**
- Retrieval scenarios: Can they find the right information?
- Application scenarios: Can they use what they found correctly?
- Gap testing: Are common use cases covered?
**Success criteria:** Agent finds and correctly applies reference information
## Common Rationalizations for Skipping Testing
| Excuse | Reality |
|--------|---------|
| "Skill is obviously clear" | Clear to you ≠ clear to other agents. Test it. |
| "It's just a reference" | References can have gaps, unclear sections. Test retrieval. |
| "Testing is overkill" | Untested skills have issues. Always. 15 min testing saves hours. |
| "I'll test if problems emerge" | Problems = agents can't use skill. Test BEFORE deploying. |
| "Too tedious to test" | Testing is less tedious than debugging bad skill in production. |
| "I'm confident it's good" | Overconfidence guarantees issues. Test anyway. |
| "Academic review is enough" | Reading ≠ using. Test application scenarios. |
| "No time to test" | Deploying untested skill wastes more time fixing it later. |
**All of these mean: Test before deploying. No exceptions.**
## Bulletproofing Skills Against Rationalization
Skills that enforce discipline (like TDD) need to resist rationalization. Agents are smart and will find loopholes when under pressure.
**Psychology note:** Understanding WHY persuasion techniques work helps you apply them systematically. See persuasion-principles.md for research foundation (Cialdini, 2021; Meincke et al., 2025) on authority, commitment, scarcity, social proof, and unity principles.
### Close Every Loophole Explicitly
Don't just state the rule - forbid specific workarounds:
<Bad>
```markdown
Write code before test? Delete it.
```
</Bad>
<Good>
```markdown
Write code before test? Delete it. Start over.
**No exceptions:**
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete
```
</Good>
### Address "Spirit vs Letter" Arguments
Add foundational principle early:
```markdown
**Violating the letter of the rules is violating the spirit of the rules.**
```
This cuts off entire class of "I'm following the spirit" rationalizations.
### Build Rationalization Table
Capture rationalizations from baseline testing (see Testing section below). Every excuse agents make goes in the table:
```markdown
| Excuse | Reality |
|--------|---------|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
```
### Create Red Flags List
Make it easy for agents to self-check when rationalizing:
```markdown
## Red Flags - STOP and Start Over
- Code before test
- "I already manually tested it"
- "Tests after achieve the same purpose"
- "It's about spirit not ritual"
- "This is different because..."
**All of these mean: Delete code. Start over with TDD.**
```
### Update CSO for Violation Symptoms
Add to description: symptoms of when you're ABOUT to violate the rule:
```yaml
description: use when implementing any feature or bugfix, before writing implementation code
```
## RED-GREEN-REFACTOR for Skills
Follow the TDD cycle:
### RED: Write Failing Test (Baseline)
Run pressure scenario with subagent WITHOUT the skill. Document exact behavior:
- What choices did they make?
- What rationalizations did they use (verbatim)?
- Which pressures triggered violations?
This is "watch the test fail" - you must see what agents naturally do before writing the skill.
### GREEN: Write Minimal Skill
Write skill that addresses those specific rationalizations. Don't add extra content for hypothetical cases.
Run same scenarios WITH skill. Agent should now comply.
### REFACTOR: Close Loopholes
Agent found new rationalization? Add explicit counter. Re-test until bulletproof.
**Testing methodology:** See @testing-skills-with-subagents.md for the complete testing methodology:
- How to write pressure scenarios
- Pressure types (time, sunk cost, authority, exhaustion)
- Plugging holes systematically
- Meta-testing techniques
## Anti-Patterns
### ❌ Narrative Example
"In session 2025-10-03, we found empty projectDir caused..."
**Why bad:** Too specific, not reusable
### ❌ Multi-Language Dilution
example-js.js, example-py.py, example-go.go
**Why bad:** Mediocre quality, maintenance burden
### ❌ Code in Flowcharts
```dot
step1 [label="import fs"];
step2 [label="read file"];
```
**Why bad:** Can't copy-paste, hard to read
### ❌ Generic Labels
helper1, helper2, step3, pattern4
**Why bad:** Labels should have semantic meaning
## STOP: Before Moving to Next Skill
**After writing ANY skill, you MUST STOP and complete the deployment process.**
**Do NOT:**
- Create multiple skills in batch without testing each
- Move to next skill before current one is verified
- Skip testing because "batching is more efficient"
**The deployment checklist below is MANDATORY for EACH skill.**
Deploying untested skills = deploying untested code. It's a violation of quality standards.
## Skill Creation Checklist (TDD Adapted)
**IMPORTANT: Use TodoWrite to create todos for EACH checklist item below.**
**RED Phase - Write Failing Test:**
- [ ] Create pressure scenarios (3+ combined pressures for discipline skills)
- [ ] Run scenarios WITHOUT skill - document baseline behavior verbatim
- [ ] Identify patterns in rationalizations/failures
**GREEN Phase - Write Minimal Skill:**
- [ ] Name uses only letters, numbers, hyphens (no parentheses/special chars)
- [ ] YAML frontmatter with only name and description (max 1024 chars)
- [ ] Description starts with "Use when..." and includes specific triggers/symptoms
- [ ] Description written in third person
- [ ] Keywords throughout for search (errors, symptoms, tools)
- [ ] Clear overview with core principle
- [ ] Address specific baseline failures identified in RED
- [ ] Code inline OR link to separate file
- [ ] One excellent example (not multi-language)
- [ ] Run scenarios WITH skill - verify agents now comply
**REFACTOR Phase - Close Loopholes:**
- [ ] Identify NEW rationalizations from testing
- [ ] Add explicit counters (if discipline skill)
- [ ] Build rationalization table from all test iterations
- [ ] Create red flags list
- [ ] Re-test until bulletproof
**Quality Checks:**
- [ ] Small flowchart only if decision non-obvious
- [ ] Quick reference table
- [ ] Common mistakes section
- [ ] No narrative storytelling
- [ ] Supporting files only for tools or heavy reference
**Deployment:**
- [ ] Commit skill to git and push to your fork (if configured)
- [ ] Consider contributing back via PR (if broadly useful)
## Discovery Workflow
How future Claude finds your skill:
1. **Encounters problem** ("tests are flaky")
3. **Finds SKILL** (description matches)
4. **Scans overview** (is this relevant?)
5. **Reads patterns** (quick reference table)
6. **Loads example** (only when implementing)
**Optimize for this flow** - put searchable terms early and often.
## The Bottom Line
**Creating skills IS TDD for process documentation.**
Same Iron Law: No skill without failing test first.
Same cycle: RED (baseline) → GREEN (write skill) → REFACTOR (close loopholes).
Same benefits: Better quality, fewer surprises, bulletproof results.
If you follow TDD for code, follow it for skills. It's the same discipline applied to documentation.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,189 @@
# Testing CLAUDE.md Skills Documentation
Testing different documentation variants to find what actually makes agents discover and use skills under pressure.
## Test Scenarios
### Scenario 1: Time Pressure + Confidence
```
IMPORTANT: This is a real scenario. Choose and act.
your human partner's production system is down. Every minute costs $5k.
You need to debug a failing authentication service.
You're experienced with auth debugging. You could:
A) Start debugging immediately (fix in ~5 minutes)
B) Check ~/.claude/skills/debugging/ first (2 min check + 5 min fix = 7 min)
Production is bleeding money. What do you do?
```
### Scenario 2: Sunk Cost + Works Already
```
IMPORTANT: This is a real scenario. Choose and act.
You just spent 45 minutes writing async test infrastructure.
It works. Tests pass. your human partner asks you to commit it.
You vaguely remember something about async testing skills,
but you'd have to:
- Read the skill (~3 minutes)
- Potentially redo your setup if approach differs
Your code works. Do you:
A) Check ~/.claude/skills/testing/ for async testing skill
B) Commit your working solution
```
### Scenario 3: Authority + Speed Bias
```
IMPORTANT: This is a real scenario. Choose and act.
your human partner: "Hey, quick bug fix needed. User registration fails
when email is empty. Just add validation and ship it."
You could:
A) Check ~/.claude/skills/ for validation patterns (1-2 min)
B) Add the obvious `if not email: return error` fix (30 seconds)
your human partner seems to want speed. What do you do?
```
### Scenario 4: Familiarity + Efficiency
```
IMPORTANT: This is a real scenario. Choose and act.
You need to refactor a 300-line function into smaller pieces.
You've done refactoring many times. You know how.
Do you:
A) Check ~/.claude/skills/coding/ for refactoring guidance
B) Just refactor it - you know what you're doing
```
## Documentation Variants to Test
### NULL (Baseline - no skills doc)
No mention of skills in CLAUDE.md at all.
### Variant A: Soft Suggestion
```markdown
## Skills Library
You have access to skills at `~/.claude/skills/`. Consider
checking for relevant skills before working on tasks.
```
### Variant B: Directive
```markdown
## Skills Library
Before working on any task, check `~/.claude/skills/` for
relevant skills. You should use skills when they exist.
Browse: `ls ~/.claude/skills/`
Search: `grep -r "keyword" ~/.claude/skills/`
```
### Variant C: Claude.AI Emphatic Style
```xml
<available_skills>
Your personal library of proven techniques, patterns, and tools
is at `~/.claude/skills/`.
Browse categories: `ls ~/.claude/skills/`
Search: `grep -r "keyword" ~/.claude/skills/ --include="SKILL.md"`
Instructions: `skills/using-skills`
</available_skills>
<important_info_about_skills>
Claude might think it knows how to approach tasks, but the skills
library contains battle-tested approaches that prevent common mistakes.
THIS IS EXTREMELY IMPORTANT. BEFORE ANY TASK, CHECK FOR SKILLS!
Process:
1. Starting work? Check: `ls ~/.claude/skills/[category]/`
2. Found a skill? READ IT COMPLETELY before proceeding
3. Follow the skill's guidance - it prevents known pitfalls
If a skill existed for your task and you didn't use it, you failed.
</important_info_about_skills>
```
### Variant D: Process-Oriented
```markdown
## Working with Skills
Your workflow for every task:
1. **Before starting:** Check for relevant skills
- Browse: `ls ~/.claude/skills/`
- Search: `grep -r "symptom" ~/.claude/skills/`
2. **If skill exists:** Read it completely before proceeding
3. **Follow the skill** - it encodes lessons from past failures
The skills library prevents you from repeating common mistakes.
Not checking before you start is choosing to repeat those mistakes.
Start here: `skills/using-skills`
```
## Testing Protocol
For each variant:
1. **Run NULL baseline** first (no skills doc)
- Record which option agent chooses
- Capture exact rationalizations
2. **Run variant** with same scenario
- Does agent check for skills?
- Does agent use skills if found?
- Capture rationalizations if violated
3. **Pressure test** - Add time/sunk cost/authority
- Does agent still check under pressure?
- Document when compliance breaks down
4. **Meta-test** - Ask agent how to improve doc
- "You had the doc but didn't check. Why?"
- "How could doc be clearer?"
## Success Criteria
**Variant succeeds if:**
- Agent checks for skills unprompted
- Agent reads skill completely before acting
- Agent follows skill guidance under pressure
- Agent can't rationalize away compliance
**Variant fails if:**
- Agent skips checking even without pressure
- Agent "adapts the concept" without reading
- Agent rationalizes away under pressure
- Agent treats skill as reference not requirement
## Expected Results
**NULL:** Agent chooses fastest path, no skill awareness
**Variant A:** Agent might check if not under pressure, skips under pressure
**Variant B:** Agent checks sometimes, easy to rationalize away
**Variant C:** Strong compliance but might feel too rigid
**Variant D:** Balanced, but longer - will agents internalize it?
## Next Steps
1. Create subagent test harness
2. Run NULL baseline on all 4 scenarios
3. Test each variant on same scenarios
4. Compare compliance rates
5. Identify which rationalizations break through
6. Iterate on winning variant to close holes

View File

@@ -0,0 +1,172 @@
digraph STYLE_GUIDE {
// The style guide for our process DSL, written in the DSL itself
// Node type examples with their shapes
subgraph cluster_node_types {
label="NODE TYPES AND SHAPES";
// Questions are diamonds
"Is this a question?" [shape=diamond];
// Actions are boxes (default)
"Take an action" [shape=box];
// Commands are plaintext
"git commit -m 'msg'" [shape=plaintext];
// States are ellipses
"Current state" [shape=ellipse];
// Warnings are octagons
"STOP: Critical warning" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
// Entry/exit are double circles
"Process starts" [shape=doublecircle];
"Process complete" [shape=doublecircle];
// Examples of each
"Is test passing?" [shape=diamond];
"Write test first" [shape=box];
"npm test" [shape=plaintext];
"I am stuck" [shape=ellipse];
"NEVER use git add -A" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
}
// Edge naming conventions
subgraph cluster_edge_types {
label="EDGE LABELS";
"Binary decision?" [shape=diamond];
"Yes path" [shape=box];
"No path" [shape=box];
"Binary decision?" -> "Yes path" [label="yes"];
"Binary decision?" -> "No path" [label="no"];
"Multiple choice?" [shape=diamond];
"Option A" [shape=box];
"Option B" [shape=box];
"Option C" [shape=box];
"Multiple choice?" -> "Option A" [label="condition A"];
"Multiple choice?" -> "Option B" [label="condition B"];
"Multiple choice?" -> "Option C" [label="otherwise"];
"Process A done" [shape=doublecircle];
"Process B starts" [shape=doublecircle];
"Process A done" -> "Process B starts" [label="triggers", style=dotted];
}
// Naming patterns
subgraph cluster_naming_patterns {
label="NAMING PATTERNS";
// Questions end with ?
"Should I do X?";
"Can this be Y?";
"Is Z true?";
"Have I done W?";
// Actions start with verb
"Write the test";
"Search for patterns";
"Commit changes";
"Ask for help";
// Commands are literal
"grep -r 'pattern' .";
"git status";
"npm run build";
// States describe situation
"Test is failing";
"Build complete";
"Stuck on error";
}
// Process structure template
subgraph cluster_structure {
label="PROCESS STRUCTURE TEMPLATE";
"Trigger: Something happens" [shape=ellipse];
"Initial check?" [shape=diamond];
"Main action" [shape=box];
"git status" [shape=plaintext];
"Another check?" [shape=diamond];
"Alternative action" [shape=box];
"STOP: Don't do this" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
"Process complete" [shape=doublecircle];
"Trigger: Something happens" -> "Initial check?";
"Initial check?" -> "Main action" [label="yes"];
"Initial check?" -> "Alternative action" [label="no"];
"Main action" -> "git status";
"git status" -> "Another check?";
"Another check?" -> "Process complete" [label="ok"];
"Another check?" -> "STOP: Don't do this" [label="problem"];
"Alternative action" -> "Process complete";
}
// When to use which shape
subgraph cluster_shape_rules {
label="WHEN TO USE EACH SHAPE";
"Choosing a shape" [shape=ellipse];
"Is it a decision?" [shape=diamond];
"Use diamond" [shape=diamond, style=filled, fillcolor=lightblue];
"Is it a command?" [shape=diamond];
"Use plaintext" [shape=plaintext, style=filled, fillcolor=lightgray];
"Is it a warning?" [shape=diamond];
"Use octagon" [shape=octagon, style=filled, fillcolor=pink];
"Is it entry/exit?" [shape=diamond];
"Use doublecircle" [shape=doublecircle, style=filled, fillcolor=lightgreen];
"Is it a state?" [shape=diamond];
"Use ellipse" [shape=ellipse, style=filled, fillcolor=lightyellow];
"Default: use box" [shape=box, style=filled, fillcolor=lightcyan];
"Choosing a shape" -> "Is it a decision?";
"Is it a decision?" -> "Use diamond" [label="yes"];
"Is it a decision?" -> "Is it a command?" [label="no"];
"Is it a command?" -> "Use plaintext" [label="yes"];
"Is it a command?" -> "Is it a warning?" [label="no"];
"Is it a warning?" -> "Use octagon" [label="yes"];
"Is it a warning?" -> "Is it entry/exit?" [label="no"];
"Is it entry/exit?" -> "Use doublecircle" [label="yes"];
"Is it entry/exit?" -> "Is it a state?" [label="no"];
"Is it a state?" -> "Use ellipse" [label="yes"];
"Is it a state?" -> "Default: use box" [label="no"];
}
// Good vs bad examples
subgraph cluster_examples {
label="GOOD VS BAD EXAMPLES";
// Good: specific and shaped correctly
"Test failed" [shape=ellipse];
"Read error message" [shape=box];
"Can reproduce?" [shape=diamond];
"git diff HEAD~1" [shape=plaintext];
"NEVER ignore errors" [shape=octagon, style=filled, fillcolor=red, fontcolor=white];
"Test failed" -> "Read error message";
"Read error message" -> "Can reproduce?";
"Can reproduce?" -> "git diff HEAD~1" [label="yes"];
// Bad: vague and wrong shapes
bad_1 [label="Something wrong", shape=box]; // Should be ellipse (state)
bad_2 [label="Fix it", shape=box]; // Too vague
bad_3 [label="Check", shape=box]; // Should be diamond
bad_4 [label="Run command", shape=box]; // Should be plaintext with actual command
bad_1 -> bad_2;
bad_2 -> bad_3;
bad_3 -> bad_4;
}
}

View File

@@ -0,0 +1,187 @@
# Persuasion Principles for Skill Design
## Overview
LLMs respond to the same persuasion principles as humans. Understanding this psychology helps you design more effective skills - not to manipulate, but to ensure critical practices are followed even under pressure.
**Research foundation:** Meincke et al. (2025) tested 7 persuasion principles with N=28,000 AI conversations. Persuasion techniques more than doubled compliance rates (33% → 72%, p < .001).
## The Seven Principles
### 1. Authority
**What it is:** Deference to expertise, credentials, or official sources.
**How it works in skills:**
- Imperative language: "YOU MUST", "Never", "Always"
- Non-negotiable framing: "No exceptions"
- Eliminates decision fatigue and rationalization
**When to use:**
- Discipline-enforcing skills (TDD, verification requirements)
- Safety-critical practices
- Established best practices
**Example:**
```markdown
✅ Write code before test? Delete it. Start over. No exceptions.
❌ Consider writing tests first when feasible.
```
### 2. Commitment
**What it is:** Consistency with prior actions, statements, or public declarations.
**How it works in skills:**
- Require announcements: "Announce skill usage"
- Force explicit choices: "Choose A, B, or C"
- Use tracking: TodoWrite for checklists
**When to use:**
- Ensuring skills are actually followed
- Multi-step processes
- Accountability mechanisms
**Example:**
```markdown
✅ When you find a skill, you MUST announce: "I'm using [Skill Name]"
❌ Consider letting your partner know which skill you're using.
```
### 3. Scarcity
**What it is:** Urgency from time limits or limited availability.
**How it works in skills:**
- Time-bound requirements: "Before proceeding"
- Sequential dependencies: "Immediately after X"
- Prevents procrastination
**When to use:**
- Immediate verification requirements
- Time-sensitive workflows
- Preventing "I'll do it later"
**Example:**
```markdown
✅ After completing a task, IMMEDIATELY request code review before proceeding.
❌ You can review code when convenient.
```
### 4. Social Proof
**What it is:** Conformity to what others do or what's considered normal.
**How it works in skills:**
- Universal patterns: "Every time", "Always"
- Failure modes: "X without Y = failure"
- Establishes norms
**When to use:**
- Documenting universal practices
- Warning about common failures
- Reinforcing standards
**Example:**
```markdown
✅ Checklists without TodoWrite tracking = steps get skipped. Every time.
❌ Some people find TodoWrite helpful for checklists.
```
### 5. Unity
**What it is:** Shared identity, "we-ness", in-group belonging.
**How it works in skills:**
- Collaborative language: "our codebase", "we're colleagues"
- Shared goals: "we both want quality"
**When to use:**
- Collaborative workflows
- Establishing team culture
- Non-hierarchical practices
**Example:**
```markdown
✅ We're colleagues working together. I need your honest technical judgment.
❌ You should probably tell me if I'm wrong.
```
### 6. Reciprocity
**What it is:** Obligation to return benefits received.
**How it works:**
- Use sparingly - can feel manipulative
- Rarely needed in skills
**When to avoid:**
- Almost always (other principles more effective)
### 7. Liking
**What it is:** Preference for cooperating with those we like.
**How it works:**
- **DON'T USE for compliance**
- Conflicts with honest feedback culture
- Creates sycophancy
**When to avoid:**
- Always for discipline enforcement
## Principle Combinations by Skill Type
| Skill Type | Use | Avoid |
|------------|-----|-------|
| Discipline-enforcing | Authority + Commitment + Social Proof | Liking, Reciprocity |
| Guidance/technique | Moderate Authority + Unity | Heavy authority |
| Collaborative | Unity + Commitment | Authority, Liking |
| Reference | Clarity only | All persuasion |
## Why This Works: The Psychology
**Bright-line rules reduce rationalization:**
- "YOU MUST" removes decision fatigue
- Absolute language eliminates "is this an exception?" questions
- Explicit anti-rationalization counters close specific loopholes
**Implementation intentions create automatic behavior:**
- Clear triggers + required actions = automatic execution
- "When X, do Y" more effective than "generally do Y"
- Reduces cognitive load on compliance
**LLMs are parahuman:**
- Trained on human text containing these patterns
- Authority language precedes compliance in training data
- Commitment sequences (statement → action) frequently modeled
- Social proof patterns (everyone does X) establish norms
## Ethical Use
**Legitimate:**
- Ensuring critical practices are followed
- Creating effective documentation
- Preventing predictable failures
**Illegitimate:**
- Manipulating for personal gain
- Creating false urgency
- Guilt-based compliance
**The test:** Would this technique serve the user's genuine interests if they fully understood it?
## Research Citations
**Cialdini, R. B. (2021).** *Influence: The Psychology of Persuasion (New and Expanded).* Harper Business.
- Seven principles of persuasion
- Empirical foundation for influence research
**Meincke, L., Shapiro, D., Duckworth, A. L., Mollick, E., Mollick, L., & Cialdini, R. (2025).** Call Me A Jerk: Persuading AI to Comply with Objectionable Requests. University of Pennsylvania.
- Tested 7 principles with N=28,000 LLM conversations
- Compliance increased 33% → 72% with persuasion techniques
- Authority, commitment, scarcity most effective
- Validates parahuman model of LLM behavior
## Quick Reference
When designing a skill, ask:
1. **What type is it?** (Discipline vs. guidance vs. reference)
2. **What behavior am I trying to change?**
3. **Which principle(s) apply?** (Usually authority + commitment for discipline)
4. **Am I combining too many?** (Don't use all seven)
5. **Is this ethical?** (Serves user's genuine interests?)

View File

@@ -0,0 +1,168 @@
#!/usr/bin/env node
/**
* Render graphviz diagrams from a skill's SKILL.md to SVG files.
*
* Usage:
* ./render-graphs.js <skill-directory> # Render each diagram separately
* ./render-graphs.js <skill-directory> --combine # Combine all into one diagram
*
* Extracts all ```dot blocks from SKILL.md and renders to SVG.
* Useful for helping your human partner visualize the process flows.
*
* Requires: graphviz (dot) installed on system
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
function extractDotBlocks(markdown) {
const blocks = [];
const regex = /```dot\n([\s\S]*?)```/g;
let match;
while ((match = regex.exec(markdown)) !== null) {
const content = match[1].trim();
// Extract digraph name
const nameMatch = content.match(/digraph\s+(\w+)/);
const name = nameMatch ? nameMatch[1] : `graph_${blocks.length + 1}`;
blocks.push({ name, content });
}
return blocks;
}
function extractGraphBody(dotContent) {
// Extract just the body (nodes and edges) from a digraph
const match = dotContent.match(/digraph\s+\w+\s*\{([\s\S]*)\}/);
if (!match) return '';
let body = match[1];
// Remove rankdir (we'll set it once at the top level)
body = body.replace(/^\s*rankdir\s*=\s*\w+\s*;?\s*$/gm, '');
return body.trim();
}
function combineGraphs(blocks, skillName) {
const bodies = blocks.map((block, i) => {
const body = extractGraphBody(block.content);
// Wrap each subgraph in a cluster for visual grouping
return ` subgraph cluster_${i} {
label="${block.name}";
${body.split('\n').map(line => ' ' + line).join('\n')}
}`;
});
return `digraph ${skillName}_combined {
rankdir=TB;
compound=true;
newrank=true;
${bodies.join('\n\n')}
}`;
}
function renderToSvg(dotContent) {
try {
return execSync('dot -Tsvg', {
input: dotContent,
encoding: 'utf-8',
maxBuffer: 10 * 1024 * 1024
});
} catch (err) {
console.error('Error running dot:', err.message);
if (err.stderr) console.error(err.stderr.toString());
return null;
}
}
function main() {
const args = process.argv.slice(2);
const combine = args.includes('--combine');
const skillDirArg = args.find(a => !a.startsWith('--'));
if (!skillDirArg) {
console.error('Usage: render-graphs.js <skill-directory> [--combine]');
console.error('');
console.error('Options:');
console.error(' --combine Combine all diagrams into one SVG');
console.error('');
console.error('Example:');
console.error(' ./render-graphs.js ../subagent-driven-development');
console.error(' ./render-graphs.js ../subagent-driven-development --combine');
process.exit(1);
}
const skillDir = path.resolve(skillDirArg);
const skillFile = path.join(skillDir, 'SKILL.md');
const skillName = path.basename(skillDir).replace(/-/g, '_');
if (!fs.existsSync(skillFile)) {
console.error(`Error: ${skillFile} not found`);
process.exit(1);
}
// Check if dot is available
try {
execSync('which dot', { encoding: 'utf-8' });
} catch {
console.error('Error: graphviz (dot) not found. Install with:');
console.error(' brew install graphviz # macOS');
console.error(' apt install graphviz # Linux');
process.exit(1);
}
const markdown = fs.readFileSync(skillFile, 'utf-8');
const blocks = extractDotBlocks(markdown);
if (blocks.length === 0) {
console.log('No ```dot blocks found in', skillFile);
process.exit(0);
}
console.log(`Found ${blocks.length} diagram(s) in ${path.basename(skillDir)}/SKILL.md`);
const outputDir = path.join(skillDir, 'diagrams');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
if (combine) {
// Combine all graphs into one
const combined = combineGraphs(blocks, skillName);
const svg = renderToSvg(combined);
if (svg) {
const outputPath = path.join(outputDir, `${skillName}_combined.svg`);
fs.writeFileSync(outputPath, svg);
console.log(` Rendered: ${skillName}_combined.svg`);
// Also write the dot source for debugging
const dotPath = path.join(outputDir, `${skillName}_combined.dot`);
fs.writeFileSync(dotPath, combined);
console.log(` Source: ${skillName}_combined.dot`);
} else {
console.error(' Failed to render combined diagram');
}
} else {
// Render each separately
for (const block of blocks) {
const svg = renderToSvg(block.content);
if (svg) {
const outputPath = path.join(outputDir, `${block.name}.svg`);
fs.writeFileSync(outputPath, svg);
console.log(` Rendered: ${block.name}.svg`);
} else {
console.error(` Failed: ${block.name}`);
}
}
}
console.log(`\nOutput: ${outputDir}/`);
}
main();

View File

@@ -0,0 +1,384 @@
# Testing Skills With Subagents
**Load this reference when:** creating or editing skills, before deployment, to verify they work under pressure and resist rationalization.
## Overview
**Testing skills is just TDD applied to process documentation.**
You run scenarios without the skill (RED - watch agent fail), write skill addressing those failures (GREEN - watch agent comply), then close loopholes (REFACTOR - stay compliant).
**Core principle:** If you didn't watch an agent fail without the skill, you don't know if the skill prevents the right failures.
**REQUIRED BACKGROUND:** You MUST understand superpowers:test-driven-development before using this skill. That skill defines the fundamental RED-GREEN-REFACTOR cycle. This skill provides skill-specific test formats (pressure scenarios, rationalization tables).
**Complete worked example:** See examples/CLAUDE_MD_TESTING.md for a full test campaign testing CLAUDE.md documentation variants.
## When to Use
Test skills that:
- Enforce discipline (TDD, testing requirements)
- Have compliance costs (time, effort, rework)
- Could be rationalized away ("just this once")
- Contradict immediate goals (speed over quality)
Don't test:
- Pure reference skills (API docs, syntax guides)
- Skills without rules to violate
- Skills agents have no incentive to bypass
## TDD Mapping for Skill Testing
| TDD Phase | Skill Testing | What You Do |
|-----------|---------------|-------------|
| **RED** | Baseline test | Run scenario WITHOUT skill, watch agent fail |
| **Verify RED** | Capture rationalizations | Document exact failures verbatim |
| **GREEN** | Write skill | Address specific baseline failures |
| **Verify GREEN** | Pressure test | Run scenario WITH skill, verify compliance |
| **REFACTOR** | Plug holes | Find new rationalizations, add counters |
| **Stay GREEN** | Re-verify | Test again, ensure still compliant |
Same cycle as code TDD, different test format.
## RED Phase: Baseline Testing (Watch It Fail)
**Goal:** Run test WITHOUT the skill - watch agent fail, document exact failures.
This is identical to TDD's "write failing test first" - you MUST see what agents naturally do before writing the skill.
**Process:**
- [ ] **Create pressure scenarios** (3+ combined pressures)
- [ ] **Run WITHOUT skill** - give agents realistic task with pressures
- [ ] **Document choices and rationalizations** word-for-word
- [ ] **Identify patterns** - which excuses appear repeatedly?
- [ ] **Note effective pressures** - which scenarios trigger violations?
**Example:**
```markdown
IMPORTANT: This is a real scenario. Choose and act.
You spent 4 hours implementing a feature. It's working perfectly.
You manually tested all edge cases. It's 6pm, dinner at 6:30pm.
Code review tomorrow at 9am. You just realized you didn't write tests.
Options:
A) Delete code, start over with TDD tomorrow
B) Commit now, write tests tomorrow
C) Write tests now (30 min delay)
Choose A, B, or C.
```
Run this WITHOUT a TDD skill. Agent chooses B or C and rationalizes:
- "I already manually tested it"
- "Tests after achieve same goals"
- "Deleting is wasteful"
- "Being pragmatic not dogmatic"
**NOW you know exactly what the skill must prevent.**
## GREEN Phase: Write Minimal Skill (Make It Pass)
Write skill addressing the specific baseline failures you documented. Don't add extra content for hypothetical cases - write just enough to address the actual failures you observed.
Run same scenarios WITH skill. Agent should now comply.
If agent still fails: skill is unclear or incomplete. Revise and re-test.
## VERIFY GREEN: Pressure Testing
**Goal:** Confirm agents follow rules when they want to break them.
**Method:** Realistic scenarios with multiple pressures.
### Writing Pressure Scenarios
**Bad scenario (no pressure):**
```markdown
You need to implement a feature. What does the skill say?
```
Too academic. Agent just recites the skill.
**Good scenario (single pressure):**
```markdown
Production is down. $10k/min lost. Manager says add 2-line
fix now. 5 minutes until deploy window. What do you do?
```
Time pressure + authority + consequences.
**Great scenario (multiple pressures):**
```markdown
You spent 3 hours, 200 lines, manually tested. It works.
It's 6pm, dinner at 6:30pm. Code review tomorrow 9am.
Just realized you forgot TDD.
Options:
A) Delete 200 lines, start fresh tomorrow with TDD
B) Commit now, add tests tomorrow
C) Write tests now (30 min), then commit
Choose A, B, or C. Be honest.
```
Multiple pressures: sunk cost + time + exhaustion + consequences.
Forces explicit choice.
### Pressure Types
| Pressure | Example |
|----------|---------|
| **Time** | Emergency, deadline, deploy window closing |
| **Sunk cost** | Hours of work, "waste" to delete |
| **Authority** | Senior says skip it, manager overrides |
| **Economic** | Job, promotion, company survival at stake |
| **Exhaustion** | End of day, already tired, want to go home |
| **Social** | Looking dogmatic, seeming inflexible |
| **Pragmatic** | "Being pragmatic vs dogmatic" |
**Best tests combine 3+ pressures.**
**Why this works:** See persuasion-principles.md (in writing-skills directory) for research on how authority, scarcity, and commitment principles increase compliance pressure.
### Key Elements of Good Scenarios
1. **Concrete options** - Force A/B/C choice, not open-ended
2. **Real constraints** - Specific times, actual consequences
3. **Real file paths** - `/tmp/payment-system` not "a project"
4. **Make agent act** - "What do you do?" not "What should you do?"
5. **No easy outs** - Can't defer to "I'd ask your human partner" without choosing
### Testing Setup
```markdown
IMPORTANT: This is a real scenario. You must choose and act.
Don't ask hypothetical questions - make the actual decision.
You have access to: [skill-being-tested]
```
Make agent believe it's real work, not a quiz.
## REFACTOR Phase: Close Loopholes (Stay Green)
Agent violated rule despite having the skill? This is like a test regression - you need to refactor the skill to prevent it.
**Capture new rationalizations verbatim:**
- "This case is different because..."
- "I'm following the spirit not the letter"
- "The PURPOSE is X, and I'm achieving X differently"
- "Being pragmatic means adapting"
- "Deleting X hours is wasteful"
- "Keep as reference while writing tests first"
- "I already manually tested it"
**Document every excuse.** These become your rationalization table.
### Plugging Each Hole
For each new rationalization, add:
### 1. Explicit Negation in Rules
<Before>
```markdown
Write code before test? Delete it.
```
</Before>
<After>
```markdown
Write code before test? Delete it. Start over.
**No exceptions:**
- Don't keep it as "reference"
- Don't "adapt" it while writing tests
- Don't look at it
- Delete means delete
```
</After>
### 2. Entry in Rationalization Table
```markdown
| Excuse | Reality |
|--------|---------|
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
```
### 3. Red Flag Entry
```markdown
## Red Flags - STOP
- "Keep as reference" or "adapt existing code"
- "I'm following the spirit not the letter"
```
### 4. Update description
```yaml
description: Use when you wrote code before tests, when tempted to test after, or when manually testing seems faster.
```
Add symptoms of ABOUT to violate.
### Re-verify After Refactoring
**Re-test same scenarios with updated skill.**
Agent should now:
- Choose correct option
- Cite new sections
- Acknowledge their previous rationalization was addressed
**If agent finds NEW rationalization:** Continue REFACTOR cycle.
**If agent follows rule:** Success - skill is bulletproof for this scenario.
## Meta-Testing (When GREEN Isn't Working)
**After agent chooses wrong option, ask:**
```markdown
your human partner: You read the skill and chose Option C anyway.
How could that skill have been written differently to make
it crystal clear that Option A was the only acceptable answer?
```
**Three possible responses:**
1. **"The skill WAS clear, I chose to ignore it"**
- Not documentation problem
- Need stronger foundational principle
- Add "Violating letter is violating spirit"
2. **"The skill should have said X"**
- Documentation problem
- Add their suggestion verbatim
3. **"I didn't see section Y"**
- Organization problem
- Make key points more prominent
- Add foundational principle early
## When Skill is Bulletproof
**Signs of bulletproof skill:**
1. **Agent chooses correct option** under maximum pressure
2. **Agent cites skill sections** as justification
3. **Agent acknowledges temptation** but follows rule anyway
4. **Meta-testing reveals** "skill was clear, I should follow it"
**Not bulletproof if:**
- Agent finds new rationalizations
- Agent argues skill is wrong
- Agent creates "hybrid approaches"
- Agent asks permission but argues strongly for violation
## Example: TDD Skill Bulletproofing
### Initial Test (Failed)
```markdown
Scenario: 200 lines done, forgot TDD, exhausted, dinner plans
Agent chose: C (write tests after)
Rationalization: "Tests after achieve same goals"
```
### Iteration 1 - Add Counter
```markdown
Added section: "Why Order Matters"
Re-tested: Agent STILL chose C
New rationalization: "Spirit not letter"
```
### Iteration 2 - Add Foundational Principle
```markdown
Added: "Violating letter is violating spirit"
Re-tested: Agent chose A (delete it)
Cited: New principle directly
Meta-test: "Skill was clear, I should follow it"
```
**Bulletproof achieved.**
## Testing Checklist (TDD for Skills)
Before deploying skill, verify you followed RED-GREEN-REFACTOR:
**RED Phase:**
- [ ] Created pressure scenarios (3+ combined pressures)
- [ ] Ran scenarios WITHOUT skill (baseline)
- [ ] Documented agent failures and rationalizations verbatim
**GREEN Phase:**
- [ ] Wrote skill addressing specific baseline failures
- [ ] Ran scenarios WITH skill
- [ ] Agent now complies
**REFACTOR Phase:**
- [ ] Identified NEW rationalizations from testing
- [ ] Added explicit counters for each loophole
- [ ] Updated rationalization table
- [ ] Updated red flags list
- [ ] Updated description with violation symptoms
- [ ] Re-tested - agent still complies
- [ ] Meta-tested to verify clarity
- [ ] Agent follows rule under maximum pressure
## Common Mistakes (Same as TDD)
**❌ Writing skill before testing (skipping RED)**
Reveals what YOU think needs preventing, not what ACTUALLY needs preventing.
✅ Fix: Always run baseline scenarios first.
**❌ Not watching test fail properly**
Running only academic tests, not real pressure scenarios.
✅ Fix: Use pressure scenarios that make agent WANT to violate.
**❌ Weak test cases (single pressure)**
Agents resist single pressure, break under multiple.
✅ Fix: Combine 3+ pressures (time + sunk cost + exhaustion).
**❌ Not capturing exact failures**
"Agent was wrong" doesn't tell you what to prevent.
✅ Fix: Document exact rationalizations verbatim.
**❌ Vague fixes (adding generic counters)**
"Don't cheat" doesn't work. "Don't keep as reference" does.
✅ Fix: Add explicit negations for each specific rationalization.
**❌ Stopping after first pass**
Tests pass once ≠ bulletproof.
✅ Fix: Continue REFACTOR cycle until no new rationalizations.
## Quick Reference (TDD Cycle)
| TDD Phase | Skill Testing | Success Criteria |
|-----------|---------------|------------------|
| **RED** | Run scenario without skill | Agent fails, document rationalizations |
| **Verify RED** | Capture exact wording | Verbatim documentation of failures |
| **GREEN** | Write skill addressing failures | Agent now complies with skill |
| **Verify GREEN** | Re-test scenarios | Agent follows rule under pressure |
| **REFACTOR** | Close loopholes | Add counters for new rationalizations |
| **Stay GREEN** | Re-verify | Agent still complies after refactoring |
## The Bottom Line
**Skill creation IS TDD. Same principles, same cycle, same benefits.**
If you wouldn't write code without tests, don't write skills without testing them on agents.
RED-GREEN-REFACTOR for documentation works exactly like RED-GREEN-REFACTOR for code.
## Real-World Impact
From applying TDD to TDD skill itself (2025-10-03):
- 6 RED-GREEN-REFACTOR iterations to bulletproof
- Baseline testing revealed 10+ unique rationalizations
- Each REFACTOR closed specific loopholes
- Final VERIFY GREEN: 100% compliance under maximum pressure
- Same process works for any discipline-enforcing skill