Repository: vercel-labs/agent-skills Branch: main Commit: 9aec8ee6aaf7 Files: 146 Total size: 542.0 KB Directory structure: gitextract_uut7bmea/ ├── .github/ │ └── workflows/ │ └── react-best-practices-ci.yml ├── .gitignore ├── AGENTS.md ├── README.md ├── packages/ │ └── react-best-practices-build/ │ ├── .gitignore │ ├── package.json │ ├── src/ │ │ ├── build.ts │ │ ├── config.ts │ │ ├── extract-tests.ts │ │ ├── migrate.ts │ │ ├── parser.ts │ │ ├── types.ts │ │ └── validate.ts │ ├── test-cases.json │ └── tsconfig.json └── skills/ ├── composition-patterns/ │ ├── AGENTS.md │ ├── README.md │ ├── SKILL.md │ ├── metadata.json │ └── rules/ │ ├── _sections.md │ ├── _template.md │ ├── architecture-avoid-boolean-props.md │ ├── architecture-compound-components.md │ ├── patterns-children-over-render-props.md │ ├── patterns-explicit-variants.md │ ├── react19-no-forwardref.md │ ├── state-context-interface.md │ ├── state-decouple-implementation.md │ └── state-lift-state.md ├── deploy-to-vercel/ │ ├── SKILL.md │ └── resources/ │ ├── deploy-codex.sh │ └── deploy.sh ├── react-best-practices/ │ ├── AGENTS.md │ ├── README.md │ ├── SKILL.md │ ├── metadata.json │ └── rules/ │ ├── _sections.md │ ├── _template.md │ ├── advanced-event-handler-refs.md │ ├── advanced-init-once.md │ ├── advanced-use-latest.md │ ├── async-api-routes.md │ ├── async-defer-await.md │ ├── async-dependencies.md │ ├── async-parallel.md │ ├── async-suspense-boundaries.md │ ├── bundle-barrel-imports.md │ ├── bundle-conditional.md │ ├── bundle-defer-third-party.md │ ├── bundle-dynamic-imports.md │ ├── bundle-preload.md │ ├── client-event-listeners.md │ ├── client-localstorage-schema.md │ ├── client-passive-event-listeners.md │ ├── client-swr-dedup.md │ ├── js-batch-dom-css.md │ ├── js-cache-function-results.md │ ├── js-cache-property-access.md │ ├── js-cache-storage.md │ ├── js-combine-iterations.md │ ├── js-early-exit.md │ ├── js-flatmap-filter.md │ ├── js-hoist-regexp.md │ ├── js-index-maps.md │ ├── js-length-check-first.md │ ├── js-min-max-loop.md │ ├── js-set-map-lookups.md │ ├── js-tosorted-immutable.md │ ├── rendering-activity.md │ ├── rendering-animate-svg-wrapper.md │ ├── rendering-conditional-render.md │ ├── rendering-content-visibility.md │ ├── rendering-hoist-jsx.md │ ├── rendering-hydration-no-flicker.md │ ├── rendering-hydration-suppress-warning.md │ ├── rendering-resource-hints.md │ ├── rendering-script-defer-async.md │ ├── rendering-svg-precision.md │ ├── rendering-usetransition-loading.md │ ├── rerender-defer-reads.md │ ├── rerender-dependencies.md │ ├── rerender-derived-state-no-effect.md │ ├── rerender-derived-state.md │ ├── rerender-functional-setstate.md │ ├── rerender-lazy-state-init.md │ ├── rerender-memo-with-default-value.md │ ├── rerender-memo.md │ ├── rerender-move-effect-to-event.md │ ├── rerender-no-inline-components.md │ ├── rerender-simple-expression-in-memo.md │ ├── rerender-split-combined-hooks.md │ ├── rerender-transitions.md │ ├── rerender-use-deferred-value.md │ ├── rerender-use-ref-transient-values.md │ ├── server-after-nonblocking.md │ ├── server-auth-actions.md │ ├── server-cache-lru.md │ ├── server-cache-react.md │ ├── server-dedup-props.md │ ├── server-hoist-static-io.md │ ├── server-parallel-fetching.md │ └── server-serialization.md ├── react-native-skills/ │ ├── AGENTS.md │ ├── README.md │ ├── SKILL.md │ ├── metadata.json │ └── rules/ │ ├── _sections.md │ ├── _template.md │ ├── animation-derived-value.md │ ├── animation-gesture-detector-press.md │ ├── animation-gpu-properties.md │ ├── design-system-compound-components.md │ ├── fonts-config-plugin.md │ ├── imports-design-system-folder.md │ ├── js-hoist-intl.md │ ├── list-performance-callbacks.md │ ├── list-performance-function-references.md │ ├── list-performance-images.md │ ├── list-performance-inline-objects.md │ ├── list-performance-item-expensive.md │ ├── list-performance-item-memo.md │ ├── list-performance-item-types.md │ ├── list-performance-virtualize.md │ ├── monorepo-native-deps-in-app.md │ ├── monorepo-single-dependency-versions.md │ ├── navigation-native-navigators.md │ ├── react-compiler-destructure-functions.md │ ├── react-compiler-reanimated-shared-values.md │ ├── react-state-dispatcher.md │ ├── react-state-fallback.md │ ├── react-state-minimize.md │ ├── rendering-no-falsy-and.md │ ├── rendering-text-in-text-component.md │ ├── scroll-position-no-state.md │ ├── state-ground-truth.md │ ├── ui-expo-image.md │ ├── ui-image-gallery.md │ ├── ui-measure-views.md │ ├── ui-menus.md │ ├── ui-native-modals.md │ ├── ui-pressable.md │ ├── ui-safe-area-scroll.md │ ├── ui-scrollview-content-inset.md │ └── ui-styling.md ├── vercel-cli-with-tokens/ │ └── SKILL.md └── web-design-guidelines/ └── SKILL.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/react-best-practices-ci.yml ================================================ name: React Best Practices CI on: push: branches: [main] paths: - 'skills/react-best-practices/**' - 'packages/react-best-practices-build/**' pull_request: branches: [main] paths: - 'skills/react-best-practices/**' - 'packages/react-best-practices-build/**' jobs: validate: runs-on: ubuntu-latest defaults: run: working-directory: packages/react-best-practices-build steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v2 with: version: 10.24.0 - uses: actions/setup-node@v4 with: node-version: '20' cache: 'pnpm' cache-dependency-path: packages/react-best-practices-build/pnpm-lock.yaml - run: pnpm install - run: pnpm validate - run: pnpm build ================================================ FILE: .gitignore ================================================ .DS_Store .vercel .env*.local ================================================ FILE: AGENTS.md ================================================ # AGENTS.md This file provides guidance to AI coding agents (Claude Code, Cursor, Copilot, etc.) when working with code in this repository. ## Repository Overview A collection of skills for Claude.ai and Claude Code for working with Vercel deployments. Skills are packaged instructions and scripts that extend Claude's capabilities. ## Creating a New Skill ### Directory Structure ``` skills/ {skill-name}/ # kebab-case directory name SKILL.md # Required: skill definition scripts/ # Required: executable scripts {script-name}.sh # Bash scripts (preferred) {skill-name}.zip # Required: packaged for distribution ``` ### Naming Conventions - **Skill directory**: `kebab-case` (e.g., `vercel-deploy`, `log-monitor`) - **SKILL.md**: Always uppercase, always this exact filename - **Scripts**: `kebab-case.sh` (e.g., `deploy.sh`, `fetch-logs.sh`) - **Zip file**: Must match directory name exactly: `{skill-name}.zip` ### SKILL.md Format ```markdown --- name: {skill-name} description: {One sentence describing when to use this skill. Include trigger phrases like "Deploy my app", "Check logs", etc.} --- # {Skill Title} {Brief description of what the skill does.} ## How It Works {Numbered list explaining the skill's workflow} ## Usage ```bash bash /mnt/skills/user/{skill-name}/scripts/{script}.sh [args] ``` **Arguments:** - `arg1` - Description (defaults to X) **Examples:** {Show 2-3 common usage patterns} ## Output {Show example output users will see} ## Present Results to User {Template for how Claude should format results when presenting to users} ## Troubleshooting {Common issues and solutions, especially network/permissions errors} ``` ### Best Practices for Context Efficiency Skills are loaded on-demand — only the skill name and description are loaded at startup. The full `SKILL.md` loads into context only when the agent decides the skill is relevant. To minimize context usage: - **Keep SKILL.md under 500 lines** — put detailed reference material in separate files - **Write specific descriptions** — helps the agent know exactly when to activate the skill - **Use progressive disclosure** — reference supporting files that get read only when needed - **Prefer scripts over inline code** — script execution doesn't consume context (only output does) - **File references work one level deep** — link directly from SKILL.md to supporting files ### Script Requirements - Use `#!/bin/bash` shebang - Use `set -e` for fail-fast behavior - Write status messages to stderr: `echo "Message" >&2` - Write machine-readable output (JSON) to stdout - Include a cleanup trap for temp files - Reference the script path as `/mnt/skills/user/{skill-name}/scripts/{script}.sh` ### Creating the Zip Package After creating or updating a skill: ```bash cd skills zip -r {skill-name}.zip {skill-name}/ ``` ### End-User Installation Document these two installation methods for users: **Claude Code:** ```bash cp -r skills/{skill-name} ~/.claude/skills/ ``` **claude.ai:** Add the skill to project knowledge or paste SKILL.md contents into the conversation. If the skill requires network access, instruct users to add required domains at `claude.ai/settings/capabilities`. ================================================ FILE: README.md ================================================ # Agent Skills A collection of skills for AI coding agents. Skills are packaged instructions and scripts that extend agent capabilities. Skills follow the [Agent Skills](https://agentskills.io/) format. ## Available Skills ### react-best-practices React and Next.js performance optimization guidelines from Vercel Engineering. Contains 40+ rules across 8 categories, prioritized by impact. **Use when:** - Writing new React components or Next.js pages - Implementing data fetching (client or server-side) - Reviewing code for performance issues - Optimizing bundle size or load times **Categories covered:** - Eliminating waterfalls (Critical) - Bundle size optimization (Critical) - Server-side performance (High) - Client-side data fetching (Medium-High) - Re-render optimization (Medium) - Rendering performance (Medium) - JavaScript micro-optimizations (Low-Medium) ### web-design-guidelines Review UI code for compliance with web interface best practices. Audits your code for 100+ rules covering accessibility, performance, and UX. **Use when:** - "Review my UI" - "Check accessibility" - "Audit design" - "Review UX" - "Check my site against best practices" **Categories covered:** - Accessibility (aria-labels, semantic HTML, keyboard handlers) - Focus States (visible focus, focus-visible patterns) - Forms (autocomplete, validation, error handling) - Animation (prefers-reduced-motion, compositor-friendly transforms) - Typography (curly quotes, ellipsis, tabular-nums) - Images (dimensions, lazy loading, alt text) - Performance (virtualization, layout thrashing, preconnect) - Navigation & State (URL reflects state, deep-linking) - Dark Mode & Theming (color-scheme, theme-color meta) - Touch & Interaction (touch-action, tap-highlight) - Locale & i18n (Intl.DateTimeFormat, Intl.NumberFormat) ### react-native-guidelines React Native best practices optimized for AI agents. Contains 16 rules across 7 sections covering performance, architecture, and platform-specific patterns. **Use when:** - Building React Native or Expo apps - Optimizing mobile performance - Implementing animations or gestures - Working with native modules or platform APIs **Categories covered:** - Performance (Critical) - FlashList, memoization, heavy computation - Layout (High) - flex patterns, safe areas, keyboard handling - Animation (High) - Reanimated, gesture handling - Images (Medium) - expo-image, caching, lazy loading - State Management (Medium) - Zustand patterns, React Compiler - Architecture (Medium) - monorepo structure, imports - Platform (Medium) - iOS/Android specific patterns ### composition-patterns React composition patterns that scale. Helps avoid boolean prop proliferation through compound components, state lifting, and internal composition. **Use when:** - Refactoring components with many boolean props - Building reusable component libraries - Designing flexible APIs - Reviewing component architecture **Patterns covered:** - Extracting compound components - Lifting state to reduce props - Composing internals for flexibility - Avoiding prop drilling ### vercel-deploy-claimable Deploy applications and websites to Vercel instantly. Designed for use with claude.ai and Claude Desktop to enable deployments directly from conversations. Deployments are "claimable" - users can transfer ownership to their own Vercel account. **Use when:** - "Deploy my app" - "Deploy this to production" - "Push this live" - "Deploy and give me the link" **Features:** - Auto-detects 40+ frameworks from `package.json` - Returns preview URL (live site) and claim URL (transfer ownership) - Handles static HTML projects automatically - Excludes `node_modules` and `.git` from uploads **How it works:** 1. Packages your project into a tarball 2. Detects framework (Next.js, Vite, Astro, etc.) 3. Uploads to deployment service 4. Returns preview URL and claim URL **Output:** ``` Deployment successful! Preview URL: https://skill-deploy-abc123.vercel.app Claim URL: https://vercel.com/claim-deployment?code=... ``` ## Installation ```bash npx skills add vercel-labs/agent-skills ``` ## Usage Skills are automatically available once installed. The agent will use them when relevant tasks are detected. **Examples:** ``` Deploy my app ``` ``` Review this React component for performance issues ``` ``` Help me optimize this Next.js page ``` ## Skill Structure Each skill contains: - `SKILL.md` - Instructions for the agent - `scripts/` - Helper scripts for automation (optional) - `references/` - Supporting documentation (optional) ## License MIT ================================================ FILE: packages/react-best-practices-build/.gitignore ================================================ node_modules/ ================================================ FILE: packages/react-best-practices-build/package.json ================================================ { "name": "react-best-practices-build", "version": "1.0.0", "description": "Build tooling for React Best Practices and React Native Guidelines skills", "type": "module", "scripts": { "build": "pnpm build-agents && pnpm extract-tests", "build-agents": "tsx src/build.ts", "build-all": "tsx src/build.ts --all", "build-react": "tsx src/build.ts --skill=react-best-practices", "build-rn": "tsx src/build.ts --skill=react-native-skills", "build-composition": "tsx src/build.ts --skill=composition-patterns", "validate": "tsx src/validate.ts", "extract-tests": "tsx src/extract-tests.ts", "migrate": "tsx src/migrate.ts", "dev": "pnpm build && pnpm validate" }, "keywords": [ "react", "performance", "guidelines", "llm", "agents" ], "license": "MIT", "devDependencies": { "@types/node": "^20.0.0", "tsx": "^4.7.0", "typescript": "^5.3.0" } } ================================================ FILE: packages/react-best-practices-build/src/build.ts ================================================ #!/usr/bin/env node /** * Build script to compile individual rule files into AGENTS.md */ import { readdir, readFile, writeFile } from 'fs/promises' import { join } from 'path' import { Rule, Section, GuidelinesDocument, ImpactLevel } from './types.js' import { parseRuleFile, RuleFile } from './parser.js' import { SKILLS, SkillConfig, DEFAULT_SKILL } from './config.js' // Parse command line arguments const args = process.argv.slice(2) const upgradeVersion = args.includes('--upgrade-version') const skillArg = args.find((arg) => arg.startsWith('--skill=')) const skillName = skillArg ? skillArg.split('=')[1] : null const buildAll = args.includes('--all') /** * Increment a semver-style version string (e.g., "0.1.0" -> "0.1.1", "1.0" -> "1.1") */ function incrementVersion(version: string): string { const parts = version.split('.').map(Number) // Increment the last part parts[parts.length - 1]++ return parts.join('.') } /** * Generate markdown from rules */ function generateMarkdown( sections: Section[], metadata: { version: string organization: string date: string abstract: string references?: string[] }, skillConfig: SkillConfig ): string { let md = `# ${skillConfig.title}\n\n` md += `**Version ${metadata.version}** \n` md += `${metadata.organization} \n` md += `${metadata.date}\n\n` md += `> **Note:** \n` md += `> This document is mainly for agents and LLMs to follow when maintaining, \n` md += `> generating, or refactoring ${skillConfig.description}. Humans \n` md += `> may also find it useful, but guidance here is optimized for automation \n` md += `> and consistency by AI-assisted workflows.\n\n` md += `---\n\n` md += `## Abstract\n\n` md += `${metadata.abstract}\n\n` md += `---\n\n` md += `## Table of Contents\n\n` // Generate TOC sections.forEach((section) => { md += `${section.number}. [${section.title}](#${ section.number }-${section.title.toLowerCase().replace(/\s+/g, '-')}) — **${ section.impact }**\n` section.rules.forEach((rule) => { // GitHub generates anchors from the full heading text: "1.1 Title" -> "#11-title" const anchor = `${rule.id} ${rule.title}` .toLowerCase() .replace(/\s+/g, '-') .replace(/[^\w-]/g, '') // Remove special characters except hyphens md += ` - ${rule.id} [${rule.title}](#${anchor})\n` }) }) md += `\n---\n\n` // Generate sections sections.forEach((section) => { md += `## ${section.number}. ${section.title}\n\n` md += `**Impact: ${section.impact}${ section.impactDescription ? ` (${section.impactDescription})` : '' }**\n\n` if (section.introduction) { md += `${section.introduction}\n\n` } section.rules.forEach((rule) => { md += `### ${rule.id} ${rule.title}\n\n` md += `**Impact: ${rule.impact}${ rule.impactDescription ? ` (${rule.impactDescription})` : '' }**\n\n` md += `${rule.explanation}\n\n` rule.examples.forEach((example) => { if (example.description) { md += `**${example.label}: ${example.description}**\n\n` } else { md += `**${example.label}:**\n\n` } // Only generate code block if there's actual code if (example.code && example.code.trim()) { md += `\`\`\`${example.language || 'typescript'}\n` md += `${example.code}\n` md += `\`\`\`\n\n` } if (example.additionalText) { md += `${example.additionalText}\n\n` } }) if (rule.references && rule.references.length > 0) { md += `Reference: ${rule.references .map((ref) => `[${ref}](${ref})`) .join(', ')}\n\n` } }) md += `---\n\n` }) // Add references section if (metadata.references && metadata.references.length > 0) { md += `## References\n\n` metadata.references.forEach((ref, i) => { md += `${i + 1}. [${ref}](${ref})\n` }) } return md } /** * Build a single skill */ async function buildSkill(skillConfig: SkillConfig) { console.log(`\nBuilding ${skillConfig.name}...`) console.log(` Rules directory: ${skillConfig.rulesDir}`) console.log(` Output file: ${skillConfig.outputFile}`) // Read all rule files (exclude files starting with _ and README.md) const files = await readdir(skillConfig.rulesDir) const ruleFiles = files .filter((f) => f.endsWith('.md') && !f.startsWith('_') && f !== 'README.md') .sort() // Sort filenames for consistent ordering across systems const ruleData: RuleFile[] = [] for (const file of ruleFiles) { const filePath = join(skillConfig.rulesDir, file) try { const parsed = await parseRuleFile(filePath, skillConfig.sectionMap) ruleData.push(parsed) } catch (error) { console.error(` Error parsing ${file}:`, error) } } // Group rules by section const sectionsMap = new Map() ruleData.forEach(({ section, rule }) => { if (!sectionsMap.has(section)) { sectionsMap.set(section, { number: section, title: `Section ${section}`, // Will be overridden by section metadata impact: rule.impact, rules: [], }) } sectionsMap.get(section)!.rules.push(rule) }) // Sort rules within each section by title (using en-US locale for consistency across environments) sectionsMap.forEach((section) => { section.rules.sort((a, b) => a.title.localeCompare(b.title, 'en-US', { sensitivity: 'base' }) ) // Assign IDs based on sorted order section.rules.forEach((rule, index) => { rule.id = `${section.number}.${index + 1}` rule.subsection = index + 1 }) }) // Convert to array and sort const sections = Array.from(sectionsMap.values()).sort( (a, b) => a.number - b.number ) // Read section metadata from consolidated _sections.md file const sectionsFile = join(skillConfig.rulesDir, '_sections.md') try { const sectionsContent = await readFile(sectionsFile, 'utf-8') // Parse sections using regex to match each section block const sectionBlocks = sectionsContent .split(/(?=^## \d+\. )/m) .filter(Boolean) for (const block of sectionBlocks) { // Extract section number and title, removing section ID in parentheses const headerMatch = block.match(/^## (\d+)\.\s+(.+?)(?:\s+\([^)]+\))?$/m) if (!headerMatch) continue const sectionNumber = parseInt(headerMatch[1]) const sectionTitle = headerMatch[2].trim() // Strip (id) for output // Extract impact (format: **Impact:** CRITICAL) const impactMatch = block.match(/\*\*Impact:\*\*\s+(\w+(?:-\w+)?)/i) const impactLevel = impactMatch ? (impactMatch[1].toUpperCase().replace(/-/g, '-') as ImpactLevel) : 'MEDIUM' // Extract description (format: **Description:** text) const descMatch = block.match(/\*\*Description:\*\*\s+(.+?)(?=\n\n##|$)/s) const description = descMatch ? descMatch[1].trim() : '' // Update section if it exists const section = sections.find((s) => s.number === sectionNumber) if (section) { section.title = sectionTitle section.impact = impactLevel section.introduction = description } } } catch (error) { console.warn(' Warning: Could not read _sections.md, using defaults') } // Read metadata let metadata try { const metadataContent = await readFile(skillConfig.metadataFile, 'utf-8') metadata = JSON.parse(metadataContent) } catch { metadata = { version: '1.0.0', organization: 'Engineering', date: new Date().toLocaleDateString('en-US', { month: 'long', year: 'numeric', }), abstract: `Performance optimization guide for ${skillConfig.description}, ordered by impact.`, } } // Upgrade version if flag is passed if (upgradeVersion) { const oldVersion = metadata.version metadata.version = incrementVersion(oldVersion) console.log(` Upgrading version: ${oldVersion} -> ${metadata.version}`) // Write updated metadata.json await writeFile( skillConfig.metadataFile, JSON.stringify(metadata, null, 2) + '\n', 'utf-8' ) console.log(` ✓ Updated metadata.json`) // Update SKILL.md frontmatter if it exists const skillFile = join(skillConfig.skillDir, 'SKILL.md') try { const skillContent = await readFile(skillFile, 'utf-8') const updatedSkillContent = skillContent.replace( /^(---[\s\S]*?version:\s*)"[^"]*"([\s\S]*?---)$/m, `$1"${metadata.version}"$2` ) await writeFile(skillFile, updatedSkillContent, 'utf-8') console.log(` ✓ Updated SKILL.md`) } catch { // SKILL.md doesn't exist, skip } } // Generate markdown const markdown = generateMarkdown(sections, metadata, skillConfig) // Write output await writeFile(skillConfig.outputFile, markdown, 'utf-8') console.log( ` ✓ Built AGENTS.md with ${sections.length} sections and ${ruleData.length} rules` ) } /** * Main build function */ async function build() { try { console.log('Building AGENTS.md from rules...') if (buildAll) { // Build all skills for (const skill of Object.values(SKILLS)) { await buildSkill(skill) } } else if (skillName) { // Build specific skill const skill = SKILLS[skillName] if (!skill) { console.error(`Unknown skill: ${skillName}`) console.error(`Available skills: ${Object.keys(SKILLS).join(', ')}`) process.exit(1) } await buildSkill(skill) } else { // Build default skill (backwards compatibility) await buildSkill(SKILLS[DEFAULT_SKILL]) } console.log('\n✓ Build complete') } catch (error) { console.error('Build failed:', error) process.exit(1) } } build() ================================================ FILE: packages/react-best-practices-build/src/config.ts ================================================ /** * Configuration for the build tooling */ import { join, dirname } from 'path' import { fileURLToPath } from 'url' const __dirname = dirname(fileURLToPath(import.meta.url)) // Base paths export const SKILLS_DIR = join(__dirname, '../../..', 'skills') export const BUILD_DIR = join(__dirname, '..') // Skill configurations export interface SkillConfig { name: string title: string description: string skillDir: string rulesDir: string metadataFile: string outputFile: string sectionMap: Record } export const SKILLS: Record = { 'react-best-practices': { name: 'react-best-practices', title: 'React Best Practices', description: 'React and Next.js codebases', skillDir: join(SKILLS_DIR, 'react-best-practices'), rulesDir: join(SKILLS_DIR, 'react-best-practices/rules'), metadataFile: join(SKILLS_DIR, 'react-best-practices/metadata.json'), outputFile: join(SKILLS_DIR, 'react-best-practices/AGENTS.md'), sectionMap: { async: 1, bundle: 2, server: 3, client: 4, rerender: 5, rendering: 6, js: 7, advanced: 8, }, }, 'react-native-skills': { name: 'react-native-skills', title: 'React Native Skills', description: 'React Native codebases', skillDir: join(SKILLS_DIR, 'react-native-skills'), rulesDir: join(SKILLS_DIR, 'react-native-skills/rules'), metadataFile: join(SKILLS_DIR, 'react-native-skills/metadata.json'), outputFile: join(SKILLS_DIR, 'react-native-skills/AGENTS.md'), sectionMap: { rendering: 1, 'list-performance': 2, animation: 3, scroll: 4, navigation: 5, 'react-state': 6, state: 7, 'react-compiler': 8, ui: 9, 'design-system': 10, monorepo: 11, imports: 12, js: 13, fonts: 14, }, }, 'composition-patterns': { name: 'composition-patterns', title: 'React Composition Patterns', description: 'React codebases using composition', skillDir: join(SKILLS_DIR, 'composition-patterns'), rulesDir: join(SKILLS_DIR, 'composition-patterns/rules'), metadataFile: join(SKILLS_DIR, 'composition-patterns/metadata.json'), outputFile: join(SKILLS_DIR, 'composition-patterns/AGENTS.md'), sectionMap: { architecture: 1, state: 2, patterns: 3, react19: 4, }, }, } // Default skill (for backwards compatibility) export const DEFAULT_SKILL = 'react-best-practices' // Legacy exports for backwards compatibility export const SKILL_DIR = SKILLS[DEFAULT_SKILL].skillDir export const RULES_DIR = SKILLS[DEFAULT_SKILL].rulesDir export const METADATA_FILE = SKILLS[DEFAULT_SKILL].metadataFile export const OUTPUT_FILE = SKILLS[DEFAULT_SKILL].outputFile // Test cases are build artifacts, not part of the skill export const TEST_CASES_FILE = join(BUILD_DIR, 'test-cases.json') ================================================ FILE: packages/react-best-practices-build/src/extract-tests.ts ================================================ #!/usr/bin/env node /** * Extract test cases from rules for LLM evaluation */ import { readdir, writeFile } from 'fs/promises' import { join } from 'path' import { Rule, TestCase } from './types.js' import { parseRuleFile } from './parser.js' import { RULES_DIR, TEST_CASES_FILE } from './config.js' /** * Extract test cases from a rule */ function extractTestCases(rule: Rule): TestCase[] { const testCases: TestCase[] = [] rule.examples.forEach((example, index) => { const isBad = example.label.toLowerCase().includes('incorrect') || example.label.toLowerCase().includes('wrong') || example.label.toLowerCase().includes('bad') const isGood = example.label.toLowerCase().includes('correct') || example.label.toLowerCase().includes('good') if (isBad || isGood) { testCases.push({ ruleId: rule.id, ruleTitle: rule.title, type: isBad ? 'bad' : 'good', code: example.code, language: example.language || 'typescript', description: example.description || `${example.label} example for ${rule.title}` }) } }) return testCases } /** * Main extraction function */ async function extractTests() { try { console.log('Extracting test cases from rules...') console.log(`Rules directory: ${RULES_DIR}`) console.log(`Output file: ${TEST_CASES_FILE}`) const files = await readdir(RULES_DIR) const ruleFiles = files.filter(f => f.endsWith('.md') && !f.startsWith('_') && f !== 'README.md') const allTestCases: TestCase[] = [] for (const file of ruleFiles) { const filePath = join(RULES_DIR, file) try { const { rule } = await parseRuleFile(filePath) const testCases = extractTestCases(rule) allTestCases.push(...testCases) } catch (error) { console.error(`Error processing ${file}:`, error) } } // Write test cases as JSON await writeFile(TEST_CASES_FILE, JSON.stringify(allTestCases, null, 2), 'utf-8') console.log(`✓ Extracted ${allTestCases.length} test cases to ${TEST_CASES_FILE}`) console.log(` - Bad examples: ${allTestCases.filter(tc => tc.type === 'bad').length}`) console.log(` - Good examples: ${allTestCases.filter(tc => tc.type === 'good').length}`) } catch (error) { console.error('Extraction failed:', error) process.exit(1) } } extractTests() ================================================ FILE: packages/react-best-practices-build/src/migrate.ts ================================================ #!/usr/bin/env node /** * Migration script to split RPG.md into individual rule files * This is a one-time script to help migrate existing content */ import { readFile, writeFile, mkdir } from 'fs/promises' import { join } from 'path' import { existsSync } from 'fs' import { SKILL_DIR, RULES_DIR } from './config.js' const RPG_FILE = join(SKILL_DIR, 'RPG.md') /** * Extract section number and title from heading */ function parseSectionHeading(line: string): { number: number; title: string } | null { const match = line.match(/^##\s+(\d+)\.\s+(.+)$/) if (match) { return { number: parseInt(match[1]), title: match[2].trim() } } return null } /** * Extract rule number and title from heading */ function parseRuleHeading(line: string): { section: number; subsection: number; title: string } | null { const match = line.match(/^###\s+(\d+)\.(\d+)\s+(.+)$/) if (match) { return { section: parseInt(match[1]), subsection: parseInt(match[2]), title: match[3].trim() } } return null } /** * Extract impact from line */ function extractImpact(line: string): { impact: string; description?: string } | null { const match = line.match(/\*\*Impact:\s*(\w+(?:-\w+)?)\s*(?:\(([^)]+)\))?/i) if (match) { return { impact: match[1].toUpperCase().replace(/-/g, '-'), description: match[2] } } return null } async function migrate() { try { console.log('Migrating RPG.md to individual rule files...') if (!existsSync(RPG_FILE)) { console.error(`RPG.md not found at ${RPG_FILE}`) process.exit(1) } // Ensure rules directory exists if (!existsSync(RULES_DIR)) { await mkdir(RULES_DIR, { recursive: true }) } const content = await readFile(RPG_FILE, 'utf-8') const lines = content.split('\n') let currentSection: { number: number; title: string; impact?: string; introduction?: string } | null = null let currentRule: { section: number; subsection: number; title: string; content: string[] } | null = null let inCodeBlock = false for (let i = 0; i < lines.length; i++) { const line = lines[i] // Check for section heading const sectionInfo = parseSectionHeading(line) if (sectionInfo) { // Save previous section if exists if (currentSection) { const sectionFile = join(RULES_DIR, `section-${currentSection.number}.md`) let sectionContent = `# ${currentSection.number}. ${currentSection.title}\n\n` if (currentSection.impact) { sectionContent += `**Impact: ${currentSection.impact}**\n\n` } if (currentSection.introduction) { sectionContent += `## Introduction\n\n${currentSection.introduction}\n` } await writeFile(sectionFile, sectionContent, 'utf-8') } currentSection = sectionInfo currentRule = null // Look for impact on next few lines for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) { const impactInfo = extractImpact(lines[j]) if (impactInfo) { currentSection.impact = impactInfo.impact break } } // Collect introduction text until first rule let introduction: string[] = [] for (let j = i + 1; j < lines.length; j++) { if (parseRuleHeading(lines[j])) { break } if (!lines[j].match(/^###/)) { introduction.push(lines[j]) } } currentSection.introduction = introduction.join('\n').trim() continue } // Check for rule heading const ruleInfo = parseRuleHeading(line) if (ruleInfo) { // Save previous rule if exists if (currentRule && currentSection) { const ruleFile = join(RULES_DIR, `section-${currentRule.section}-rule-${currentRule.subsection}.md`) const ruleContent = currentRule.content.join('\n') await writeFile(ruleFile, ruleContent, 'utf-8') console.log(`Created ${ruleFile}`) } currentRule = { ...ruleInfo, content: [line] } continue } // Accumulate content for current rule if (currentRule) { currentRule.content.push(line) } } // Save last rule if (currentRule && currentSection) { const ruleFile = join(RULES_DIR, `section-${currentRule.section}-rule-${currentRule.subsection}.md`) const ruleContent = currentRule.content.join('\n') await writeFile(ruleFile, ruleContent, 'utf-8') console.log(`Created ${ruleFile}`) } // Save last section if (currentSection) { const sectionFile = join(RULES_DIR, `section-${currentSection.number}.md`) let sectionContent = `# ${currentSection.number}. ${currentSection.title}\n\n` if (currentSection.impact) { sectionContent += `**Impact: ${currentSection.impact}**\n\n` } if (currentSection.introduction) { sectionContent += `## Introduction\n\n${currentSection.introduction}\n` } await writeFile(sectionFile, sectionContent, 'utf-8') console.log(`Created ${sectionFile}`) } console.log('\n✓ Migration complete!') console.log('Note: You may need to manually add frontmatter to rule files.') } catch (error) { console.error('Migration failed:', error) process.exit(1) } } migrate() ================================================ FILE: packages/react-best-practices-build/src/parser.ts ================================================ /** * Parser for rule markdown files */ import { readFile } from 'fs/promises' import { basename } from 'path' import { Rule, ImpactLevel } from './types.js' export interface RuleFile { section: number subsection?: number rule: Rule } /** * Parse a rule markdown file into a Rule object */ export async function parseRuleFile( filePath: string, sectionMap?: Record ): Promise { const rawContent = await readFile(filePath, 'utf-8') // Normalize Windows CRLF line endings to LF for consistent parsing const content = rawContent.replace(/\r\n/g, '\n') const lines = content.split('\n') // Extract frontmatter if present let frontmatter: Record = {} let contentStart = 0 if (content.startsWith('---')) { const frontmatterEnd = content.indexOf('---', 3) if (frontmatterEnd !== -1) { const frontmatterText = content.slice(3, frontmatterEnd).trim() frontmatterText.split('\n').forEach((line) => { const [key, ...valueParts] = line.split(':') if (key && valueParts.length) { const value = valueParts.join(':').trim() frontmatter[key.trim()] = value.replace(/^["']|["']$/g, '') } }) contentStart = frontmatterEnd + 3 } } // Parse the rule content const ruleContent = content.slice(contentStart).trim() const ruleLines = ruleContent.split('\n') // Extract title (first # or ## heading) let title = '' let titleLine = 0 for (let i = 0; i < ruleLines.length; i++) { if (ruleLines[i].startsWith('##')) { title = ruleLines[i].replace(/^##+\s*/, '').trim() titleLine = i break } } // Extract impact let impact: Rule['impact'] = 'MEDIUM' let impactDescription = '' let explanation = '' let examples: Rule['examples'] = [] let references: string[] = [] // Parse content after title let currentExample: { label: string description?: string code: string language?: string additionalText?: string } | null = null let inCodeBlock = false let codeBlockLanguage = 'typescript' let codeBlockContent: string[] = [] let afterCodeBlock = false let additionalText: string[] = [] let hasCodeBlockForCurrentExample = false for (let i = titleLine + 1; i < ruleLines.length; i++) { const line = ruleLines[i] // Impact line if (line.includes('**Impact:')) { const match = line.match( /\*\*Impact:\s*(\w+(?:-\w+)?)\s*(?:\(([^)]+)\))?/i ) if (match) { impact = match[1].toUpperCase().replace(/-/g, '-') as ImpactLevel impactDescription = match[2] || '' } continue } // Code block start if (line.startsWith('```')) { if (inCodeBlock) { // End of code block if (currentExample) { currentExample.code = codeBlockContent.join('\n') currentExample.language = codeBlockLanguage } codeBlockContent = [] inCodeBlock = false afterCodeBlock = true } else { // Start of code block inCodeBlock = true hasCodeBlockForCurrentExample = true codeBlockLanguage = line.slice(3).trim() || 'typescript' codeBlockContent = [] afterCodeBlock = false } continue } if (inCodeBlock) { codeBlockContent.push(line) continue } // Example label (Incorrect, Correct, Example, Usage, Implementation, etc.) // Match pattern: **Label:** or **Label (description):** at end of line // This distinguishes example labels from inline bold text like "**Trade-off:** some text" const labelMatch = line.match(/^\*\*([^:]+?):\*?\*?$/) if (labelMatch) { // Save previous example if it exists if (currentExample) { if (additionalText.length > 0) { currentExample.additionalText = additionalText.join('\n\n') additionalText = [] } examples.push(currentExample) } afterCodeBlock = false hasCodeBlockForCurrentExample = false const fullLabel = labelMatch[1].trim() // Try to extract description from parentheses if present (handles simple cases) // For nested parentheses like "Incorrect (O(n) per lookup)", we keep the full label const descMatch = fullLabel.match( /^([A-Za-z]+(?:\s+[A-Za-z]+)*)\s*\(([^()]+)\)$/ ) currentExample = { label: descMatch ? descMatch[1].trim() : fullLabel, description: descMatch ? descMatch[2].trim() : undefined, code: '', language: codeBlockLanguage, } continue } // Reference links if (line.startsWith('Reference:') || line.startsWith('References:')) { // Save current example before processing references if (currentExample) { if (additionalText.length > 0) { currentExample.additionalText = additionalText.join('\n\n') additionalText = [] } examples.push(currentExample) currentExample = null } const refMatch = line.match(/\[([^\]]+)\]\(([^)]+)\)/g) if (refMatch) { references.push( ...refMatch.map((ref) => { const m = ref.match(/\[([^\]]+)\]\(([^)]+)\)/) return m ? m[2] : ref }) ) } continue } // Regular text (explanation or additional context after examples) if (line.trim() && !line.startsWith('#')) { if (!currentExample && !inCodeBlock) { // Main explanation before any examples explanation += (explanation ? '\n\n' : '') + line } else if ( currentExample && (afterCodeBlock || !hasCodeBlockForCurrentExample) ) { // Text after a code block, or text in a section without a code block // (e.g., "When NOT to use this pattern:" with bullet points instead of code) additionalText.push(line) } } } // Handle last example if still open if (currentExample) { if (additionalText.length > 0) { currentExample.additionalText = additionalText.join('\n\n') } examples.push(currentExample) } // Infer section from filename patterns // Pattern: area-description.md where area determines section const filename = basename(filePath) // Default section map (for backwards compatibility) const defaultSectionMap: Record = { async: 1, bundle: 2, server: 3, client: 4, rerender: 5, rendering: 6, js: 7, advanced: 8, } const effectiveSectionMap = sectionMap || defaultSectionMap // Extract area from filename - try longest prefix match first // This handles prefixes like "list-performance" vs "list" const filenameParts = filename.replace('.md', '').split('-') let section = 0 // Try progressively shorter prefixes to find the best match for (let len = filenameParts.length; len > 0; len--) { const prefix = filenameParts.slice(0, len).join('-') if (effectiveSectionMap[prefix] !== undefined) { section = effectiveSectionMap[prefix] break } } // Fall back to frontmatter section if specified section = frontmatter.section || section || 0 const rule: Rule = { id: '', // Will be assigned by build script based on sorted order title: frontmatter.title || title, section: section, subsection: undefined, impact: frontmatter.impact || impact, impactDescription: frontmatter.impactDescription || impactDescription, explanation: frontmatter.explanation || explanation.trim(), examples, references: frontmatter.references ? frontmatter.references.split(',').map((r: string) => r.trim()) : references, tags: frontmatter.tags ? frontmatter.tags.split(',').map((t: string) => t.trim()) : undefined, } return { section, subsection: 0, rule, } } ================================================ FILE: packages/react-best-practices-build/src/types.ts ================================================ /** * Type definitions for React Performance Guidelines rules */ export type ImpactLevel = 'CRITICAL' | 'HIGH' | 'MEDIUM-HIGH' | 'MEDIUM' | 'LOW-MEDIUM' | 'LOW' export interface CodeExample { label: string // e.g., "Incorrect", "Correct", "Example" description?: string // Optional description before code code: string language?: string // Default: 'typescript' or 'tsx' additionalText?: string // Optional text after code block (explanations, reasons) } export interface Rule { id: string // e.g., "1.1", "2.3" title: string section: number // Main section number (1-8) subsection?: number // Subsection number within section impact: ImpactLevel impactDescription?: string // e.g., "2-10× improvement" explanation: string examples: CodeExample[] references?: string[] // URLs or citations tags?: string[] // For categorization/search } export interface Section { number: number title: string impact: ImpactLevel impactDescription?: string introduction?: string rules: Rule[] } export interface GuidelinesDocument { version: string organization: string date: string abstract: string sections: Section[] references?: string[] } export interface TestCase { ruleId: string ruleTitle: string type: 'bad' | 'good' code: string language: string description?: string } ================================================ FILE: packages/react-best-practices-build/src/validate.ts ================================================ #!/usr/bin/env node /** * Validate rule files follow the correct structure */ import { readdir } from 'fs/promises' import { join } from 'path' import { Rule } from './types.js' import { parseRuleFile } from './parser.js' import { RULES_DIR } from './config.js' interface ValidationError { file: string ruleId?: string message: string } /** * Validate a rule */ function validateRule(rule: Rule, file: string): ValidationError[] { const errors: ValidationError[] = [] // Note: rule.id is auto-generated during build, not required in source files if (!rule.title || rule.title.trim().length === 0) { errors.push({ file, ruleId: rule.id, message: 'Missing or empty title' }) } if (!rule.explanation || rule.explanation.trim().length === 0) { errors.push({ file, ruleId: rule.id, message: 'Missing or empty explanation' }) } if (!rule.examples || rule.examples.length === 0) { errors.push({ file, ruleId: rule.id, message: 'Missing examples (need at least one bad and one good example)' }) } else { // Filter out informational examples (notes, trade-offs, etc.) that don't have code const codeExamples = rule.examples.filter(e => e.code && e.code.trim().length > 0) const hasBad = codeExamples.some(e => e.label.toLowerCase().includes('incorrect') || e.label.toLowerCase().includes('wrong') || e.label.toLowerCase().includes('bad') ) const hasGood = codeExamples.some(e => e.label.toLowerCase().includes('correct') || e.label.toLowerCase().includes('good') || e.label.toLowerCase().includes('usage') || e.label.toLowerCase().includes('implementation') || e.label.toLowerCase().includes('example') ) if (codeExamples.length === 0) { errors.push({ file, ruleId: rule.id, message: 'Missing code examples' }) } else if (!hasBad && !hasGood) { errors.push({ file, ruleId: rule.id, message: 'Missing bad/incorrect or good/correct examples' }) } } const validImpacts: Rule['impact'][] = ['CRITICAL', 'HIGH', 'MEDIUM-HIGH', 'MEDIUM', 'LOW-MEDIUM', 'LOW'] if (!validImpacts.includes(rule.impact)) { errors.push({ file, ruleId: rule.id, message: `Invalid impact level: ${rule.impact}. Must be one of: ${validImpacts.join(', ')}` }) } return errors } /** * Main validation function */ async function validate() { try { console.log('Validating rule files...') console.log(`Rules directory: ${RULES_DIR}`) const files = await readdir(RULES_DIR) const ruleFiles = files.filter(f => f.endsWith('.md') && !f.startsWith('_')) const allErrors: ValidationError[] = [] for (const file of ruleFiles) { const filePath = join(RULES_DIR, file) try { const { rule } = await parseRuleFile(filePath) const errors = validateRule(rule, file) allErrors.push(...errors) } catch (error) { allErrors.push({ file, message: `Failed to parse: ${error instanceof Error ? error.message : String(error)}` }) } } if (allErrors.length > 0) { console.error('\n✗ Validation failed:\n') allErrors.forEach(error => { console.error(` ${error.file}${error.ruleId ? ` (${error.ruleId})` : ''}: ${error.message}`) }) process.exit(1) } else { console.log(`✓ All ${ruleFiles.length} rule files are valid`) } } catch (error) { console.error('Validation failed:', error) process.exit(1) } } validate() ================================================ FILE: packages/react-best-practices-build/test-cases.json ================================================ [ { "ruleId": "", "ruleTitle": "Store Event Handlers in Refs", "type": "bad", "code": "function useWindowEvent(event: string, handler: (e) => void) {\n useEffect(() => {\n window.addEventListener(event, handler)\n return () => window.removeEventListener(event, handler)\n }, [event, handler])\n}", "language": "tsx", "description": "re-subscribes on every render" }, { "ruleId": "", "ruleTitle": "Store Event Handlers in Refs", "type": "good", "code": "import { useEffectEvent } from 'react'\n\nfunction useWindowEvent(event: string, handler: (e) => void) {\n const onEvent = useEffectEvent(handler)\n\n useEffect(() => {\n window.addEventListener(event, onEvent)\n return () => window.removeEventListener(event, onEvent)\n }, [event])\n}", "language": "tsx", "description": "stable subscription" }, { "ruleId": "", "ruleTitle": "Initialize App Once, Not Per Mount", "type": "bad", "code": "function Comp() {\n useEffect(() => {\n loadFromStorage()\n checkAuthToken()\n }, [])\n\n // ...\n}", "language": "tsx", "description": "runs twice in dev, re-runs on remount" }, { "ruleId": "", "ruleTitle": "Initialize App Once, Not Per Mount", "type": "good", "code": "let didInit = false\n\nfunction Comp() {\n useEffect(() => {\n if (didInit) return\n didInit = true\n loadFromStorage()\n checkAuthToken()\n }, [])\n\n // ...\n}", "language": "tsx", "description": "once per app load" }, { "ruleId": "", "ruleTitle": "useEffectEvent for Stable Callback Refs", "type": "bad", "code": "function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n const [query, setQuery] = useState('')\n\n useEffect(() => {\n const timeout = setTimeout(() => onSearch(query), 300)\n return () => clearTimeout(timeout)\n }, [query, onSearch])\n}", "language": "tsx", "description": "effect re-runs on every callback change" }, { "ruleId": "", "ruleTitle": "useEffectEvent for Stable Callback Refs", "type": "good", "code": "import { useEffectEvent } from 'react';\n\nfunction SearchInput({ onSearch }: { onSearch: (q: string) => void }) {\n const [query, setQuery] = useState('')\n const onSearchEvent = useEffectEvent(onSearch)\n\n useEffect(() => {\n const timeout = setTimeout(() => onSearchEvent(query), 300)\n return () => clearTimeout(timeout)\n }, [query])\n}", "language": "tsx", "description": "using React's useEffectEvent" }, { "ruleId": "", "ruleTitle": "Prevent Waterfall Chains in API Routes", "type": "bad", "code": "export async function GET(request: Request) {\n const session = await auth()\n const config = await fetchConfig()\n const data = await fetchData(session.user.id)\n return Response.json({ data, config })\n}", "language": "typescript", "description": "config waits for auth, data waits for both" }, { "ruleId": "", "ruleTitle": "Prevent Waterfall Chains in API Routes", "type": "good", "code": "export async function GET(request: Request) {\n const sessionPromise = auth()\n const configPromise = fetchConfig()\n const session = await sessionPromise\n const [config, data] = await Promise.all([\n configPromise,\n fetchData(session.user.id)\n ])\n return Response.json({ data, config })\n}", "language": "typescript", "description": "auth and config start immediately" }, { "ruleId": "", "ruleTitle": "Defer Await Until Needed", "type": "bad", "code": "async function handleRequest(userId: string, skipProcessing: boolean) {\n const userData = await fetchUserData(userId)\n \n if (skipProcessing) {\n // Returns immediately but still waited for userData\n return { skipped: true }\n }\n \n // Only this branch uses userData\n return processUserData(userData)\n}", "language": "typescript", "description": "blocks both branches" }, { "ruleId": "", "ruleTitle": "Defer Await Until Needed", "type": "good", "code": "async function handleRequest(userId: string, skipProcessing: boolean) {\n if (skipProcessing) {\n // Returns immediately without waiting\n return { skipped: true }\n }\n \n // Fetch only when needed\n const userData = await fetchUserData(userId)\n return processUserData(userData)\n}", "language": "typescript", "description": "only blocks when needed" }, { "ruleId": "", "ruleTitle": "Dependency-Based Parallelization", "type": "bad", "code": "const [user, config] = await Promise.all([\n fetchUser(),\n fetchConfig()\n])\nconst profile = await fetchProfile(user.id)", "language": "typescript", "description": "profile waits for config unnecessarily" }, { "ruleId": "", "ruleTitle": "Dependency-Based Parallelization", "type": "good", "code": "import { all } from 'better-all'\n\nconst { user, config, profile } = await all({\n async user() { return fetchUser() },\n async config() { return fetchConfig() },\n async profile() {\n return fetchProfile((await this.$.user).id)\n }\n})", "language": "typescript", "description": "config and profile run in parallel" }, { "ruleId": "", "ruleTitle": "Promise.all() for Independent Operations", "type": "bad", "code": "const user = await fetchUser()\nconst posts = await fetchPosts()\nconst comments = await fetchComments()", "language": "typescript", "description": "sequential execution, 3 round trips" }, { "ruleId": "", "ruleTitle": "Promise.all() for Independent Operations", "type": "good", "code": "const [user, posts, comments] = await Promise.all([\n fetchUser(),\n fetchPosts(),\n fetchComments()\n])", "language": "typescript", "description": "parallel execution, 1 round trip" }, { "ruleId": "", "ruleTitle": "Strategic Suspense Boundaries", "type": "bad", "code": "async function Page() {\n const data = await fetchData() // Blocks entire page\n \n return (\n
\n
Sidebar
\n
Header
\n
\n \n
\n
Footer
\n
\n )\n}", "language": "tsx", "description": "wrapper blocked by data fetching" }, { "ruleId": "", "ruleTitle": "Strategic Suspense Boundaries", "type": "good", "code": "function Page() {\n return (\n
\n
Sidebar
\n
Header
\n
\n }>\n \n \n
\n
Footer
\n
\n )\n}\n\nasync function DataDisplay() {\n const data = await fetchData() // Only blocks this component\n return
{data.content}
\n}", "language": "tsx", "description": "wrapper shows immediately, data streams in" }, { "ruleId": "", "ruleTitle": "Avoid Barrel File Imports", "type": "bad", "code": "import { Check, X, Menu } from 'lucide-react'\n// Loads 1,583 modules, takes ~2.8s extra in dev\n// Runtime cost: 200-800ms on every cold start\n\nimport { Button, TextField } from '@mui/material'\n// Loads 2,225 modules, takes ~4.2s extra in dev", "language": "tsx", "description": "imports entire library" }, { "ruleId": "", "ruleTitle": "Avoid Barrel File Imports", "type": "good", "code": "import Check from 'lucide-react/dist/esm/icons/check'\nimport X from 'lucide-react/dist/esm/icons/x'\nimport Menu from 'lucide-react/dist/esm/icons/menu'\n// Loads only 3 modules (~2KB vs ~1MB)\n\nimport Button from '@mui/material/Button'\nimport TextField from '@mui/material/TextField'\n// Loads only what you use", "language": "tsx", "description": "imports only what you need" }, { "ruleId": "", "ruleTitle": "Defer Non-Critical Third-Party Libraries", "type": "bad", "code": "import { Analytics } from '@vercel/analytics/react'\n\nexport default function RootLayout({ children }) {\n return (\n \n \n {children}\n \n \n \n )\n}", "language": "tsx", "description": "blocks initial bundle" }, { "ruleId": "", "ruleTitle": "Defer Non-Critical Third-Party Libraries", "type": "good", "code": "import dynamic from 'next/dynamic'\n\nconst Analytics = dynamic(\n () => import('@vercel/analytics/react').then(m => m.Analytics),\n { ssr: false }\n)\n\nexport default function RootLayout({ children }) {\n return (\n \n \n {children}\n \n \n \n )\n}", "language": "tsx", "description": "loads after hydration" }, { "ruleId": "", "ruleTitle": "Dynamic Imports for Heavy Components", "type": "bad", "code": "import { MonacoEditor } from './monaco-editor'\n\nfunction CodePanel({ code }: { code: string }) {\n return \n}", "language": "tsx", "description": "Monaco bundles with main chunk ~300KB" }, { "ruleId": "", "ruleTitle": "Dynamic Imports for Heavy Components", "type": "good", "code": "import dynamic from 'next/dynamic'\n\nconst MonacoEditor = dynamic(\n () => import('./monaco-editor').then(m => m.MonacoEditor),\n { ssr: false }\n)\n\nfunction CodePanel({ code }: { code: string }) {\n return \n}", "language": "tsx", "description": "Monaco loads on demand" }, { "ruleId": "", "ruleTitle": "Deduplicate Global Event Listeners", "type": "bad", "code": "function useKeyboardShortcut(key: string, callback: () => void) {\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n if (e.metaKey && e.key === key) {\n callback()\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [key, callback])\n}", "language": "tsx", "description": "N instances = N listeners" }, { "ruleId": "", "ruleTitle": "Deduplicate Global Event Listeners", "type": "good", "code": "import useSWRSubscription from 'swr/subscription'\n\n// Module-level Map to track callbacks per key\nconst keyCallbacks = new Map void>>()\n\nfunction useKeyboardShortcut(key: string, callback: () => void) {\n // Register this callback in the Map\n useEffect(() => {\n if (!keyCallbacks.has(key)) {\n keyCallbacks.set(key, new Set())\n }\n keyCallbacks.get(key)!.add(callback)\n\n return () => {\n const set = keyCallbacks.get(key)\n if (set) {\n set.delete(callback)\n if (set.size === 0) {\n keyCallbacks.delete(key)\n }\n }\n }\n }, [key, callback])\n\n useSWRSubscription('global-keydown', () => {\n const handler = (e: KeyboardEvent) => {\n if (e.metaKey && keyCallbacks.has(e.key)) {\n keyCallbacks.get(e.key)!.forEach(cb => cb())\n }\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n })\n}\n\nfunction Profile() {\n // Multiple shortcuts will share the same listener\n useKeyboardShortcut('p', () => { /* ... */ }) \n useKeyboardShortcut('k', () => { /* ... */ })\n // ...\n}", "language": "tsx", "description": "N instances = 1 listener" }, { "ruleId": "", "ruleTitle": "Version and Minimize localStorage Data", "type": "bad", "code": "// No version, stores everything, no error handling\nlocalStorage.setItem('userConfig', JSON.stringify(fullUserObject))\nconst data = localStorage.getItem('userConfig')", "language": "typescript", "description": "Incorrect example for Version and Minimize localStorage Data" }, { "ruleId": "", "ruleTitle": "Version and Minimize localStorage Data", "type": "good", "code": "const VERSION = 'v2'\n\nfunction saveConfig(config: { theme: string; language: string }) {\n try {\n localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))\n } catch {\n // Throws in incognito/private browsing, quota exceeded, or disabled\n }\n}\n\nfunction loadConfig() {\n try {\n const data = localStorage.getItem(`userConfig:${VERSION}`)\n return data ? JSON.parse(data) : null\n } catch {\n return null\n }\n}\n\n// Migration from v1 to v2\nfunction migrate() {\n try {\n const v1 = localStorage.getItem('userConfig:v1')\n if (v1) {\n const old = JSON.parse(v1)\n saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang })\n localStorage.removeItem('userConfig:v1')\n }\n } catch {}\n}", "language": "typescript", "description": "Correct example for Version and Minimize localStorage Data" }, { "ruleId": "", "ruleTitle": "Use Passive Event Listeners for Scrolling Performance", "type": "bad", "code": "useEffect(() => {\n const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n \n document.addEventListener('touchstart', handleTouch)\n document.addEventListener('wheel', handleWheel)\n \n return () => {\n document.removeEventListener('touchstart', handleTouch)\n document.removeEventListener('wheel', handleWheel)\n }\n}, [])", "language": "typescript", "description": "Incorrect example for Use Passive Event Listeners for Scrolling Performance" }, { "ruleId": "", "ruleTitle": "Use Passive Event Listeners for Scrolling Performance", "type": "good", "code": "useEffect(() => {\n const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX)\n const handleWheel = (e: WheelEvent) => console.log(e.deltaY)\n \n document.addEventListener('touchstart', handleTouch, { passive: true })\n document.addEventListener('wheel', handleWheel, { passive: true })\n \n return () => {\n document.removeEventListener('touchstart', handleTouch)\n document.removeEventListener('wheel', handleWheel)\n }\n}, [])", "language": "typescript", "description": "Correct example for Use Passive Event Listeners for Scrolling Performance" }, { "ruleId": "", "ruleTitle": "Use SWR for Automatic Deduplication", "type": "bad", "code": "function UserList() {\n const [users, setUsers] = useState([])\n useEffect(() => {\n fetch('/api/users')\n .then(r => r.json())\n .then(setUsers)\n }, [])\n}", "language": "tsx", "description": "no deduplication, each instance fetches" }, { "ruleId": "", "ruleTitle": "Use SWR for Automatic Deduplication", "type": "good", "code": "import useSWR from 'swr'\n\nfunction UserList() {\n const { data: users } = useSWR('/api/users', fetcher)\n}", "language": "tsx", "description": "multiple instances share one request" }, { "ruleId": "", "ruleTitle": "Avoid Layout Thrashing", "type": "bad", "code": "function layoutThrashing(element: HTMLElement) {\n element.style.width = '100px'\n const width = element.offsetWidth // Forces reflow\n element.style.height = '200px'\n const height = element.offsetHeight // Forces another reflow\n}", "language": "typescript", "description": "interleaved reads and writes force reflows" }, { "ruleId": "", "ruleTitle": "Avoid Layout Thrashing", "type": "good", "code": "function updateElementStyles(element: HTMLElement) {\n // Batch all writes together\n element.style.width = '100px'\n element.style.height = '200px'\n element.style.backgroundColor = 'blue'\n element.style.border = '1px solid black'\n \n // Read after all writes are done (single reflow)\n const { width, height } = element.getBoundingClientRect()\n}", "language": "typescript", "description": "batch writes, then read once" }, { "ruleId": "", "ruleTitle": "Avoid Layout Thrashing", "type": "good", "code": "function updateElementStyles(element: HTMLElement) {\n element.classList.add('highlighted-box')\n \n const { width, height } = element.getBoundingClientRect()\n}", "language": "typescript", "description": "batch reads, then writes" }, { "ruleId": "", "ruleTitle": "Cache Repeated Function Calls", "type": "bad", "code": "function ProjectList({ projects }: { projects: Project[] }) {\n return (\n
\n {projects.map(project => {\n // slugify() called 100+ times for same project names\n const slug = slugify(project.name)\n \n return \n })}\n
\n )\n}", "language": "typescript", "description": "redundant computation" }, { "ruleId": "", "ruleTitle": "Cache Repeated Function Calls", "type": "good", "code": "// Module-level cache\nconst slugifyCache = new Map()\n\nfunction cachedSlugify(text: string): string {\n if (slugifyCache.has(text)) {\n return slugifyCache.get(text)!\n }\n const result = slugify(text)\n slugifyCache.set(text, result)\n return result\n}\n\nfunction ProjectList({ projects }: { projects: Project[] }) {\n return (\n
\n {projects.map(project => {\n // Computed only once per unique project name\n const slug = cachedSlugify(project.name)\n \n return \n })}\n
\n )\n}", "language": "typescript", "description": "cached results" }, { "ruleId": "", "ruleTitle": "Cache Property Access in Loops", "type": "bad", "code": "for (let i = 0; i < arr.length; i++) {\n process(obj.config.settings.value)\n}", "language": "typescript", "description": "3 lookups × N iterations" }, { "ruleId": "", "ruleTitle": "Cache Property Access in Loops", "type": "good", "code": "const value = obj.config.settings.value\nconst len = arr.length\nfor (let i = 0; i < len; i++) {\n process(value)\n}", "language": "typescript", "description": "1 lookup total" }, { "ruleId": "", "ruleTitle": "Cache Storage API Calls", "type": "bad", "code": "function getTheme() {\n return localStorage.getItem('theme') ?? 'light'\n}\n// Called 10 times = 10 storage reads", "language": "typescript", "description": "reads storage on every call" }, { "ruleId": "", "ruleTitle": "Cache Storage API Calls", "type": "good", "code": "const storageCache = new Map()\n\nfunction getLocalStorage(key: string) {\n if (!storageCache.has(key)) {\n storageCache.set(key, localStorage.getItem(key))\n }\n return storageCache.get(key)\n}\n\nfunction setLocalStorage(key: string, value: string) {\n localStorage.setItem(key, value)\n storageCache.set(key, value) // keep cache in sync\n}", "language": "typescript", "description": "Map cache" }, { "ruleId": "", "ruleTitle": "Combine Multiple Array Iterations", "type": "bad", "code": "const admins = users.filter(u => u.isAdmin)\nconst testers = users.filter(u => u.isTester)\nconst inactive = users.filter(u => !u.isActive)", "language": "typescript", "description": "3 iterations" }, { "ruleId": "", "ruleTitle": "Combine Multiple Array Iterations", "type": "good", "code": "const admins: User[] = []\nconst testers: User[] = []\nconst inactive: User[] = []\n\nfor (const user of users) {\n if (user.isAdmin) admins.push(user)\n if (user.isTester) testers.push(user)\n if (!user.isActive) inactive.push(user)\n}", "language": "typescript", "description": "1 iteration" }, { "ruleId": "", "ruleTitle": "Early Return from Functions", "type": "bad", "code": "function validateUsers(users: User[]) {\n let hasError = false\n let errorMessage = ''\n \n for (const user of users) {\n if (!user.email) {\n hasError = true\n errorMessage = 'Email required'\n }\n if (!user.name) {\n hasError = true\n errorMessage = 'Name required'\n }\n // Continues checking all users even after error found\n }\n \n return hasError ? { valid: false, error: errorMessage } : { valid: true }\n}", "language": "typescript", "description": "processes all items even after finding answer" }, { "ruleId": "", "ruleTitle": "Early Return from Functions", "type": "good", "code": "function validateUsers(users: User[]) {\n for (const user of users) {\n if (!user.email) {\n return { valid: false, error: 'Email required' }\n }\n if (!user.name) {\n return { valid: false, error: 'Name required' }\n }\n }\n\n return { valid: true }\n}", "language": "typescript", "description": "returns immediately on first error" }, { "ruleId": "", "ruleTitle": "Use flatMap to Map and Filter in One Pass", "type": "bad", "code": "const userNames = users\n .map(user => user.isActive ? user.name : null)\n .filter(Boolean)", "language": "typescript", "description": "2 iterations, intermediate array" }, { "ruleId": "", "ruleTitle": "Use flatMap to Map and Filter in One Pass", "type": "good", "code": "const userNames = users.flatMap(user =>\n user.isActive ? [user.name] : []\n)", "language": "typescript", "description": "1 iteration, no intermediate array" }, { "ruleId": "", "ruleTitle": "Hoist RegExp Creation", "type": "bad", "code": "function Highlighter({ text, query }: Props) {\n const regex = new RegExp(`(${query})`, 'gi')\n const parts = text.split(regex)\n return <>{parts.map((part, i) => ...)}\n}", "language": "tsx", "description": "new RegExp every render" }, { "ruleId": "", "ruleTitle": "Hoist RegExp Creation", "type": "good", "code": "const EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nfunction Highlighter({ text, query }: Props) {\n const regex = useMemo(\n () => new RegExp(`(${escapeRegex(query)})`, 'gi'),\n [query]\n )\n const parts = text.split(regex)\n return <>{parts.map((part, i) => ...)}\n}", "language": "tsx", "description": "memoize or hoist" }, { "ruleId": "", "ruleTitle": "Build Index Maps for Repeated Lookups", "type": "bad", "code": "function processOrders(orders: Order[], users: User[]) {\n return orders.map(order => ({\n ...order,\n user: users.find(u => u.id === order.userId)\n }))\n}", "language": "typescript", "description": "Incorrect (O(n) per lookup) example for Build Index Maps for Repeated Lookups" }, { "ruleId": "", "ruleTitle": "Build Index Maps for Repeated Lookups", "type": "good", "code": "function processOrders(orders: Order[], users: User[]) {\n const userById = new Map(users.map(u => [u.id, u]))\n\n return orders.map(order => ({\n ...order,\n user: userById.get(order.userId)\n }))\n}", "language": "typescript", "description": "Correct (O(1) per lookup) example for Build Index Maps for Repeated Lookups" }, { "ruleId": "", "ruleTitle": "Early Length Check for Array Comparisons", "type": "bad", "code": "function hasChanges(current: string[], original: string[]) {\n // Always sorts and joins, even when lengths differ\n return current.sort().join() !== original.sort().join()\n}", "language": "typescript", "description": "always runs expensive comparison" }, { "ruleId": "", "ruleTitle": "Early Length Check for Array Comparisons", "type": "good", "code": "function hasChanges(current: string[], original: string[]) {\n // Early return if lengths differ\n if (current.length !== original.length) {\n return true\n }\n // Only sort when lengths match\n const currentSorted = current.toSorted()\n const originalSorted = original.toSorted()\n for (let i = 0; i < currentSorted.length; i++) {\n if (currentSorted[i] !== originalSorted[i]) {\n return true\n }\n }\n return false\n}", "language": "typescript", "description": "Correct (O(1) length check first) example for Early Length Check for Array Comparisons" }, { "ruleId": "", "ruleTitle": "Use Loop for Min/Max Instead of Sort", "type": "bad", "code": "interface Project {\n id: string\n name: string\n updatedAt: number\n}\n\nfunction getLatestProject(projects: Project[]) {\n const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)\n return sorted[0]\n}", "language": "typescript", "description": "Incorrect (O(n log n) - sort to find latest) example for Use Loop for Min/Max Instead of Sort" }, { "ruleId": "", "ruleTitle": "Use Loop for Min/Max Instead of Sort", "type": "bad", "code": "function getOldestAndNewest(projects: Project[]) {\n const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)\n return { oldest: sorted[0], newest: sorted[sorted.length - 1] }\n}", "language": "typescript", "description": "Incorrect (O(n log n) - sort for oldest and newest) example for Use Loop for Min/Max Instead of Sort" }, { "ruleId": "", "ruleTitle": "Use Loop for Min/Max Instead of Sort", "type": "good", "code": "function getLatestProject(projects: Project[]) {\n if (projects.length === 0) return null\n \n let latest = projects[0]\n \n for (let i = 1; i < projects.length; i++) {\n if (projects[i].updatedAt > latest.updatedAt) {\n latest = projects[i]\n }\n }\n \n return latest\n}\n\nfunction getOldestAndNewest(projects: Project[]) {\n if (projects.length === 0) return { oldest: null, newest: null }\n \n let oldest = projects[0]\n let newest = projects[0]\n \n for (let i = 1; i < projects.length; i++) {\n if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]\n if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]\n }\n \n return { oldest, newest }\n}", "language": "typescript", "description": "Correct (O(n) - single loop) example for Use Loop for Min/Max Instead of Sort" }, { "ruleId": "", "ruleTitle": "Use Set/Map for O(1) Lookups", "type": "bad", "code": "const allowedIds = ['a', 'b', 'c', ...]\nitems.filter(item => allowedIds.includes(item.id))", "language": "typescript", "description": "Incorrect (O(n) per check) example for Use Set/Map for O(1) Lookups" }, { "ruleId": "", "ruleTitle": "Use Set/Map for O(1) Lookups", "type": "good", "code": "const allowedIds = new Set(['a', 'b', 'c', ...])\nitems.filter(item => allowedIds.has(item.id))", "language": "typescript", "description": "Correct (O(1) per check) example for Use Set/Map for O(1) Lookups" }, { "ruleId": "", "ruleTitle": "Use toSorted() Instead of sort() for Immutability", "type": "bad", "code": "function UserList({ users }: { users: User[] }) {\n // Mutates the users prop array!\n const sorted = useMemo(\n () => users.sort((a, b) => a.name.localeCompare(b.name)),\n [users]\n )\n return
{sorted.map(renderUser)}
\n}", "language": "typescript", "description": "mutates original array" }, { "ruleId": "", "ruleTitle": "Use toSorted() Instead of sort() for Immutability", "type": "good", "code": "function UserList({ users }: { users: User[] }) {\n // Creates new sorted array, original unchanged\n const sorted = useMemo(\n () => users.toSorted((a, b) => a.name.localeCompare(b.name)),\n [users]\n )\n return
{sorted.map(renderUser)}
\n}", "language": "typescript", "description": "creates new array" }, { "ruleId": "", "ruleTitle": "Animate SVG Wrapper Instead of SVG Element", "type": "bad", "code": "function LoadingSpinner() {\n return (\n \n \n \n )\n}", "language": "tsx", "description": "animating SVG directly - no hardware acceleration" }, { "ruleId": "", "ruleTitle": "Animate SVG Wrapper Instead of SVG Element", "type": "good", "code": "function LoadingSpinner() {\n return (\n
\n \n \n \n
\n )\n}", "language": "tsx", "description": "animating wrapper div - hardware accelerated" }, { "ruleId": "", "ruleTitle": "Use Explicit Conditional Rendering", "type": "bad", "code": "function Badge({ count }: { count: number }) {\n return (\n
\n {count && {count}}\n
\n )\n}\n\n// When count = 0, renders:
0
\n// When count = 5, renders:
5
", "language": "tsx", "description": "renders \"0\" when count is 0" }, { "ruleId": "", "ruleTitle": "Use Explicit Conditional Rendering", "type": "good", "code": "function Badge({ count }: { count: number }) {\n return (\n
\n {count > 0 ? {count} : null}\n
\n )\n}\n\n// When count = 0, renders:
\n// When count = 5, renders:
5
", "language": "tsx", "description": "renders nothing when count is 0" }, { "ruleId": "", "ruleTitle": "Hoist Static JSX Elements", "type": "bad", "code": "function LoadingSkeleton() {\n return
\n}\n\nfunction Container() {\n return (\n
\n {loading && }\n
\n )\n}", "language": "tsx", "description": "recreates element every render" }, { "ruleId": "", "ruleTitle": "Hoist Static JSX Elements", "type": "good", "code": "const loadingSkeleton = (\n
\n)\n\nfunction Container() {\n return (\n
\n {loading && loadingSkeleton}\n
\n )\n}", "language": "tsx", "description": "reuses same element" }, { "ruleId": "", "ruleTitle": "Prevent Hydration Mismatch Without Flickering", "type": "bad", "code": "function ThemeWrapper({ children }: { children: ReactNode }) {\n // localStorage is not available on server - throws error\n const theme = localStorage.getItem('theme') || 'light'\n \n return (\n
\n {children}\n
\n )\n}", "language": "tsx", "description": "breaks SSR" }, { "ruleId": "", "ruleTitle": "Prevent Hydration Mismatch Without Flickering", "type": "bad", "code": "function ThemeWrapper({ children }: { children: ReactNode }) {\n const [theme, setTheme] = useState('light')\n \n useEffect(() => {\n // Runs after hydration - causes visible flash\n const stored = localStorage.getItem('theme')\n if (stored) {\n setTheme(stored)\n }\n }, [])\n \n return (\n
\n {children}\n
\n )\n}", "language": "tsx", "description": "visual flickering" }, { "ruleId": "", "ruleTitle": "Prevent Hydration Mismatch Without Flickering", "type": "good", "code": "function ThemeWrapper({ children }: { children: ReactNode }) {\n return (\n <>\n
\n {children}\n
\n \n \n )\n}", "language": "tsx", "description": "no flicker, no hydration mismatch" }, { "ruleId": "", "ruleTitle": "Suppress Expected Hydration Mismatches", "type": "bad", "code": "function Timestamp() {\n return {new Date().toLocaleString()}\n}", "language": "tsx", "description": "known mismatch warnings" }, { "ruleId": "", "ruleTitle": "Suppress Expected Hydration Mismatches", "type": "good", "code": "function Timestamp() {\n return (\n \n {new Date().toLocaleString()}\n \n )\n}", "language": "tsx", "description": "suppress expected mismatch only" }, { "ruleId": "", "ruleTitle": "Use defer or async on Script Tags", "type": "bad", "code": "export default function Document() {\n return (\n \n \n