askill
frontend-architecture

frontend-architectureSafety 95Repository

Component Architecture Patterns, State Management Strategien, Data Flow Design fuer React/Next.js/Vue Projekte.

0 stars
1.2k downloads
Updated 2/16/2026

Package Files

Loading files...
SKILL.md

Frontend Architecture Skill

Systematische Anleitung fuer skalierbare Frontend-Architekturen. Component Hierarchien, State Management Entscheidungen, Data Fetching Patterns und Performance-Optimierung.

Component Hierarchy Patterns

Container/Presentational Pattern

Trennung von Logik und Darstellung. Container kuemmern sich um Daten und State, Presentational Components sind rein visuell.

// Container: Logik + Daten
function DealListContainer() {
  const { data, isLoading, error } = useDeals();
  const [filter, setFilter] = useState<DealFilter>({});

  if (isLoading) return <DealListSkeleton />;
  if (error) return <ErrorBanner message={error.message} />;

  return <DealList deals={data} filter={filter} onFilterChange={setFilter} />;
}

// Presentational: Nur Darstellung, vollstaendig ueber Props gesteuert
interface DealListProps {
  deals: Deal[];
  filter: DealFilter;
  onFilterChange: (filter: DealFilter) => void;
}

function DealList({ deals, filter, onFilterChange }: DealListProps) {
  return (
    <div>
      <FilterBar value={filter} onChange={onFilterChange} />
      {deals.map(deal => <DealCard key={deal.id} deal={deal} />)}
    </div>
  );
}

Compound Components Pattern

Zusammengehoerige Components, die sich einen impliziten State teilen. Ideal fuer flexible APIs (Tabs, Accordions, Dropdowns).

// API: <Select> <Select.Trigger /> <Select.Options> <Select.Option /> </Select.Options> </Select>
const SelectContext = createContext<SelectContextValue | null>(null);

function Select({ children, value, onChange }: SelectProps) {
  const [open, setOpen] = useState(false);
  return (
    <SelectContext.Provider value={{ open, setOpen, value, onChange }}>
      <div className="relative">{children}</div>
    </SelectContext.Provider>
  );
}

Select.Trigger = function SelectTrigger({ children }: { children: ReactNode }) {
  const ctx = useSelectContext();
  return <button onClick={() => ctx.setOpen(!ctx.open)}>{children}</button>;
};

Select.Option = function SelectOption({ value, children }: OptionProps) {
  const ctx = useSelectContext();
  return (
    <li
      role="option"
      aria-selected={ctx.value === value}
      onClick={() => { ctx.onChange(value); ctx.setOpen(false); }}
    >
      {children}
    </li>
  );
};

Render Props / Headless Components

Logik ohne UI. Der Consumer entscheidet ueber die Darstellung.

function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn(prev => !prev), []);
  const setTrue = useCallback(() => setOn(true), []);
  const setFalse = useCallback(() => setOn(false), []);
  return { on, toggle, setTrue, setFalse } as const;
}

// Headless Disclosure
function Disclosure({ children }: { children: (state: DisclosureState) => ReactNode }) {
  const state = useToggle();
  return <>{children(state)}</>;
}

State Management Decision Tree

Braucht nur 1 Component den State?
  -> YES: useState / useReducer (lokal)

Brauchen Parent + wenige Children den State?
  -> YES: Props Drilling (max 2-3 Ebenen)

Braucht ein Subtree den State?
  -> YES: React Context + useReducer

Braucht die ganze App den State?
  -> YES: Zustand / Jotai / Redux Toolkit

Kommt der State vom Server?
  -> YES: React Query / SWR / Server Components (KEIN globaler Store!)

Regeln

RegelErklaerung
Server State != Client StateAPI-Daten gehoeren in React Query, nicht in Zustand
Context ist kein State ManagerContext fuer seltene Updates (Theme, Auth, Locale)
Kein Global Store fuer FormsFormular-State bleibt lokal (react-hook-form)
Derived State berechnen, nicht speichernuseMemo statt separatem State
// FALSCH: Server State im globalen Store
const useStore = create((set) => ({
  deals: [],
  fetchDeals: async () => {
    const deals = await api.getDeals();
    set({ deals }); // Manuelles Cache Management - fehleranfaellig
  },
}));

// RICHTIG: React Query fuer Server State
function useDeals(filter?: DealFilter) {
  return useQuery({
    queryKey: ['deals', filter],
    queryFn: () => api.getDeals(filter),
    placeholderData: [],
    staleTime: 30_000,
  });
}

Data Fetching Patterns

React Query / TanStack Query

// Query mit abhaengigem Query
function useDealWithContacts(dealId: string) {
  const dealQuery = useQuery({
    queryKey: ['deals', dealId],
    queryFn: () => api.getDeal(dealId),
  });

  const contactsQuery = useQuery({
    queryKey: ['deals', dealId, 'contacts'],
    queryFn: () => api.getDealContacts(dealId),
    enabled: !!dealQuery.data, // Nur fetchen wenn Deal geladen
  });

  return { deal: dealQuery, contacts: contactsQuery };
}

Optimistic Updates

function useUpdateDealStage() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ dealId, stage }: { dealId: string; stage: string }) =>
      api.updateDeal(dealId, { stage }),
    onMutate: async ({ dealId, stage }) => {
      await queryClient.cancelQueries({ queryKey: ['deals'] });
      const previous = queryClient.getQueryData<Deal[]>(['deals']);

      queryClient.setQueryData<Deal[]>(['deals'], old =>
        (old ?? []).map(d => d.id === dealId ? { ...d, stage } : d)
      );

      return { previous };
    },
    onError: (_err, _vars, context) => {
      if (context?.previous) {
        queryClient.setQueryData(['deals'], context.previous);
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['deals'] });
    },
  });
}

