Building React components, optimizing Next.js performance
Senior frontend

Frontend development skill for React, Next.js, TypeScript, and Tailwind CSS applications. Use when building React components, optimizing Next.js performance, analyzing bundle sizes, scaffolding frontend projects, implementing…
--- name: "senior-frontend" description: Frontend development skill for React, Next.js, TypeScript, and Tailwind CSS applications. Use when building React components, optimizing Next.js performance, analyzing bundle sizes, scaffolding frontend projects, implementing accessibility, or reviewing frontend code quality. --- # Senior Frontend Frontend development patterns, performance optimization, and automation tools for React/Next.js applications. ## Table of Contents - [Project Scaffolding](#project-scaffolding) - [Component Generation](#component-generation) - [Bundle Analysis](#bundle-analysis) - [React Patterns](#react-patterns) - [Next.js Optimization](#nextjs-optimization) - [Accessibility and Testing](#accessibility-and-testing)
Overview
Frontend development patterns, performance optimization, and automation tools for React/Next.js applications.
Manual install (npx, ZIP)
npx skills add alirezarezvani/claude-skills --skill senior-frontend -g -a claude-codemkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/alirezarezvani/claude-skills/tar.gz/19392f7a0826 \
| tar -xz -C ~/.claude/skills --strip-components=3 claude-skills-19392f7a0826/engineering-team/skills/senior-frontendNew-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/b34098762657db0a1dec/senior-frontend.zip" -OutFile "$env:TEMP\senior-frontend.zip"; Expand-Archive "$env:TEMP\senior-frontend.zip" -DestinationPath "$HOME\.claude\skills" -ForceConnects with Agent Alley MCP · agentalley.io/mcp
Files included
SKILL.md15.3 KBprofiles/4 files · 9.6 KBreferences/5 files · 63.1 KBscripts/4 files · 60.9 KB
Read the files here
alirezarezvani/
- astro-or-static.json2.5 KB
- next-app-router.json2.5 KB
- remix-or-sveltekit.json2.3 KB
- vite-spa.json2.3 KB
- composition_map.md4.2 KB
- forcing_questions.md7.9 KB
- frontend_best_practices.md19.3 KB
- nextjs_optimization_guide.md14.8 KB
- react_patterns.md16.9 KB
- bundle_analyzer.py13.0 KB
- component_generator.py8.5 KB
- frontend_decision_engine.py12.0 KB
- frontend_scaffolder.py27.3 KB
Shared references1SKILL.md reads these from elsewhere in the repo. Not in the ZIP.
Get them on GitHub ↗Show the full text573 lines
Senior Frontend
Frontend development patterns, performance optimization, and automation tools for React/Next.js applications.
Table of Contents
- Project Scaffolding
- Component Generation
- Bundle Analysis
- React Patterns
- Next.js Optimization
- Accessibility and Testing
Project Scaffolding
Generate a new Next.js or React project with TypeScript, Tailwind CSS, and best practice configurations.
Workflow: Create New Frontend Project
Run the scaffolder with your project name and template:
python scripts/frontend_scaffolder.py my-app --template nextjsAdd optional features (auth, api, forms, testing, storybook):
python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,apiNavigate to the project and install dependencies:
cd my-app && npm installStart the development server:
npm run dev
Scaffolder Options
| Option | Description |
|---|---|
--template nextjs |
Next.js 14+ with App Router and Server Components |
--template react |
React + Vite with TypeScript |
--features auth |
Add NextAuth.js authentication |
--features api |
Add React Query + API client |
--features forms |
Add React Hook Form + Zod validation |
--features testing |
Add Vitest + Testing Library |
--dry-run |
Preview files without creating them |
Generated Structure (Next.js)
my-app/
├── app/
│ ├── layout.tsx # Root layout with fonts
│ ├── page.tsx # Home page
│ ├── globals.css # Tailwind + CSS variables
│ └── api/health/route.ts
├── components/
│ ├── ui/ # Button, Input, Card
│ └── layout/ # Header, Footer, Sidebar
├── hooks/ # useDebounce, useLocalStorage
├── lib/ # utils (cn), constants
├── types/ # TypeScript interfaces
├── tailwind.config.ts
├── next.config.js
└── package.json
Component Generation
Generate React components with TypeScript, tests, and Storybook stories.
Workflow: Create a New Component
Generate a client component:
python scripts/component_generator.py Button --dir src/components/uiGenerate a server component:
python scripts/component_generator.py ProductCard --type serverGenerate with test and story files:
python scripts/component_generator.py UserProfile --with-test --with-storyGenerate a custom hook:
python scripts/component_generator.py FormValidation --type hook
Generator Options
| Option | Description |
|---|---|
--type client |
Client component with 'use client' (default) |
--type server |
Async server component |
--type hook |
Custom React hook |
--with-test |
Include test file |
--with-story |
Include Storybook story |
--flat |
Create in output dir without subdirectory |
--dry-run |
Preview without creating files |
Generated Component Example
'use client';
import { useState } from 'react';
import { cn } from '@/lib/utils';
interface ButtonProps {
className?: string;
children?: React.ReactNode;
}
export function Button({ className, children }: ButtonProps) {
return (
<div className={cn('', className)}>
{children}
</div>
);
}
Bundle Analysis
Analyze package.json and project structure for bundle optimization opportunities.
Workflow: Optimize Bundle Size
Run the analyzer on your project:
python scripts/bundle_analyzer.py /path/to/projectReview the health score and issues:
Bundle Health Score: 75/100 (C) HEAVY DEPENDENCIES: moment (290KB) Alternative: date-fns (12KB) or dayjs (2KB) lodash (71KB) Alternative: lodash-es with tree-shakingApply the recommended fixes by replacing heavy dependencies.
Re-run with verbose mode to check import patterns:
python scripts/bundle_analyzer.py . --verbose
Bundle Score Interpretation
| Score | Grade | Action |
|---|---|---|
| 90-100 | A | Bundle is well-optimized |
| 80-89 | B | Minor optimizations available |
| 70-79 | C | Replace heavy dependencies |
| 60-69 | D | Multiple issues need attention |
| 0-59 | F | Critical bundle size problems |
Heavy Dependencies Detected
The analyzer identifies these common heavy packages:
| Package | Size | Alternative |
|---|---|---|
| moment | 290KB | date-fns (12KB) or dayjs (2KB) |
| lodash | 71KB | lodash-es with tree-shaking |
| axios | 14KB | Native fetch or ky (3KB) |
| jquery | 87KB | Native DOM APIs |
| @mui/material | Large | shadcn/ui or Radix UI |
React Patterns
Reference: references/react_patterns.md
Compound Components
Share state between related components:
const Tabs = ({ children }) => {
const [active, setActive] = useState(0);
return (
<TabsContext.Provider value={{ active, setActive }}>
{children}
</TabsContext.Provider>
);
};
Tabs.List = TabList;
Tabs.Panel = TabPanel;
// Usage
<Tabs>
<Tabs.List>
<Tabs.Tab>One</Tabs.Tab>
<Tabs.Tab>Two</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>Content 1</Tabs.Panel>
<Tabs.Panel>Content 2</Tabs.Panel>
</Tabs>
Custom Hooks
Extract reusable logic:
function useDebounce<T>(value: T, delay = 500): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Usage
const debouncedSearch = useDebounce(searchTerm, 300);
Render Props
Share rendering logic:
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url).then(r => r.json()).then(setData).finally(() => setLoading(false));
}, [url]);
return render({ data, loading });
}
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading }) =>
loading ? <Spinner /> : <UserList users={data} />
}
/>
Next.js Optimization
Reference: references/nextjs_optimization_guide.md
Server vs Client Components
Use Server Components by default. Add 'use client' only when you need:
- Event handlers (onClick, onChange)
- State (useState, useReducer)
- Effects (useEffect)
- Browser APIs
// Server Component (default) - no 'use client'
async function ProductPage({ params }) {
const product = await getProduct(params.id); // Server-side fetch
return (
<div>
<h1>{product.name}</h1>
<AddToCartButton productId={product.id} /> {/* Client component */}
</div>
);
}
// Client Component
'use client';
function AddToCartButton({ productId }) {
const [adding, setAdding] = useState(false);
return <button onClick={() => addToCart(productId)}>Add</button>;
}
Image Optimization
import Image from 'next/image';
// Above the fold - load immediately
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority
/>
// Responsive image with fill
<div className="relative aspect-video">
<Image
src="/product.jpg"
alt="Product"
fill
sizes="(max-width: 768px) 100vw, 50vw"
className="object-cover"
/>
</div>
Data Fetching Patterns
// Parallel fetching
async function Dashboard() {
const [user, stats] = await Promise.all([
getUser(),
getStats()
]);
return <div>...</div>;
}
// Streaming with Suspense
async function ProductPage({ params }) {
return (
<div>
<ProductDetails id={params.id} />
<Suspense fallback={<ReviewsSkeleton />}>
<Reviews productId={params.id} />
</Suspense>
</div>
);
}
Accessibility and Testing
Reference: references/frontend_best_practices.md
Accessibility Checklist
- Semantic HTML: Use proper elements (
<button>,<nav>,<main>) - Keyboard Navigation: All interactive elements focusable
- ARIA Labels: Provide labels for icons and complex widgets
- Color Contrast: Minimum 4.5:1 for normal text
- Focus Indicators: Visible focus states
// Accessible button
<button
type="button"
aria-label="Close dialog"
onClick={onClose}
className="focus-visible:ring-2 focus-visible:ring-blue-500"
>
<XIcon aria-hidden="true" />
</button>
// Skip link for keyboard users
<a href="#main-content" className="sr-only focus:not-sr-only">
Skip to main content
</a>
Testing Strategy
// Component test with React Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('button triggers action on click', async () => {
const onClick = vi.fn();
render(<Button onClick={onClick}>Click me</Button>);
await userEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});
// Test accessibility
test('dialog is accessible', async () => {
render(<Dialog open={true} title="Confirm" />);
expect(screen.getByRole('dialog')).toBeInTheDocument();
expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
});
Quick Reference
Common Next.js Config
// next.config.js
const nextConfig = {
images: {
remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }],
formats: ['image/avif', 'image/webp'],
},
experimental: {
optimizePackageImports: ['lucide-react', '@heroicons/react'],
},
};
Tailwind CSS Utilities
// Conditional classes with cn()
import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded',
variant === 'primary' && 'bg-blue-500 text-white',
disabled && 'opacity-50 cursor-not-allowed'
)} />
TypeScript Patterns
// Props with children
interface CardProps {
className?: string;
children: React.ReactNode;
}
// Generic component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
Resources
- React Patterns:
references/react_patterns.md - Next.js Optimization:
references/nextjs_optimization_guide.md - Best Practices:
references/frontend_best_practices.md - Forcing-question library (Matt Pocock grill):
references/forcing_questions.md - Composition map (which specialist to fork into):
references/composition_map.md
Assumptions and Verifiable Success Criteria (Karpathy discipline)
Before this skill scaffolds a component, recommends a framework, or audits a bundle, the following four assumptions MUST be surfaced.
- Primary user device + network — mobile-4G, desktop-fiber, low-end-Android, or corporate-network. Drives every perf decision.
- LCP target in milliseconds — a single number, not "fast." Drives bundle budget and rendering choice.
- SEO-dependent vs. auth-walled — drives rendering (SSR/SSG/RSC vs. SPA).
- WCAG target + named a11y owner — AA, AAA, or best-effort. Drives a11y investment and CI gates.
Verifiable success criteria (Karpathy #4) — every recommendation must include:
- Core Web Vitals targets (LCP, INP, CLS) at p75 on the primary device
- A per-route JS bundle budget in KB-gzip
- A Lighthouse a11y floor + perf floor
If any of those three is not stated, the recommendation is incomplete — return to Q2 of the forcing-question library.
The scripts/frontend_decision_engine.py tool encodes these checks: it refuses to recommend a profile without the four assumption inputs and prints the verifiable thresholds for the matched profile.
Customization profiles
Four built-in profiles in profiles/ calibrate every recommendation:
| Profile | When to pick | LCP target (mobile-4G p75) | Bundle budget |
|---|---|---|---|
next-app-router |
SaaS customer-facing, SEO + dynamic, RSC-first | 2000ms | 150 KB-gzip / route |
remix-or-sveltekit |
Mobile-4G primary, low-JS-first, progressive enhancement | 1500ms | 80 KB-gzip / route |
vite-spa |
Auth-walled app, desktop/corporate primary | 2500ms | 200 KB init + 80 KB / route |
astro-or-static |
Marketing / docs / blog, near-zero write, SEO-critical | 1200ms | 30 KB JS / page |
Pick a profile via:
python scripts/frontend_decision_engine.py \
--primary-device mobile-4g --lcp-target-ms 2000 \
--seo-dependent true --auth-walled false --team-size 5
The tool returns the best-fit profile, the runner-up tradeoff (if within 15%), the stack picks, the anti-patterns to avoid on that profile, and the required CI gates.
To add a custom profile (e.g., your org's internal-tool defaults): copy profiles/vite-spa.json to profiles/<your-org>.json and adjust constraints + success_thresholds.
Composition map
This skill does NOT reimplement scope owned by the POWERFUL-tier specialists. It forks into them. See references/composition_map.md for the full routing table. Key forks:
| Concern | Fork into |
|---|---|
| WCAG audit, contrast, screen-reader | engineering-team/skills/a11y-audit/ |
| Bundle profiling + runtime perf | engineering/skills/performance-profiler/ |
| Cinematic / scroll-storytelling landing | engineering-team/skills/epic-design/ |
| Apple HIG (iOS / macOS / visionOS) | product-team/skills/apple-hig-expert/ |
| Pre-commit Karpathy review | engineering/karpathy-coder/ |
| Pre-flight architecture grill | engineering/grill-me/ |
The cs-frontend-engineer agent orchestrates these forks via context: fork. Invoke it from another agent with Agent({subagent_type: "cs-frontend-engineer", prompt: "..."}) or via /cs:frontend-review <your problem>.
Forcing-question library (Matt Pocock grill)
Before locking any framework or rendering decision, walk the seven forcing questions in references/forcing_questions.md. Discipline:
- One question per turn. No bundling.
- Always recommend the answer with cited canon.
- Track answers in
/tmp/frontend-grill-<date>.md. - If a kill criterion trips, stop. Don't scaffold around an unresolved gap.
- After Q7, run
frontend_decision_engine.pywith the seven answers.
Summary:
- Primary device + network?
- LCP target in ms (and INP, CLS)?
- RSC / SPA / SSR / SSG — pick and defend?
- JS bundle budget per route?
- SEO-dependent or auth-walled?
- Design-system source of truth?
- WCAG target + named a11y owner?
Invocation from other agents and skills
Three surfaces:
- Slash command:
/cs:frontend-review <prompt>— full grill + decision engine + composition routing. - Agent subagent:
Agent({subagent_type: "cs-frontend-engineer", prompt: "..."})— forks context, returns ≤ 200-word digest. - Direct tool call:
python scripts/frontend_decision_engine.py ...— deterministic profile match when inputs are known.
See agents/engineering/cs-frontend-engineer.md for the full invocation contract.
| 1 | |
| 2 | name "senior-frontend" |
| 3 | description Frontend development skill for React, Next.js, TypeScript, and Tailwind CSS applications. Use when building React components, optimizing Next.js performance, analyzing bundle sizes, scaffolding frontend projects, implementing accessibility, or reviewing frontend code quality. |
| 4 | |
| 5 | |
| 6 | # Senior Frontend |
| 7 | |
| 8 | Frontend development patterns, performance optimization, and automation tools for React/Next.js applications. |
| 9 | |
| 10 | ## Table of Contents |
| 11 | |
| 12 | [Project Scaffolding] |
| 13 | [Component Generation] |
| 14 | [Bundle Analysis] |
| 15 | [React Patterns] |
| 16 | [Next.js Optimization] |
| 17 | [Accessibility and Testing] |
| 18 | |
| 19 | |
| 20 | |
| 21 | ## Project Scaffolding |
| 22 | |
| 23 | Generate a new Next.js or React project with TypeScript, Tailwind CSS, and best practice configurations. |
| 24 | |
| 25 | ### Workflow: Create New Frontend Project |
| 26 | |
| 27 | Run the scaffolder with your project name and template: |
| 28 | |
| 29 | python scripts/frontend_scaffolder.py my-app --template nextjs |
| 30 | |
| 31 | |
| 32 | Add optional features (auth, api, forms, testing, storybook): |
| 33 | |
| 34 | python scripts/frontend_scaffolder.py dashboard --template nextjs --features auth,api |
| 35 | |
| 36 | |
| 37 | Navigate to the project and install dependencies: |
| 38 | |
| 39 | cd my-app && npm install |
| 40 | |
| 41 | |
| 42 | Start the development server: |
| 43 | |
| 44 | npm run dev |
| 45 | |
| 46 | |
| 47 | ### Scaffolder Options |
| 48 | |
| 49 | | Option | Description | |
| 50 | |--------|-------------| |
| 51 | | `--template nextjs` | Next.js 14+ with App Router and Server Components | |
| 52 | | `--template react` | React + Vite with TypeScript | |
| 53 | | `--features auth` | Add NextAuth.js authentication | |
| 54 | | `--features api` | Add React Query + API client | |
| 55 | | `--features forms` | Add React Hook Form + Zod validation | |
| 56 | | `--features testing` | Add Vitest + Testing Library | |
| 57 | | `--dry-run` | Preview files without creating them | |
| 58 | |
| 59 | ### Generated Structure (Next.js) |
| 60 | |
| 61 | |
| 62 | my-app/ |
| 63 | ├── app/ |
| 64 | │ ├── layout.tsx # Root layout with fonts |
| 65 | │ ├── page.tsx # Home page |
| 66 | │ ├── globals.css # Tailwind + CSS variables |
| 67 | │ └── api/health/route.ts |
| 68 | ├── components/ |
| 69 | │ ├── ui/ # Button, Input, Card |
| 70 | │ └── layout/ # Header, Footer, Sidebar |
| 71 | ├── hooks/ # useDebounce, useLocalStorage |
| 72 | ├── lib/ # utils (cn), constants |
| 73 | ├── types/ # TypeScript interfaces |
| 74 | ├── tailwind.config.ts |
| 75 | ├── next.config.js |
| 76 | └── package.json |
| 77 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | ## Component Generation |
| 82 | |
| 83 | Generate React components with TypeScript, tests, and Storybook stories. |
| 84 | |
| 85 | ### Workflow: Create a New Component |
| 86 | |
| 87 | Generate a client component: |
| 88 | |
| 89 | python scripts/component_generator.py Button --dir src/components/ui |
| 90 | |
| 91 | |
| 92 | Generate a server component: |
| 93 | |
| 94 | python scripts/component_generator.py ProductCard --type server |
| 95 | |
| 96 | |
| 97 | Generate with test and story files: |
| 98 | |
| 99 | python scripts/component_generator.py UserProfile --with-test --with-story |
| 100 | |
| 101 | |
| 102 | Generate a custom hook: |
| 103 | |
| 104 | python scripts/component_generator.py FormValidation --type hook |
| 105 | |
| 106 | |
| 107 | ### Generator Options |
| 108 | |
| 109 | | Option | Description | |
| 110 | |--------|-------------| |
| 111 | | `--type client` | Client component with 'use client' (default) | |
| 112 | | `--type server` | Async server component | |
| 113 | | `--type hook` | Custom React hook | |
| 114 | | `--with-test` | Include test file | |
| 115 | | `--with-story` | Include Storybook story | |
| 116 | | `--flat` | Create in output dir without subdirectory | |
| 117 | | `--dry-run` | Preview without creating files | |
| 118 | |
| 119 | ### Generated Component Example |
| 120 | |
| 121 | |
| 122 | 'use client'; |
| 123 | |
| 124 | import { useState } from 'react'; |
| 125 | import { cn } from '@/lib/utils'; |
| 126 | |
| 127 | interface ButtonProps { |
| 128 | className?: string; |
| 129 | children?: React.ReactNode; |
| 130 | } |
| 131 | |
| 132 | export function Button({ className, children }: ButtonProps) { |
| 133 | return ( |
| 134 | <div className={cn('', className)}> |
| 135 | {children} |
| 136 | </div> |
| 137 | ); |
| 138 | } |
| 139 | |
| 140 | |
| 141 | |
| 142 | |
| 143 | ## Bundle Analysis |
| 144 | |
| 145 | Analyze package.json and project structure for bundle optimization opportunities. |
| 146 | |
| 147 | ### Workflow: Optimize Bundle Size |
| 148 | |
| 149 | Run the analyzer on your project: |
| 150 | |
| 151 | python scripts/bundle_analyzer.py /path/to/project |
| 152 | |
| 153 | |
| 154 | Review the health score and issues: |
| 155 | |
| 156 | Bundle Health Score: 75/100 (C) |
| 157 | |
| 158 | HEAVY DEPENDENCIES: |
| 159 | moment (290KB) |
| 160 | Alternative: date-fns (12KB) or dayjs (2KB) |
| 161 | |
| 162 | lodash (71KB) |
| 163 | Alternative: lodash-es with tree-shaking |
| 164 | |
| 165 | |
| 166 | Apply the recommended fixes by replacing heavy dependencies. |
| 167 | |
| 168 | Re-run with verbose mode to check import patterns: |
| 169 | |
| 170 | python scripts/bundle_analyzer.py . --verbose |
| 171 | |
| 172 | |
| 173 | ### Bundle Score Interpretation |
| 174 | |
| 175 | | Score | Grade | Action | |
| 176 | |-------|-------|--------| |
| 177 | | 90-100 | A | Bundle is well-optimized | |
| 178 | | 80-89 | B | Minor optimizations available | |
| 179 | | 70-79 | C | Replace heavy dependencies | |
| 180 | | 60-69 | D | Multiple issues need attention | |
| 181 | | 0-59 | F | Critical bundle size problems | |
| 182 | |
| 183 | ### Heavy Dependencies Detected |
| 184 | |
| 185 | The analyzer identifies these common heavy packages: |
| 186 | |
| 187 | | Package | Size | Alternative | |
| 188 | |---------|------|-------------| |
| 189 | | moment | 290KB | date-fns (12KB) or dayjs (2KB) | |
| 190 | | lodash | 71KB | lodash-es with tree-shaking | |
| 191 | | axios | 14KB | Native fetch or ky (3KB) | |
| 192 | | jquery | 87KB | Native DOM APIs | |
| 193 | | @mui/material | Large | shadcn/ui or Radix UI | |
| 194 | |
| 195 | |
| 196 | |
| 197 | ## React Patterns |
| 198 | |
| 199 | Reference: `references/react_patterns.md` |
| 200 | |
| 201 | ### Compound Components |
| 202 | |
| 203 | Share state between related components: |
| 204 | |
| 205 | |
| 206 | const Tabs = ({ children }) => { |
| 207 | const [active, setActive] = useState(0); |
| 208 | return ( |
| 209 | <TabsContext.Provider value={{ active, setActive }}> |
| 210 | {children} |
| 211 | </TabsContext.Provider> |
| 212 | ); |
| 213 | }; |
| 214 | |
| 215 | Tabs.List = TabList; |
| 216 | Tabs.Panel = TabPanel; |
| 217 | |
| 218 | // Usage |
| 219 | <Tabs> |
| 220 | <Tabs.List> |
| 221 | <Tabs.Tab>One</Tabs.Tab> |
| 222 | <Tabs.Tab>Two</Tabs.Tab> |
| 223 | </Tabs.List> |
| 224 | <Tabs.Panel>Content 1</Tabs.Panel> |
| 225 | <Tabs.Panel>Content 2</Tabs.Panel> |
| 226 | </Tabs> |
| 227 | |
| 228 | |
| 229 | ### Custom Hooks |
| 230 | |
| 231 | Extract reusable logic: |
| 232 | |
| 233 | |
| 234 | function useDebounce<T>(value: T, delay = 500): T { |
| 235 | const [debouncedValue, setDebouncedValue] = useState(value); |
| 236 | |
| 237 | useEffect(() => { |
| 238 | const timer = setTimeout(() => setDebouncedValue(value), delay); |
| 239 | return () => clearTimeout(timer); |
| 240 | }, [value, delay]); |
| 241 | |
| 242 | return debouncedValue; |
| 243 | } |
| 244 | |
| 245 | // Usage |
| 246 | const debouncedSearch = useDebounce(searchTerm, 300); |
| 247 | |
| 248 | |
| 249 | ### Render Props |
| 250 | |
| 251 | Share rendering logic: |
| 252 | |
| 253 | |
| 254 | function DataFetcher({ url, render }) { |
| 255 | const [data, setData] = useState(null); |
| 256 | const [loading, setLoading] = useState(true); |
| 257 | |
| 258 | useEffect(() => { |
| 259 | fetch(url).then(r => r.json()).then(setData).finally(() => setLoading(false)); |
| 260 | }, [url]); |
| 261 | |
| 262 | return render({ data, loading }); |
| 263 | } |
| 264 | |
| 265 | // Usage |
| 266 | <DataFetcher |
| 267 | url="/api/users" |
| 268 | render={({ data, loading }) => |
| 269 | loading ? <Spinner /> : <UserList users={data} /> |
| 270 | } |
| 271 | /> |
| 272 | |
| 273 | |
| 274 | |
| 275 | |
| 276 | ## Next.js Optimization |
| 277 | |
| 278 | Reference: `references/nextjs_optimization_guide.md` |
| 279 | |
| 280 | ### Server vs Client Components |
| 281 | |
| 282 | Use Server Components by default. Add 'use client' only when you need: |
| 283 | Event handlers (onClick, onChange) |
| 284 | State (useState, useReducer) |
| 285 | Effects (useEffect) |
| 286 | Browser APIs |
| 287 | |
| 288 | |
| 289 | // Server Component (default) - no 'use client' |
| 290 | async function ProductPage({ params }) { |
| 291 | const product = await getProduct(params.id); // Server-side fetch |
| 292 | |
| 293 | return ( |
| 294 | <div> |
| 295 | <h1>{product.name}</h1> |
| 296 | <AddToCartButton productId={product.id} /> {/* Client component */} |
| 297 | </div> |
| 298 | ); |
| 299 | } |
| 300 | |
| 301 | // Client Component |
| 302 | 'use client'; |
| 303 | function AddToCartButton({ productId }) { |
| 304 | const [adding, setAdding] = useState(false); |
| 305 | return <button onClick={() => addToCart(productId)}>Add</button>; |
| 306 | } |
| 307 | |
| 308 | |
| 309 | ### Image Optimization |
| 310 | |
| 311 | |
| 312 | import Image from 'next/image'; |
| 313 | |
| 314 | // Above the fold - load immediately |
| 315 | <Image |
| 316 | src="/hero.jpg" |
| 317 | alt="Hero" |
| 318 | width={1200} |
| 319 | height={600} |
| 320 | priority |
| 321 | /> |
| 322 | |
| 323 | // Responsive image with fill |
| 324 | <div className="relative aspect-video"> |
| 325 | <Image |
| 326 | src="/product.jpg" |
| 327 | alt="Product" |
| 328 | fill |
| 329 | sizes="(max-width: 768px) 100vw, 50vw" |
| 330 | className="object-cover" |
| 331 | /> |
| 332 | </div> |
| 333 | |
| 334 | |
| 335 | ### Data Fetching Patterns |
| 336 | |
| 337 | |
| 338 | // Parallel fetching |
| 339 | async function Dashboard() { |
| 340 | const [user, stats] = await Promise.all([ |
| 341 | getUser(), |
| 342 | getStats() |
| 343 | ]); |
| 344 | return <div>...</div>; |
| 345 | } |
| 346 | |
| 347 | // Streaming with Suspense |
| 348 | async function ProductPage({ params }) { |
| 349 | return ( |
| 350 | <div> |
| 351 | <ProductDetails id={params.id} /> |
| 352 | <Suspense fallback={<ReviewsSkeleton />}> |
| 353 | <Reviews productId={params.id} /> |
| 354 | </Suspense> |
| 355 | </div> |
| 356 | ); |
| 357 | } |
| 358 | |
| 359 | |
| 360 | |
| 361 | |
| 362 | ## Accessibility and Testing |
| 363 | |
| 364 | Reference: `references/frontend_best_practices.md` |
| 365 | |
| 366 | ### Accessibility Checklist |
| 367 | |
| 368 | **Semantic HTML**: Use proper elements (`<button>`, `<nav>`, `<main>`) |
| 369 | **Keyboard Navigation**: All interactive elements focusable |
| 370 | **ARIA Labels**: Provide labels for icons and complex widgets |
| 371 | **Color Contrast**: Minimum 4.5:1 for normal text |
| 372 | **Focus Indicators**: Visible focus states |
| 373 | |
| 374 | |
| 375 | // Accessible button |
| 376 | <button |
| 377 | type="button" |
| 378 | aria-label="Close dialog" |
| 379 | onClick={onClose} |
| 380 | className="focus-visible:ring-2 focus-visible:ring-blue-500" |
| 381 | > |
| 382 | <XIcon aria-hidden="true" /> |
| 383 | </button> |
| 384 | |
| 385 | // Skip link for keyboard users |
| 386 | <a href="#main-content" className="sr-only focus:not-sr-only"> |
| 387 | Skip to main content |
| 388 | </a> |
| 389 | |
| 390 | |
| 391 | ### Testing Strategy |
| 392 | |
| 393 | |
| 394 | // Component test with React Testing Library |
| 395 | import { render, screen } from '@testing-library/react'; |
| 396 | import userEvent from '@testing-library/user-event'; |
| 397 | |
| 398 | test('button triggers action on click', async () => { |
| 399 | const onClick = vi.fn(); |
| 400 | render(<Button onClick={onClick}>Click me</Button>); |
| 401 | |
| 402 | await userEvent.click(screen.getByRole('button')); |
| 403 | expect(onClick).toHaveBeenCalledTimes(1); |
| 404 | }); |
| 405 | |
| 406 | // Test accessibility |
| 407 | test('dialog is accessible', async () => { |
| 408 | render(<Dialog open={true} title="Confirm" />); |
| 409 | |
| 410 | expect(screen.getByRole('dialog')).toBeInTheDocument(); |
| 411 | expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby'); |
| 412 | }); |
| 413 | |
| 414 | |
| 415 | |
| 416 | |
| 417 | ## Quick Reference |
| 418 | |
| 419 | ### Common Next.js Config |
| 420 | |
| 421 | |
| 422 | // next.config.js |
| 423 | const nextConfig = { |
| 424 | images: { |
| 425 | remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }], |
| 426 | formats: ['image/avif', 'image/webp'], |
| 427 | }, |
| 428 | experimental: { |
| 429 | optimizePackageImports: ['lucide-react', '@heroicons/react'], |
| 430 | }, |
| 431 | }; |
| 432 | |
| 433 | |
| 434 | ### Tailwind CSS Utilities |
| 435 | |
| 436 | |
| 437 | // Conditional classes with cn() |
| 438 | import { cn } from '@/lib/utils'; |
| 439 | |
| 440 | <button className={cn( |
| 441 | 'px-4 py-2 rounded', |
| 442 | variant === 'primary' && 'bg-blue-500 text-white', |
| 443 | disabled && 'opacity-50 cursor-not-allowed' |
| 444 | )} /> |
| 445 | |
| 446 | |
| 447 | ### TypeScript Patterns |
| 448 | |
| 449 | |
| 450 | // Props with children |
| 451 | interface CardProps { |
| 452 | className?: string; |
| 453 | children: React.ReactNode; |
| 454 | } |
| 455 | |
| 456 | // Generic component |
| 457 | interface ListProps<T> { |
| 458 | items: T[]; |
| 459 | renderItem: (item: T) => React.ReactNode; |
| 460 | } |
| 461 | |
| 462 | function List<T>({ items, renderItem }: ListProps<T>) { |
| 463 | return <ul>{items.map(renderItem)}</ul>; |
| 464 | } |
| 465 | |
| 466 | |
| 467 | |
| 468 | |
| 469 | ## Resources |
| 470 | |
| 471 | React Patterns: `references/react_patterns.md` |
| 472 | Next.js Optimization: `references/nextjs_optimization_guide.md` |
| 473 | Best Practices: `references/frontend_best_practices.md` |
| 474 | Forcing-question library (Matt Pocock grill): `references/forcing_questions.md` |
| 475 | Composition map (which specialist to fork into): `references/composition_map.md` |
| 476 | |
| 477 | |
| 478 | |
| 479 | ## Assumptions and Verifiable Success Criteria (Karpathy discipline) |
| 480 | |
| 481 | Before this skill scaffolds a component, recommends a framework, or audits a bundle, the following four assumptions MUST be surfaced. |
| 482 | |
| 483 | **Primary user device + network** — mobile-4G, desktop-fiber, low-end-Android, or corporate-network. Drives every perf decision. |
| 484 | **LCP target in milliseconds** — a single number, not "fast." Drives bundle budget and rendering choice. |
| 485 | **SEO-dependent vs. auth-walled** — drives rendering (SSR/SSG/RSC vs. SPA). |
| 486 | **WCAG target + named a11y owner** — AA, AAA, or best-effort. Drives a11y investment and CI gates. |
| 487 | |
| 488 | **Verifiable success criteria** (Karpathy #4) — every recommendation must include: |
| 489 | |
| 490 | Core Web Vitals targets (LCP, INP, CLS) at p75 on the primary device |
| 491 | A per-route JS bundle budget in KB-gzip |
| 492 | A Lighthouse a11y floor + perf floor |
| 493 | |
| 494 | If any of those three is not stated, the recommendation is incomplete — return to Q2 of the forcing-question library. |
| 495 | |
| 496 | The `scripts/frontend_decision_engine.py` tool encodes these checks: it refuses to recommend a profile without the four assumption inputs and prints the verifiable thresholds for the matched profile. |
| 497 | |
| 498 | |
| 499 | |
| 500 | ## Customization profiles |
| 501 | |
| 502 | Four built-in profiles in `profiles/` calibrate every recommendation: |
| 503 | |
| 504 | | Profile | When to pick | LCP target (mobile-4G p75) | Bundle budget | |
| 505 | |---|---|---|---| |
| 506 | | `next-app-router` | SaaS customer-facing, SEO + dynamic, RSC-first | 2000ms | 150 KB-gzip / route | |
| 507 | | `remix-or-sveltekit` | Mobile-4G primary, low-JS-first, progressive enhancement | 1500ms | 80 KB-gzip / route | |
| 508 | | `vite-spa` | Auth-walled app, desktop/corporate primary | 2500ms | 200 KB init + 80 KB / route | |
| 509 | | `astro-or-static` | Marketing / docs / blog, near-zero write, SEO-critical | 1200ms | 30 KB JS / page | |
| 510 | |
| 511 | Pick a profile via: |
| 512 | |
| 513 | |
| 514 | python scripts/frontend_decision_engine.py \ |
| 515 | --primary-device mobile-4g --lcp-target-ms 2000 \ |
| 516 | --seo-dependent true --auth-walled false --team-size 5 |
| 517 | |
| 518 | |
| 519 | The tool returns the best-fit profile, the runner-up tradeoff (if within 15%), the stack picks, the anti-patterns to avoid on that profile, and the required CI gates. |
| 520 | |
| 521 | To add a custom profile (e.g., your org's internal-tool defaults): copy `profiles/vite-spa.json` to `profiles/<your-org>.json` and adjust `constraints` + `success_thresholds`. |
| 522 | |
| 523 | |
| 524 | |
| 525 | ## Composition map |
| 526 | |
| 527 | This skill does NOT reimplement scope owned by the POWERFUL-tier specialists. It forks into them. See `references/composition_map.md` for the full routing table. Key forks: |
| 528 | |
| 529 | | Concern | Fork into | |
| 530 | |---|---| |
| 531 | | WCAG audit, contrast, screen-reader | `engineering-team/skills/a11y-audit/` | |
| 532 | | Bundle profiling + runtime perf | `engineering/skills/performance-profiler/` | |
| 533 | | Cinematic / scroll-storytelling landing | `engineering-team/skills/epic-design/` | |
| 534 | | Apple HIG (iOS / macOS / visionOS) | `product-team/skills/apple-hig-expert/` | |
| 535 | | Pre-commit Karpathy review | `engineering/karpathy-coder/` | |
| 536 | | Pre-flight architecture grill | `engineering/grill-me/` | |
| 537 | |
| 538 | The `cs-frontend-engineer` agent orchestrates these forks via `context: fork`. Invoke it from another agent with `Agent({subagent_type: "cs-frontend-engineer", prompt: "..."})` or via `/cs:frontend-review <your problem>`. |
| 539 | |
| 540 | |
| 541 | |
| 542 | ## Forcing-question library (Matt Pocock grill) |
| 543 | |
| 544 | Before locking any framework or rendering decision, walk the seven forcing questions in `references/forcing_questions.md`. Discipline: |
| 545 | |
| 546 | One question per turn. No bundling. |
| 547 | Always recommend the answer with cited canon. |
| 548 | Track answers in `/tmp/frontend-grill-<date>.md`. |
| 549 | If a kill criterion trips, stop. Don't scaffold around an unresolved gap. |
| 550 | After Q7, run `frontend_decision_engine.py` with the seven answers. |
| 551 | |
| 552 | Summary: |
| 553 | |
| 554 | Primary device + network? |
| 555 | LCP target in ms (and INP, CLS)? |
| 556 | RSC / SPA / SSR / SSG — pick and defend? |
| 557 | JS bundle budget per route? |
| 558 | SEO-dependent or auth-walled? |
| 559 | Design-system source of truth? |
| 560 | WCAG target + named a11y owner? |
| 561 | |
| 562 | |
| 563 | |
| 564 | ## Invocation from other agents and skills |
| 565 | |
| 566 | Three surfaces: |
| 567 | |
| 568 | **Slash command:** `/cs:frontend-review <prompt>` — full grill + decision engine + composition routing. |
| 569 | **Agent subagent:** `Agent({subagent_type: "cs-frontend-engineer", prompt: "..."})` — forks context, returns ≤ 200-word digest. |
| 570 | **Direct tool call:** `python scripts/frontend_decision_engine.py ...` — deterministic profile match when inputs are known. |
| 571 | |
| 572 | See `agents/engineering/cs-frontend-engineer.md` for the full invocation contract. |
| 573 |
Details
- Updated
- Aug 30, 2026
- License
- MIT
- Stars on the repo
- ★ 26,349
- Checked
- Source
- alirezarezvani/claude-skills ↗
- Creator
- alirezarezvani · 363 listings
Similar skills
See all →
SkillDoing ANY task involving Supabase
PromptAct as a specialized front-end developer with expertiseNext.js Specialized Front-End Developer
SkillBuilding Next.js applications, implementing SSR/SSGNext.js App Router Patterns
SkillReviewing a specific inbound deal before closeDeal desk
SkillGenerates complete, production-ready SaaS projectSAAS scaffolder
MCPBuilding command-line tools and terminal applicationsCLI developerMore from alirezarezvani
SkillResume a paused experiment/ar:ar-resume — Resume Experiment
SkillShow experiment dashboard with results, active loops/ar:ar-status — Experiment Dashboard
SkillStart an autonomous experiment loop with user-selected/ar:loop — Autonomous Experiment Loop
SkillRun a single experiment iteration/ar:run — Single Experiment Iteration
SkillSet up a new autoresearch experiment interactively/ar:setup — Create New Experiment
Skill/cs:boardroom <brief/cs:boardroom — Multi-Role Boardroom Deliberation