Claude Code Plugins

Community-maintained marketplace

Feedback

performance-security

@MadAppGang/claude-code
0
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name performance-security
description 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.

Performance, Accessibility & Security

Production-ready patterns for building fast, accessible, and secure React applications.

Performance Optimization

Code-Splitting

Automatic with TanStack Router:

  • File-based routing automatically code-splits by route
  • Each route is its own chunk
  • Vite handles dynamic imports efficiently

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,
})

React Compiler First

The React Compiler automatically optimizes performance when you write compiler-friendly code:

✅ Do:

  • Keep components pure (no side effects in render)
  • Derive values during render (don't stash in refs)
  • Keep props serializable
  • Inline event handlers (unless they close over large objects)

❌ Avoid:

  • Mutating props or state
  • Side effects in render phase
  • Over-using useCallback/useMemo (compiler handles this)
  • Non-serializable props (functions, symbols)

Verify optimization:

  • Check React DevTools for "Memo ✨" badge
  • Components without badge weren't optimized (check for violations)

Images & Assets

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"
/>

Bundle Analysis

# 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 }),
  ],
})

Performance Checklist

  • Code-split routes and heavy components
  • Verify React Compiler optimizations (✨ badges)
  • Optimize images (WebP, lazy loading, responsive)
  • Prefetch critical data in route loaders
  • Use TanStack Query for automatic deduplication
  • Set appropriate staleTime per query
  • Minimize bundle size (check with visualizer)
  • Enable compression (gzip/brotli on server)

Accessibility (a11y)

Semantic HTML

✅ 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>

ARIA When Needed

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>

Keyboard Navigation

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
}

Testing with React Testing Library

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)

Color Contrast

  • Ensure 4.5:1 contrast ratio for normal text
  • Ensure 3:1 contrast ratio for large text (18pt+)
  • Don't rely on color alone for meaning
  • Test with browser DevTools accessibility panel

Accessibility Checklist

  • Use semantic HTML elements
  • Add alt text to all images
  • Ensure keyboard navigation works
  • Provide focus indicators
  • Test with screen reader (NVDA/JAWS/VoiceOver)
  • Verify color contrast meets WCAG AA
  • Use React Testing Library accessible queries
  • Add skip links for main content
  • Ensure form inputs have labels

Security

Never Ship Secrets

❌ 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

Validate All Untrusted Data

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)
  }
}

XSS Prevention

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)
}} />

Content Security Policy

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;
";

Dependency Security

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 }
}

CI Security

Run with --ignore-scripts:

# Prevents malicious post-install scripts
pnpm install --ignore-scripts

Scan for secrets:

# Add to CI
git-secrets --scan

Security Checklist

  • Never commit secrets or API keys
  • Only expose VITE_* env vars to client
  • Validate all API responses with Zod
  • Sanitize user-generated HTML (if needed)
  • Set Content Security Policy headers
  • Pin dependency versions
  • Run pnpm audit regularly
  • Enable Renovate/Dependabot
  • Use --ignore-scripts in CI
  • Implement proper authentication flow

Related Skills

  • core-principles - Project structure and standards
  • react-patterns - Compiler-friendly code
  • tanstack-query - Performance via caching and deduplication
  • tooling-setup - TypeScript strict mode for type safety