Server Components (Next.js App Router)

// Server Component: Daten direkt laden, kein Client-Bundle
async function DealsPage({ searchParams }: { searchParams: Promise<{ stage?: string }> }) {
  const params = await searchParams;
  const deals = await getDeals({ stage: params.stage });

  return (
    <main>
      <Suspense fallback={<DealListSkeleton />}>
        <DealList deals={deals} />
      </Suspense>
      {/* Client Component fuer Interaktivitaet */}
      <DealFilterSidebar />
    </main>
  );
}

File Organisation

Feature-basiert (empfohlen)

src/
  features/
    deals/
      components/
        DealCard.tsx
        DealList.tsx
        DealForm/
          DealForm.tsx
          DealFormFields.tsx
          DealForm.test.tsx
      hooks/
        useDeals.ts
        useDealMutation.ts
      api/
        deals.api.ts
        deals.types.ts
      utils/
        deal-calculations.ts
      index.ts          # Public API des Features
    contacts/
      ...
  shared/
    components/         # App-weite UI Components (Button, Modal, ...)
    hooks/              # App-weite Hooks (useDebounce, useMediaQuery)
    lib/                # Utilities (formatCurrency, date helpers)

Barrel Exports

// features/deals/index.ts - Public API
export { DealCard } from './components/DealCard';
export { DealList } from './components/DealList';
export { useDeals } from './hooks/useDeals';
export type { Deal, DealFilter } from './api/deals.types';

// Alles andere ist "private" - nicht von aussen importieren

Performance Patterns

React.memo - Wann sinnvoll

// SINNVOLL: Teure Render-Komponente in einer Liste
const DealCard = memo(function DealCard({ deal }: { deal: Deal }) {
  return <ExpensiveVisualization data={deal} />;
});

// NICHT SINNVOLL: Einfache Komponente, die sowieso immer neu rendert
// const Button = memo(({ onClick, children }) => ...); // Overhead > Nutzen

Code Splitting

// Route-basiertes Splitting
const DealsDashboard = lazy(() => import('./features/deals/pages/DealsDashboard'));
const AnalyticsPage = lazy(() => import('./features/analytics/pages/AnalyticsPage'));

// Component-basiertes Splitting fuer schwere Abhaengigkeiten
const HeavyChart = lazy(() => import('./components/HeavyChart'));

function Dashboard() {
  return (
    <Suspense fallback={<ChartSkeleton />}>
      <HeavyChart data={chartData} />
    </Suspense>
  );
}

Virtualisierung fuer grosse Listen

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualDealList({ deals }: { deals: Deal[] }) {
  const parentRef = useRef<HTMLDivElement>(null);

  const virtualizer = useVirtualizer({
    count: deals.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 72,
  });

  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      <div style={{ height: `${virtualizer.getTotalSize()}px`, position: 'relative' }}>
        {virtualizer.getVirtualItems().map(virtualRow => (
          <div
            key={virtualRow.key}
            style={{
              position: 'absolute',
              top: 0,
              transform: `translateY(${virtualRow.start}px)`,
              width: '100%',
            }}
          >
            <DealCard deal={deals[virtualRow.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}

Anti-Patterns

Anti-PatternProblemLoesung
Prop Drilling 5+ EbenenUnlesbar, fragilContext oder Composition
God Component (500+ Zeilen)Nicht testbar, nicht wartbarAufteilen in Sub-Components
useEffect fuer Derived StateUnnoetige Re-RendersuseMemo oder direkt berechnen
State SynchronisationRace Conditions, InkonsistenzSingle Source of Truth
Business Logic in ComponentsNicht testbarCustom Hooks / Service Layer
Conditional HooksReact Rules ViolationHooks immer aufrufen, Logik im Hook
Index als Key in dynamischen ListenFalsche Re-RendersStabile ID verwenden
// FALSCH: useEffect fuer derived State
const [items, setItems] = useState<Item[]>([]);
const [total, setTotal] = useState(0);
useEffect(() => {
  setTotal(items.reduce((sum, i) => sum + i.price, 0));
}, [items]); // Doppelter Render!

// RICHTIG: Direkt berechnen
const [items, setItems] = useState<Item[]>([]);
const total = useMemo(() => items.reduce((sum, i) => sum + i.price, 0), [items]);

Checkliste

  • Component Hierarchie: Max 3-4 Ebenen Tiefe
  • State: Server State in React Query, Client State lokal/Zustand
  • Kein Prop Drilling ueber 3 Ebenen
  • Feature-basierte Ordnerstruktur
  • Barrel Exports fuer Public APIs
  • Lazy Loading fuer Routes und schwere Components
  • Listen > 100 Items virtualisiert
  • Keine Business Logik in Components
  • Error + Loading + Empty States ueberall

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

88/100Analyzed 2/23/2026

Comprehensive frontend architecture skill covering component patterns, state management, data fetching, and performance optimization with extensive TypeScript examples. Well-structured with decision trees, anti-patterns, and actionable checklist. Minor gap: tags mention security/testing but content focuses purely on architecture patterns. Located in proper skills folder with good metadata.

95
90
85
85
90

Metadata

Licenseunknown
Version-
Updated2/16/2026
Publisherolli107x

Tags

apisecuritytesting