# frontend-development > Activate this skill when working on: - Building or modifying React/Vue/Next.js applications - Setting up modern JavaScript/TypeScript build tooling - Implementing responsive UI components - Working with CSS frameworks (Tailwind, Bootstrap, custom) - Configuring Webpack, Vite, or other bundlers - Optimizing frontend build processes - Debugging frontend compilation or runtime issues - Implementing state management (Redux, Zustand, Context API) - Working with modern ES6+ JavaScript features - Author: eboncorp - Repository: eboncorp/claude-code-config - Version: 20260130180656 - Stars: 0 - Forks: 0 - Last Updated: 2026-02-08 - Source: https://github.com/eboncorp/claude-code-config - Web: https://mule.run/skillshub/@@eboncorp/claude-code-config~frontend-development:20260130180656 --- --- name: frontend-development description: Activate this skill when working on: Building or modifying React/Vue/Next.js applications Setting up modern JavaScript/TypeScript build tooling --- # Frontend Development Skill ## When to Use This Skill Activate this skill when working on: - Building or modifying React/Vue/Next.js applications - Setting up modern JavaScript/TypeScript build tooling - Implementing responsive UI components - Working with CSS frameworks (Tailwind, Bootstrap, custom) - Configuring Webpack, Vite, or other bundlers - Optimizing frontend build processes - Debugging frontend compilation or runtime issues - Implementing state management (Redux, Zustand, Context API) - Working with modern ES6+ JavaScript features ## Core Technologies Overview ### JavaScript/TypeScript Ecosystem **Modern JavaScript (ES6+)** ```javascript // Destructuring and spread operators const { title, content, ...metadata } = post; const updatedPost = { ...post, status: 'published' }; // Arrow functions with implicit returns const formatPrice = (amount) => `$${amount.toFixed(2)}`; // Template literals for string interpolation const message = `Welcome, ${user.name}! You have ${notifications.length} new notifications.`; // Optional chaining and nullish coalescing const userName = user?.profile?.name ?? 'Guest'; // Array methods for functional programming const publishedPosts = posts .filter(post => post.status === 'published') .map(post => ({ ...post, url: `/posts/${post.slug}` })) .sort((a, b) => new Date(b.date) - new Date(a.date)); // Async/await for cleaner promises async function fetchPolicyData(policyId) { try { const response = await fetch(`/api/policies/${policyId}`); if (!response.ok) throw new Error(`HTTP ${response.status}`); return await response.json(); } catch (error) { console.error('Failed to fetch policy:', error); return null; } } // Modules (import/export) // In utils/formatting.js export const formatDate = (date) => new Intl.DateTimeFormat('en-US').format(date); export const formatCurrency = (amount) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount); // In components/PolicyCard.js import { formatDate, formatCurrency } from '../utils/formatting.js'; ``` **TypeScript Fundamentals** ```typescript // Interface definitions for type safety interface Policy { id: number; title: string; content: string; category: 'public-safety' | 'education' | 'wellness'; published: boolean; metadata?: { author: string; lastModified: Date; }; } // Type aliases for complex types type ApiResponse = { data: T; status: 'success' | 'error'; message?: string; }; // Generic functions async function fetchData(url: string): Promise> { const response = await fetch(url); return response.json(); } // Usage with type inference const policies = await fetchData('/api/policies'); // Utility types for transformations type PartialPolicy = Partial; // All properties optional type PolicyPreview = Pick; // Subset type ReadonlyPolicy = Readonly; // Immutable version // Discriminated unions for state management type LoadingState = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: Policy[] } | { status: 'error'; error: string }; function renderPolicies(state: LoadingState) { switch (state.status) { case 'idle': return 'Click to load policies'; case 'loading': return 'Loading...'; case 'success': return state.data.map(p => p.title).join(', '); case 'error': return `Error: ${state.error}`; } } ``` ### React Development **Modern React Patterns** ```jsx // Functional components with hooks import { useState, useEffect, useCallback, useMemo } from 'react'; function PolicyManager() { // State management const [policies, setPolicies] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState('all'); // Data fetching with useEffect useEffect(() => { async function loadPolicies() { try { setLoading(true); const response = await fetch('/api/policies'); const data = await response.json(); setPolicies(data); } catch (error) { console.error('Failed to load policies:', error); } finally { setLoading(false); } } loadPolicies(); }, []); // Empty deps = run once on mount // Memoized expensive computations const filteredPolicies = useMemo(() => { if (filter === 'all') return policies; return policies.filter(p => p.category === filter); }, [policies, filter]); // Memoized callbacks to prevent re-renders const handleDelete = useCallback((policyId) => { setPolicies(prev => prev.filter(p => p.id !== policyId)); }, []); if (loading) return ; return (
); } // Custom hooks for reusable logic function useFetchPolicies() { const [state, setState] = useState({ data: [], loading: true, error: null }); useEffect(() => { let cancelled = false; async function fetchData() { try { const response = await fetch('/api/policies'); const data = await response.json(); if (!cancelled) { setState({ data, loading: false, error: null }); } } catch (error) { if (!cancelled) { setState({ data: [], loading: false, error: error.message }); } } } fetchData(); return () => { cancelled = true; // Cleanup to prevent state updates on unmount }; }, []); return state; } // Context API for global state import { createContext, useContext } from 'react'; const CampaignContext = createContext(null); export function CampaignProvider({ children }) { const [campaign, setCampaign] = useState({ budget: 81000000, policies: [], events: [] }); return ( {children} ); } export function useCampaign() { const context = useContext(CampaignContext); if (!context) { throw new Error('useCampaign must be used within CampaignProvider'); } return context; } // Component composition patterns function PolicyCard({ policy, onEdit, onDelete }) { return (
onEdit(policy.id)} onDelete={() => onDelete(policy.id)} />
); } // Higher-order component for auth protection function withAuth(Component) { return function AuthenticatedComponent(props) { const { user, loading } = useAuth(); if (loading) return ; if (!user) return ; return ; }; } const ProtectedPolicyEditor = withAuth(PolicyEditor); ``` ### Vue.js Development **Vue 3 Composition API** ```vue ``` **Composables (Vue's Custom Hooks)** ```javascript // composables/usePolicies.js import { ref, computed } from 'vue'; export function usePolicies() { const policies = ref([]); const loading = ref(false); const error = ref(null); async function fetchPolicies() { loading.value = true; error.value = null; try { const response = await fetch('/api/policies'); if (!response.ok) throw new Error(`HTTP ${response.status}`); policies.value = await response.json(); } catch (e) { error.value = e.message; } finally { loading.value = false; } } async function createPolicy(policyData) { const response = await fetch('/api/policies', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(policyData) }); const newPolicy = await response.json(); policies.value.push(newPolicy); return newPolicy; } async function updatePolicy(id, updates) { const response = await fetch(`/api/policies/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates) }); const updated = await response.json(); const index = policies.value.findIndex(p => p.id === id); if (index !== -1) { policies.value[index] = updated; } return updated; } async function deletePolicy(id) { await fetch(`/api/policies/${id}`, { method: 'DELETE' }); policies.value = policies.value.filter(p => p.id !== id); } const publishedPolicies = computed(() => policies.value.filter(p => p.published) ); return { policies, loading, error, fetchPolicies, createPolicy, updatePolicy, deletePolicy, publishedPolicies }; } ``` ### Next.js Development **App Router (Next.js 13+)** ```typescript // app/policies/page.tsx (Server Component by default) import { PolicyList } from '@/components/PolicyList'; async function getPolicies() { const res = await fetch('https://api.example.com/policies', { next: { revalidate: 3600 } // ISR: revalidate every hour }); return res.json(); } export default async function PoliciesPage() { const policies = await getPolicies(); return (

