NYC
skills/smithery/ai/frontend-developer

frontend-developer

SKILL.md

Frontend Developer Expert

You are a frontend development expert specializing in modern JavaScript frameworks, responsive design, and user experience optimization.

Frontend Frameworks Comparison

React

// Modern React with Hooks
import React, { useState, useEffect, useContext, useCallback } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';

// Custom Hook for data fetching
const useUserProfile = (userId) => {
  const queryClient = new QueryClient();

  return useQuery(
    ['user', userId],
    async () => {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) throw new Error('Failed to fetch user');
      return response.json();
    },
    {
      enabled: !!userId,
      staleTime: 5 * 60 * 1000, // 5 minutes
      cacheTime: 10 * 60 * 1000, // 10 minutes
    }
  );
};

// Context for global state
const ThemeContext = React.createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = useCallback(() => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  }, []);

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

// Component using hooks
const UserProfile = ({ userId }) => {
  const { data: user, isLoading, error, refetch } = useUserProfile(userId);
  const { theme } = useContext(ThemeContext);
  const navigate = useNavigate();

  useEffect(() => {
    document.title = user ? `${user.name} - Profile` : 'Loading...';
  }, [user]);

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;

  return (
    <div className={`profile ${theme}`}>
      <img src={user.avatar} alt={user.name} />
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <button onClick={() => navigate('/edit')}>Edit Profile</button>
      <button onClick={() => refetch()}>Refresh</button>
    </div>
  );
};

// App component
const App = () => {
  const queryClient = new QueryClient();

  return (
    <QueryClientProvider client={queryClient}>
      <ThemeProvider>
        <BrowserRouter>
          <Routes>
            <Route path="/users/:id" element={<UserProfile />} />
            <Route path="/" element={<Home />} />
          </Routes>
        </BrowserRouter>
      </ThemeProvider>
    </QueryClientProvider>
  );
};

Vue 3 (Composition API)

// Vue 3 with Composition API
import { ref, computed, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import axios from 'axios';

export default {
  name: 'UserProfile',
  props: {
    userId: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const router = useRouter();
    const store = useStore();

    // Reactive state
    const user = ref(null);
    const loading = ref(false);
    const error = ref(null);

    // Computed properties
    const displayName = computed(() => {
      return user.value ? `${user.value.firstName} ${user.value.lastName}` : 'Guest';
    });

    const isAdmin = computed(() => {
      return user.value?.role === 'admin';
    });

    // Methods
    const fetchUser = async () => {
      loading.value = true;
      error.value = null;

      try {
        const response = await axios.get(`/api/users/${props.userId}`);
        user.value = response.data;
      } catch (err) {
        error.value = err.message;
      } finally {
        loading.value = false;
      }
    };

    const updateUser = async (updates) => {
      try {
        const response = await axios.put(`/api/users/${props.userId}`, updates);
        user.value = response.data;
        store.dispatch('showNotification', {
          type: 'success',
          message: 'Profile updated successfully'
        });
      } catch (err) {
        store.dispatch('showNotification', {
          type: 'error',
          message: 'Failed to update profile'
        });
      }
    };

    // Lifecycle hooks
    onMounted(() => {
      fetchUser();
    });

    // Watchers
    watch(() => props.userId, (newId, oldId) => {
      if (newId !== oldId) {
        fetchUser();
      }
    });

    return {
      user,
      loading,
      error,
      displayName,
      isAdmin,
      fetchUser,
      updateUser
    };
  }
};

// Template
/*
<template>
  <div class="user-profile">
    <div v-if="loading">Loading...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <div v-else-if="user">
      <h1>{{ displayName }}</h1>
      <p>{{ user.email }}</p>
      <button v-if="isAdmin" @click="editProfile">Edit</button>
      <button @click="fetchUser">Refresh</button>
    </div>
  </div>
</template>
*/

Angular

// Angular service
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, throwError, BehaviorSubject } from 'rxjs';
import { catchError, tap, shareReplay } from 'rxjs/operators';
import { User } from './user.model';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  private apiUrl = 'https://api.example.com/users';

  // State management with BehaviorSubject
  private currentUser$ = new BehaviorSubject<User | null>(null);
  public currentUser = this.currentUser$.asObservable();

  constructor(private http: HttpClient) {}

  getUsers(filters?: any): Observable<User[]> {
    let params = new HttpParams();

    if (filters) {
      if (filters.page) params = params.append('page', filters.page.toString());
      if (filters.limit) params = params.append('limit', filters.limit.toString());
      if (filters.search) params = params.append('search', filters.search);
    }

    return this.http.get<User[]>(this.apiUrl, { params })
      .pipe(
        tap(users => console.log(`Fetched ${users.length} users`)),
        catchError(this.handleError)
      );
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`)
      .pipe(
        tap(user => this.currentUser$.next(user)),
        shareReplay(1), // Cache the result
        catchError(this.handleError)
      );
  }

  createUser(user: User): Observable<User> {
    return this.http.post<User>(this.apiUrl, user)
      .pipe(
        tap(user => this.currentUser$.next(user)),
        catchError(this.handleError)
      );
  }

  updateUser(user: User): Observable<User> {
    return this.http.put<User>(`${this.apiUrl}/${user.id}`, user)
      .pipe(
        tap(updatedUser => this.currentUser$.next(updatedUser)),
        catchError(this.handleError)
      );
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`)
      .pipe(
        tap(() => this.currentUser$.next(null)),
        catchError(this.handleError)
      );
  }

  private handleError(error: any) {
    console.error('An error occurred:', error);
    return throwError(() => error);
  }
}

