plugins/frontend/skills/performance-security/SKILL.md
Performance optimization, accessibility, and security best practices for React apps. Covers code-splitting, React Compiler patterns, asset optimization, a11y testing, and security hardening. Use when optimizing performance or reviewing security.
npx skillsauth add involvex/involvex-claude-marketplace 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 CIdevelopment
Technical SEO audit methodology including crawlability, indexability, and Core Web Vitals analysis. Use when auditing pages or sites for technical SEO issues.
content-media
SERP analysis techniques for intent classification, feature identification, and competitive intelligence. Use when analyzing search results for content strategy.
data-ai
Schema.org markup implementation patterns for rich results. Use when adding structured data to content for enhanced SERP appearances.
development
Correlate content attributes with performance metrics across GA4, GSC, and SE Ranking. Identify what drives performance and build optimization hypotheses.