Campaign Policies

); } // app/policies/[id]/page.tsx (Dynamic route) export async function generateStaticParams() { const policies = await fetch('https://api.example.com/policies').then(r => r.json()); return policies.map((policy) => ({ id: policy.id.toString() })); } async function getPolicy(id: string) { const res = await fetch(`https://api.example.com/policies/${id}`, { next: { revalidate: 3600 } }); return res.json(); } export default async function PolicyPage({ params }: { params: { id: string } }) { const policy = await getPolicy(params.id); return (

{policy.title}

); } // app/api/policies/route.ts (API Route) import { NextResponse } from 'next/server'; export async function GET() { const policies = await fetchPoliciesFromDB(); return NextResponse.json(policies); } export async function POST(request: Request) { const body = await request.json(); const newPolicy = await createPolicy(body); return NextResponse.json(newPolicy, { status: 201 }); } // components/PolicyList.tsx (Client Component) 'use client'; import { useState } from 'react'; export function PolicyList({ policies }) { const [filter, setFilter] = useState('all'); const filtered = policies.filter(p => filter === 'all' || p.category === filter ); return (
{filtered.map(policy => ( ))}
); } ``` ### CSS and Styling **Modern CSS Patterns** ```css /* CSS Custom Properties (Variables) */ :root { --color-primary: #1e40af; --color-secondary: #7c3aed; --spacing-unit: 8px; --font-size-base: 16px; --border-radius: 4px; --transition-speed: 200ms; } /* Responsive design with container queries */ .policy-card { container-type: inline-size; container-name: policy-card; } @container policy-card (min-width: 400px) { .policy-card__content { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } } /* Modern layout with CSS Grid */ .policy-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: calc(var(--spacing-unit) * 2); padding: var(--spacing-unit); } /* Flexbox for component layout */ .policy-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; } /* Modern selectors */ .policy-list > * + * { margin-top: 1rem; /* Spacing between siblings */ } .policy-card:has(.urgent-badge) { border-left: 4px solid red; /* Style parent based on child */ } /* Smooth transitions and animations */ .policy-card { transition: transform var(--transition-speed) ease, box-shadow var(--transition-speed) ease; } .policy-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } /* Dark mode support */ @media (prefers-color-scheme: dark) { :root { --color-primary: #60a5fa; --color-secondary: #a78bfa; } } ``` **Tailwind CSS** ```jsx // Installation: npm install -D tailwindcss postcss autoprefixer // Setup: npx tailwindcss init -p // tailwind.config.js module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: { colors: { campaign: { blue: '#1e40af', purple: '#7c3aed', }, }, spacing: { '128': '32rem', }, }, }, plugins: [], }; // Usage in components function PolicyCard({ policy }) { return (

{policy.title}

{policy.excerpt}

{policy.category}
); } ``` ### Build Tools **Vite Configuration** ```javascript // vite.config.js import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@utils': path.resolve(__dirname, './src/utils'), }, }, server: { port: 3000, proxy: { '/api': { target: 'http://rundaverun-local-complete-022655.local', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '/wp-json/wp/v2'), }, }, }, build: { outDir: 'dist', sourcemap: true, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], utils: ['lodash', 'date-fns'], }, }, }, }, css: { preprocessorOptions: { scss: { additionalData: `@import "@/styles/variables.scss";`, }, }, }, }); ``` **Webpack Configuration** ```javascript // webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js', clean: true, }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'], }, }, }, { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'], }, { test: /\.(png|svg|jpg|jpeg|gif)$/i, type: 'asset/resource', }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', }), new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', }), ], optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', priority: 10, }, }, }, }, devServer: { static: './dist', port: 3000, hot: true, }, }; ``` ## Common Patterns and Anti-Patterns ### React Best Practices **Good Patterns:** ```jsx // ✅ Separate concerns with custom hooks function usePolicyForm(initialPolicy) { const [policy, setPolicy] = useState(initialPolicy); const [errors, setErrors] = useState({}); const validate = useCallback(() => { const newErrors = {}; if (!policy.title) newErrors.title = 'Title required'; if (!policy.content) newErrors.content = 'Content required'; setErrors(newErrors); return Object.keys(newErrors).length === 0; }, [policy]); return { policy, setPolicy, errors, validate }; } // ✅ Controlled components for forms function PolicyForm({ onSubmit }) { const { policy, setPolicy, errors, validate } = usePolicyForm({ title: '', content: '', category: 'public-safety' }); const handleSubmit = (e) => { e.preventDefault(); if (validate()) { onSubmit(policy); } }; return (
setPolicy(prev => ({ ...prev, title: e.target.value }))} /> {errors.title && {errors.title}}
); } // ✅ Proper key usage in lists function PolicyList({ policies }) { return policies.map(policy => ( )); } ``` **Anti-Patterns:** ```jsx // ❌ Mutating state directly function BadComponent() { const [items, setItems] = useState([1, 2, 3]); const addItem = () => { items.push(4); // WRONG - mutates state setItems(items); // Won't trigger re-render }; // ✅ Correct: Create new array const addItemCorrectly = () => { setItems([...items, 4]); }; } // ❌ Using index as key function BadList({ items }) { return items.map((item, index) => (
{item.name}
// WRONG - breaks on reorder )); } // ❌ Side effects in render function BadComponent() { const [count, setCount] = useState(0); setCount(count + 1); // WRONG - infinite loop // ✅ Use useEffect for side effects useEffect(() => { setCount(count + 1); }, []); } // ❌ Prop drilling through many levels // Instead use Context API or state management library ``` ## Troubleshooting Common Issues ### Build Errors **"Module not found" errors:** ```bash # Check import path exists ls -la src/components/PolicyCard.jsx # Verify path alias in vite.config.js or tsconfig.json # Example fix in vite.config.js: resolve: { alias: { '@': path.resolve(__dirname, './src'), } } # Clear cache and reinstall rm -rf node_modules package-lock.json npm install ``` **TypeScript type errors:** ```bash # Generate types from node_modules npm run type-check # Check tsconfig.json includes all source files cat tsconfig.json | grep include # Install missing type definitions npm install --save-dev @types/react @types/node ``` ### Runtime Errors **React hooks errors:** ```javascript // Error: "Rendered more hooks than during previous render" // Cause: Conditional hooks // ❌ WRONG function BadComponent({ condition }) { if (condition) { const [value, setValue] = useState(0); // Conditional hook } } // ✅ CORRECT function GoodComponent({ condition }) { const [value, setValue] = useState(0); if (condition) { // Use the hook result conditionally, not the hook itself } } ``` **Memory leaks:** ```javascript // Error: "Can't perform a React state update on an unmounted component" // ✅ Solution: Cleanup in useEffect useEffect(() => { let cancelled = false; async function fetchData() { const data = await fetch('/api/policies'); if (!cancelled) { setData(data); } } fetchData(); return () => { cancelled = true; // Cleanup flag }; }, []); ``` ### Performance Issues **Identifying slow renders:** ```javascript // Use React DevTools Profiler // Or add manual profiling: import { Profiler } from 'react'; function onRenderCallback( id, // component identifier phase, // "mount" or "update" actualDuration, // time spent rendering ) { console.log(`${id} (${phase}) took ${actualDuration}ms`); } ``` **Optimizing re-renders:** ```javascript // Use React.memo for expensive components const PolicyCard = React.memo(({ policy }) => { return
{policy.title}
; }, (prevProps, nextProps) => { // Custom comparison return prevProps.policy.id === nextProps.policy.id; }); // Use useMemo for expensive computations const sortedPolicies = useMemo(() => { return policies.sort((a, b) => a.title.localeCompare(b.title)); }, [policies]); // Use useCallback to prevent function recreation const handleClick = useCallback(() => { console.log('Clicked'); }, []); // Deps array determines when to recreate ``` ## Integration with Other Skills ### With WordPress (Campaign Site) ```javascript // Custom WordPress REST API client class WordPressAPI { constructor(baseUrl) { this.baseUrl = baseUrl; } async getPolicies() { const response = await fetch(`${this.baseUrl}/wp-json/wp/v2/posts?categories=5`); return response.json(); } async getPolicy(id) { const response = await fetch(`${this.baseUrl}/wp-json/wp/v2/posts/${id}`); return response.json(); } async createPolicy(policy, authToken) { const response = await fetch(`${this.baseUrl}/wp-json/wp/v2/posts`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ title: policy.title, content: policy.content, status: 'draft' }) }); return response.json(); } } // React component using WordPress API function WordPressPolicies() { const [policies, setPolicies] = useState([]); const api = new WordPressAPI('http://rundaverun-local-complete-022655.local'); useEffect(() => { api.getPolicies().then(setPolicies); }, []); return ; } ``` ### With CI/CD Pipelines ```yaml # .github/workflows/frontend-build.yml name: Frontend Build and Deploy on: push: branches: [main] paths: - 'frontend/**' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '18' cache: 'npm' - name: Install dependencies run: npm ci - name: Run type check run: npm run type-check - name: Run linter run: npm run lint - name: Run tests run: npm run test:ci - name: Build production bundle run: npm run build env: NODE_ENV: production - name: Deploy to staging run: npm run deploy:staging ``` ## Quick Reference ### Essential Commands ```bash # Create new React app npx create-react-app my-app npx create-react-app my-app --template typescript # Create new Next.js app npx create-next-app@latest my-app npx create-next-app@latest my-app --typescript # Create new Vue app npm create vue@latest npm create vite@latest my-app -- --template vue # Install dependencies npm install react react-dom npm install -D typescript @types/react @types/react-dom # Development npm run dev npm run build npm run preview # Testing npm test npm run test:coverage # Linting npm run lint npm run lint:fix ``` ### Key File Locations ``` /home/dave/skippy/frontend/ # Frontend projects /home/dave/skippy/scripts/frontend/ # Frontend build scripts /home/dave/.config/vite/ # Vite config /home/dave/.config/webpack/ # Webpack config ``` ### Environment Variables ```bash # .env.local (Next.js, Vite) NEXT_PUBLIC_API_URL=http://rundaverun-local-complete-022655.local NEXT_PUBLIC_WP_API=http://rundaverun-local-complete-022655.local/wp-json/wp/v2 # Access in code const apiUrl = process.env.NEXT_PUBLIC_API_URL; ``` ### Common Package.json Scripts ```json { "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", "test": "vitest", "test:ui": "vitest --ui", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "type-check": "tsc --noEmit", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,md}\"" } } ``` ## WordPress-Specific Frontend Patterns ### Gutenberg Block Development ```jsx // Custom Gutenberg block for policy display import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps, InspectorControls } from '@wordpress/block-editor'; import { PanelBody, SelectControl } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; registerBlockType('campaign/policy-display', { title: 'Policy Display', category: 'widgets', attributes: { policyId: { type: 'number', default: 0 } }, edit: ({ attributes, setAttributes }) => { const blockProps = useBlockProps(); const policies = useSelect((select) => { return select('core').getEntityRecords('postType', 'policy'); }, []); return (
({ label: p.title.rendered, value: p.id }))} onChange={(policyId) => setAttributes({ policyId: parseInt(policyId) })} />
{attributes.policyId ? ( ) : (

Select a policy from the sidebar

)}
); }, save: ({ attributes }) => { return (
); } }); ``` ### Headless WordPress with React ```jsx // Complete headless WordPress setup import { useEffect, useState } from 'react'; const WP_API = 'http://rundaverun-local-complete-022655.local/wp-json/wp/v2'; function useWordPressPosts(category = null) { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchPosts() { try { let url = `${WP_API}/posts?_embed`; if (category) url += `&categories=${category}`; const response = await fetch(url); const data = await response.json(); setPosts(data); } catch (error) { console.error('Failed to fetch posts:', error); } finally { setLoading(false); } } fetchPosts(); }, [category]); return { posts, loading }; } function CampaignBlog() { const { posts, loading } = useWordPressPosts(5); // Category ID 5 = Policies if (loading) return
Loading...
; return (
{posts.map(post => (
{post._embedded?.['wp:featuredmedia']?.[0] && ( {post.title.rendered} )}

))}
); } ``` --- **Last Updated:** November 2025 **Skill Version:** 1.0.0