plugins/frontend/skills/performance-security/SKILL.md
Use when optimizing performance or reviewing security. Covers code-splitting, React Compiler patterns, asset optimization, a11y testing, and security hardening for React apps.
npx skillsauth add madappgang/claude-code performance-securityInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Production-ready patterns for building fast, accessible, and secure React applications.
Automatic with TanStack Router:
Manual code-splitting:
import { lazy, Suspense } from 'react'
// Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'))
function Dashboard() {
return (
<Suspense fallback={<Spinner />}>
<HeavyChart data={data} />
</Suspense>
)
}
Route-level lazy loading:
// src/routes/dashboard.lazy.tsx
export const Route = createLazyFileRoute('/dashboard')({
component: DashboardComponent,
})
The React Compiler automatically optimizes performance when you write compiler-friendly code:
✅ Do:
❌ Avoid:
Verify optimization:
Use Vite asset pipeline:
// Imports are optimized and hashed
import logo from './logo.png'
<img src={logo} alt="Logo" />
Prefer modern formats:
// WebP for photos
<img src="/hero.webp" alt="Hero" />
// SVG for icons
import { ReactComponent as Icon } from './icon.svg'
<Icon />
Lazy load images:
<img src={imageSrc} loading="lazy" alt="Description" />
Responsive images:
<img
srcSet="
/image-320w.webp 320w,
/image-640w.webp 640w,
/image-1280w.webp 1280w
"
sizes="(max-width: 640px) 100vw, 640px"
src="/image-640w.webp"
alt="Description"
/>
# Build with analysis
npx vite build --mode production
# Visualize bundle
pnpm add -D rollup-plugin-visualizer
// vite.config.ts
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
react(),
visualizer({ open: true }),
],
})
staleTime per query✅ Use semantic elements:
// Good
<nav><a href="/about">About</a></nav>
<button onClick={handleClick}>Submit</button>
<main><article>Content</article></main>
// Bad
<div onClick={handleNav}>About</div>
<div onClick={handleClick}>Submit</div>
<div><div>Content</div></div>
Only add ARIA when semantic HTML isn't enough:
// Custom select component
<div
role="listbox"
aria-label="Select country"
aria-activedescendant={activeId}
>
<div role="option" id="us">United States</div>
<div role="option" id="uk">United Kingdom</div>
</div>
// Loading state
<button aria-busy={isLoading} disabled={isLoading}>
{isLoading ? 'Loading...' : 'Submit'}
</button>
Ensure all interactive elements are keyboard accessible:
function Dialog({ isOpen, onClose }: DialogProps) {
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
if (isOpen) {
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}
}, [isOpen, onClose])
return isOpen ? (
<div role="dialog" aria-modal="true">
{/* Focus trap implementation */}
<button onClick={onClose} aria-label="Close dialog">×</button>
{/* Dialog content */}
</div>
) : null
}
Use accessible queries (by role/label):
import { render, screen } from '@testing-library/react'
test('button is accessible', () => {
render(<button>Submit</button>)
// ✅ Good - query by role
const button = screen.getByRole('button', { name: /submit/i })
expect(button).toBeInTheDocument()
// ❌ Avoid - query by test ID
const button = screen.getByTestId('submit-button')
})
Common accessible queries:
// By role (preferred)
screen.getByRole('button', { name: /submit/i })
screen.getByRole('textbox', { name: /email/i })
screen.getByRole('heading', { level: 1 })
// By label
screen.getByLabelText(/email address/i)
// By text
screen.getByText(/welcome/i)
❌ Wrong - secrets in code:
const API_KEY = 'sk_live_abc123' // Exposed in bundle!
✅ Correct - environment variables:
// Only VITE_* variables are exposed to client
const API_KEY = import.meta.env.VITE_PUBLIC_KEY
In .env.local (not committed):
VITE_PUBLIC_KEY=pk_live_abc123 # Public key only!
Backend handles secrets:
// Frontend calls backend, backend uses secret API key
await apiClient.post('/process-payment', { amount, token })
// Backend has access to SECRET_KEY via server env
At boundaries (API responses):
import { z } from 'zod'
const UserSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string().email(),
})
async function fetchUser(id: string) {
const response = await apiClient.get(`/users/${id}`)
// Validate response
return UserSchema.parse(response.data)
}
User input:
const formSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be 8+ characters'),
})
type FormData = z.infer<typeof formSchema>
function LoginForm() {
const handleSubmit = (data: unknown) => {
const result = formSchema.safeParse(data)
if (!result.success) {
setErrors(result.error.errors)
return
}
// result.data is typed and validated
login(result.data)
}
}
React automatically escapes content in JSX:
// ✅ Safe - React escapes
<div>{userInput}</div>
// ❌ Dangerous - bypasses escaping
<div dangerouslySetInnerHTML={{ __html: userInput }} />
If you must use HTML:
import DOMPurify from 'dompurify'
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(trustedHTML)
}} />
Add CSP headers on server:
# nginx example
add_header Content-Security-Policy "
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
connect-src 'self' https://api.example.com;
";
Pin versions in package.json:
{
"dependencies": {
"react": "19.0.0", // Exact version
"@tanstack/react-query": "^5.59.0" // Allow patches
}
}
Audit regularly:
pnpm audit
pnpm audit --fix
Use Renovate or Dependabot:
// .github/renovate.json
{
"extends": ["config:base"],
"automerge": true,
"major": { "automerge": false }
}
Run with --ignore-scripts:
# Prevents malicious post-install scripts
pnpm install --ignore-scripts
Scan for secrets:
# Add to CI
git-secrets --scan
VITE_* env vars to clientpnpm audit regularly--ignore-scripts in CItesting
A test skill for validation testing. Use when testing skill parsing and validation logic.
tools
--- name: bad-skill description: This skill has invalid YAML in frontmatter allowed-tools: [invalid, array, syntax prerequisites: not-an-array --- # Bad Skill This skill has malformed frontmatter that should fail parsing. The YAML has: - Unclosed array bracket - Wrong type for prerequisites (should be array, not string)
tools
Plugin release process for MAG Claude Plugins marketplace. Covers version bumping, marketplace.json updates, git tagging, and common mistakes. Use when releasing new plugin versions or troubleshooting update issues.
testing
Fetch trending programming models from OpenRouter rankings. Use when selecting models for multi-model review, updating model recommendations, or researching current AI coding trends. Provides model IDs, context windows, pricing, and usage statistics from the most recent week.