// Angular component
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { UserService } from './user.service';
import { User } from './user.model';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
  selector: 'app-user-profile',
  template: `
    <div class="user-profile" *ngIf="user">
      <h1>{{ user.firstName }} {{ user.lastName }}</h1>
      <p>{{ user.email }}</p>
      <button (click)="editProfile()">Edit Profile</button>
      <button (click)="deleteUser()">Delete</button>
    </div>
  `,
  styles: [`
    .user-profile {
      padding: 20px;
      border: 1px solid #ccc;
      border-radius: 8px;
    }
  `]
})
export class UserProfileComponent implements OnInit, OnDestroy {
  user: User | null = null;
  private destroy$ = new Subject<void>();

  constructor(
    private route: ActivatedRoute,
    private router: Router,
    private userService: UserService
  ) {}

  ngOnInit(): void {
    const userId = this.route.snapshot.paramMap.get('id');

    this.userService.getUser(Number(userId))
      .pipe(takeUntil(this.destroy$))
      .subscribe({
        next: (user) => {
          this.user = user;
        },
        error: (error) => {
          console.error('Failed to load user:', error);
        }
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  editProfile(): void {
    this.router.navigate(['/users', this.user?.id, 'edit']);
  }

  deleteUser(): void {
    if (this.user && confirm('Are you sure you want to delete this user?')) {
      this.userService.deleteUser(this.user.id)
        .pipe(takeUntil(this.destroy$))
        .subscribe({
          next: () => {
            this.router.navigate(['/users']);
          },
          error: (error) => {
            console.error('Failed to delete user:', error);
          }
        });
    }
  }
}

State Management

Redux Toolkit (React)

import { createSlice, configureStore } from '@reduxjs/toolkit';

// User slice
const userSlice = createSlice({
  name: 'user',
  initialState: {
    user: null,
    loading: false,
    error: null
  },
  reducers: {
    setUser: (state, action) => {
      state.user = action.payload;
    },
    setLoading: (state, action) => {
      state.loading = action.payload;
    },
    setError: (state, action) => {
      state.error = action.payload;
    },
    clearUser: (state) => {
      state.user = null;
    }
  }
});

export const { setUser, setLoading, setError, clearUser } = userSlice.actions;

// Async thunks
export const fetchUser = (userId) => async (dispatch) => {
  dispatch(setLoading(true));

  try {
    const response = await fetch(`/api/users/${userId}`);
    const data = await response.json();
    dispatch(setUser(data));
    dispatch(setError(null));
  } catch (error) {
    dispatch(setError(error.message));
  } finally {
    dispatch(setLoading(false));
  }
};

// Store configuration
const store = configureStore({
  reducer: {
    user: userSlice.reducer,
    // other reducers...
  }
});

export default store;

Pinia (Vue)

// User store
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useUserStore = defineStore('user', () => {
  // State
  const user = ref(null);
  const loading = ref(false);
  const error = ref(null);

  // Getters
  const isAuthenticated = computed(() => !!user.value);
  const isAdmin = computed(() => user.value?.role === 'admin');

  // Actions
  async function fetchUser(userId) {
    loading.value = true;
    error.value = null;

    try {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      user.value = data;
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  }

  async function updateUser(updates) {
    loading.value = true;
    error.value = null;

    try {
      const response = await fetch(`/api/users/${user.value.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(updates)
      });
      const data = await response.json();
      user.value = data;
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  }

  function clearUser() {
    user.value = null;
    error.value = null;
  }

  return {
    // State
    user,
    loading,
    error,
    // Getters
    isAuthenticated,
    isAdmin,
    // Actions
    fetchUser,
    updateUser,
    clearUser
  };
});

NgRx (Angular)

// Actions
import { createAction, props } from '@ngrx/store';

export const loadUser = createAction(
  '[User] Load User',
  props<{ userId: number }>()
);

export const loadUserSuccess = createAction(
  '[User] Load User Success',
  props<{ user: User }>()
);

export const loadUserFailure = createAction(
  '[User] Load User Failure',
  props<{ error: string }>()
);

// Reducer
import { createReducer, on } from '@ngrx/store';

export interface UserState {
  user: User | null;
  loading: boolean;
  error: string | null;
}

export const initialState: UserState = {
  user: null,
  loading: false,
  error: null
};

export const userReducer = createReducer(
  initialState,
  on(loadUser, (state) => ({
    ...state,
    loading: true,
    error: null
  })),
  on(loadUserSuccess, (state, { user }) => ({
    ...state,
    user,
    loading: false
  })),
  on(loadUserFailure, (state, { error }) => ({
    ...state,
    error,
    loading: false
  }))
);

// Effects
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { UserService } from './user.service';
import * as UserActions from './user.actions';

@Injectable()
export class UserEffects {
  loadUser$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.loadUser),
      mergeMap((action) =>
        this.userService.getUser(action.userId).pipe(
          map((user) => UserActions.loadUserSuccess({ user })),
          catchError((error) =>
            of(UserActions.loadUserFailure({ error: error.message }))
          )
        )
      )
    )
  );

  constructor(
    private actions$: Actions,
    private userService: UserService
  ) {}
}

Styling & CSS

CSS-in-JS (Styled Components)

import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.primary ? '#007bff' : '#6c757d'};
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.3s ease;

  &:hover {
    background: ${props => props.primary ? '#0056b3' : '#545b62'};
    transform: translateY(-2px);
  }

  &:active {
    transform: translateY(0);
  }

  &:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
`;

const Card = styled.div`
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  margin: 10px;

  @media (max-width: 768px) {
    padding: 15px;
    margin: 5px;
  }
`;

// Usage
const UserProfile = ({ user }) => (
  <Card>
    <h1>{user.name}</h1>
    <p>{user.email}</p>
    <Button primary>Edit Profile</Button>
  </Card>
);

Tailwind CSS

<!-- Utility-first CSS framework -->
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
  <div class="md:flex">
    <div class="md:shrink-0">
      <img class="h-48 w-full object-cover md:h-full md:w-48"
           src="/img/store.jpg"
           alt="Store">
    </div>
    <div class="p-8">
      <div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
        Case study
      </div>
      <a href="#" class="block mt-1 text-lg leading-tight font-medium text-black hover:underline">
        Integrating with Tailwind CSS
      </a>
      <p class="mt-2 text-slate-500">
        Getting a new project off the ground is always a challenge. We've made it easier with pre-built components and examples.
      </p>
    </div>
  </div>
</div>

<!-- Responsive design with Tailwind -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <div class="bg-white p-6 rounded-lg shadow">Item 1</div>
  <div class="bg-white p-6 rounded-lg shadow">Item 2</div>
  <div class="bg-white p-6 rounded-lg shadow">Item 3</div>
</div>

<!-- Dark mode -->
<div class="bg-white dark:bg-gray-800 text-gray-900 dark:text-white">
  <h1 class="text-2xl font-bold">Dark Mode Support</h1>
</div>

CSS Modules

/* UserProfile.module.css */
.profileContainer {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 20px;
  border-radius: 8px;
  background: #f5f5f5;
}

.avatar {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  object-fit: cover;
}

.userInfo {
  flex: 1;
}

.userName {
  font-size: 24px;
  font-weight: bold;
  margin-bottom: 8px;
}

.userEmail {
  color: #666;
  font-size: 14px;
}

.editButton {
  padding: 10px 20px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.editButton:hover {
  background: #0056b3;
}
import styles from './UserProfile.module.css';

const UserProfile = ({ user }) => (
  <div className={styles.profileContainer}>
    <img src={user.avatar} alt={user.name} className={styles.avatar} />
    <div className={styles.userInfo}>
      <h1 className={styles.userName}>{user.name}</h1>
      <p className={styles.userEmail}>{user.email}</p>
      <button className={styles.editButton}>Edit Profile</button>
    </div>
  </div>
);

Performance Optimization

Code Splitting

// React - Lazy loading components
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Memoization

import { memo, useMemo, useCallback } from 'react';

// Memo component to prevent unnecessary re-renders
const ExpensiveComponent = memo(({ data, onAction }) => {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      value: item.value * 2
    }));
  }, [data]);

  const handleClick = useCallback((id) => {
    onAction(id);
  }, [onAction]);

  return (
    <div>
      {processedData.map(item => (
        <div key={item.id} onClick={() => handleClick(item.id)}>
          {item.value}
        </div>
      ))}
    </div>
  );
});

Virtual Scrolling

import { FixedSizeList } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>
    Row {index}
  </div>

);

const VirtualList = ({ items }) => (
  <FixedSizeList
    height={600}
    itemCount={items.length}
    itemSize={50}
    width="100%"
  >
    {Row}
  </FixedSizeList>
);

Testing

Unit Testing (Jest + React Testing Library)

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserProfile from './UserProfile';

describe('UserProfile', () => {
  const mockUser = {
    id: 1,
    name: 'John Doe',
    email: 'john@example.com',
    avatar: 'avatar.jpg'
  };

  test('renders user information', async () => {
    render(<UserProfile userId={1} />);

    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument();
      expect(screen.getByText('john@example.com')).toBeInTheDocument();
    });
  });

  test('handles edit button click', async () => {
    const user = userEvent.setup();
    render(<UserProfile userId={1} />);

    await waitFor(() => {
      const editButton = screen.getByText('Edit Profile');
      expect(editButton).toBeInTheDocument();
    });

    await user.click(screen.getByText('Edit Profile'));

    // Assert navigation or state change
  });

  test('shows loading state', () => {
    render(<UserProfile userId={1} />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });

  test('handles error state', async () => {
    // Mock failed API call
    jest.spyOn(global, 'fetch').mockRejectedValueOnce(
      new Error('Failed to fetch')
    );

    render(<UserProfile userId={1} />);

    await waitFor(() => {
      expect(screen.getByText(/error/i)).toBeInTheDocument();
    });
  });
});

Best Practices

Component Design

✅ DO:
  - Keep components small and focused
  - Use composition over inheritance
  - Follow single responsibility principle
  - Use functional components with hooks
  - Implement proper TypeScript types
  - Write reusable components
  - Use proper prop validation
  - Handle loading and error states
  - Make components accessible
  - Test components thoroughly

❌ DON'T:
  - Create huge monolithic components
  - Mix business logic with UI
  - Ignore TypeScript errors
  - Skip error handling
  - Forget about accessibility
  - Write tightly coupled code
  - Ignore performance

Performance

✅ DO:
  - Use code splitting
  - Implement lazy loading
  - Optimize images
  - Use memoization wisely
  - Implement virtual scrolling for long lists
  - Minimize bundle size
  - Use production builds
  - Implement caching strategies
  - Optimize re-renders

❌ DON'T:
  - Bundle everything together
  - Load unnecessary dependencies
  - Ignore bundle size
  - Over-memoize (premature optimization)
  - Render large lists without virtualization
  - Use large images unoptimized

Tools & Resources

Build Tools

  • Vite: Fast build tool
  • Webpack: Module bundler
  • Rollup: Library bundler
  • esbuild: Extremely fast bundler
  • Turbopack: Next.js bundler

Development Tools

  • ESLint: Linting
  • Prettier: Code formatting
  • TypeScript: Type safety
  • Jest: Testing framework
  • Cypress: E2E testing

Documentation

Weekly Installs
1
Repository
smithery/ai
First Seen
Feb 5, 2026
Installed on
claude-code1