---
name: frontend-engineering
description: |-
  Enforces professional frontend architecture — component structure, CSS
  methodology, state management, performance, and React patterns.
  TRIGGER when: user builds or modifies any React/Next.js component, asks
  about component structure, state management, CSS architecture, responsive
  layout, TypeScript typing, lazy loading, or frontend file organization.
  TRIGGER also when: user asks why frontend is slow, broken on mobile,
  causing re-render loops, or has hydration errors.
  DO NOT TRIGGER when: user is working on backend routes, database schemas,
  server logic, or non-frontend deployment tasks.
title: frontend-engineering
canonical_url: https://skilld.dev/gh/mdzubair933/web-design-mastery-skills/frontend-engineering
last_updated: 2026-09-29T08:35:58.000Z
---

> **Skill from skilld.dev.** Follow the instructions below for this session. You do not need to install anything.
>
> If the user asked to install this Skill, run `npx skilld install mdzubair933/web-design-mastery-skills/frontend-engineering`. Install writes the Skill files into the project, so every session loads them.

# Frontend Engineering

Enforces production-grade React/Next.js frontend architecture — clean
component structure, scalable CSS, correct state management, and performance
patterns that eliminate AI-slop and amateur mistakes.
Primary constraint: every component must be reusable, every style must be
variable-mapped, and every state update must be traceable and loop-free.

## Core Rules

Rule: Isolate frontend and backend completely — separate root folders,
separate servers. Why: mixing them creates ambiguous security boundaries and
makes credential leaks far more likely.

Rule: Never make database calls from React/Next.js files. Why: this exposes
credentials in the browser and bypasses all backend security validation.
All data fetching goes through the `services/` API layer only.

Rule: Never use `any` type in TypeScript. Why: `any` disables the type
system entirely — bugs that TypeScript would catch at compile time become
runtime crashes in production.

Rule: Never store JWT access tokens in localStorage or sessionStorage.
Why: XSS scripts can read these stores; stolen tokens grant full account
access. Access tokens stay in React state/context only.

Rule: Always define explicit dependency arrays in every `useEffect`.
Why: missing dependency arrays trigger infinite render loops — these are
nearly invisible during light testing but crash real browsers under load.

## Component Architecture

**Directory Structure (non-negotiable):**
```
src/
├── components/
│   ├── ui/         ← reusable: Button, Input, Modal, Card, Tooltip, Label
│   ├── auth/       ← Login, Register, PasswordRecovery forms
│   └── dashboard/  ← Sidebar, DataGrid, StatsCard, NavBar
├── styles/
│   └── global.css  ← all CSS root variables live here
├── services/       ← all API call functions — never DB calls
└── app/ or pages/  ← Next.js routing only
```

**Component Rules:**
- Build generic UI elements in `components/ui/` — they must accept props for
  variants (Primary/Ghost/Danger) and sizes, never hardcode a style inside.
- One component, one responsibility. If a component exceeds ~150 lines,
  it is doing too many things — extract sub-components.
- Brand assets (logos, SVGs) go in `public/Branding/`. Custom fonts go in
  `public/fonts/`. Never inline SVGs larger than icons.

**Reusability Test:** Before building a new component, ask: "Have I built
something similar before?" If yes, abstract the existing one into `components/ui/`
and reuse it. Duplication is the primary driver of unmaintainable frontends.

## CSS Architecture

**Tailwind + CSS Root Variables — always together:**
Tailwind for layout, spacing, and utility classes. CSS root variables in
`global.css` for all brand colors, radii, and theme tokens.

```css
/* Correct — maps to variable */
className="bg-[var(--primary)] text-[var(--foreground)]"

/* Wrong — hardcoded hex */
className="bg-[#6d28d9] text-[#111111]"
```

**Why:** Hardcoded values in 50+ components require 50+ file edits for one
brand change. Variable-mapped components require editing one line in `global.css`.

**Responsive Breakpoints — plan from the start, not as an afterthought:**
- `sm:` (640px) — mobile landscape
- `md:` (768px) — tablet
- `lg:` (1024px) — desktop
- `xl:` (1280px) — wide desktop

**Mobile Rules:**
- Desktop sidebars collapse to a drawer/sheet — never inline-shrink.
- Multi-column grids stack vertically on mobile — never overflow or clip.
- Navigation becomes a bottom bar or hamburger drawer on mobile.

**A4 Print Styles (for invoice/document views):**
```css
@media print {
  body { background: white; margin: 0; }
  .sidebar, .no-print { display: none; }
  .document { width: 210mm; min-height: 297mm; padding: 20mm; }
}
```

## State Management

**Local vs. Global — use this rule:**
| State type | Hook / Tool | Examples |
|------------|-------------|---------|
| Isolated UI state | `useState` | Dropdown open, form input, step counter |
| Shared across multiple branches | React Context | Auth session, theme preference |
| Server data / async | `useEffect` + `useState` or SWR | API responses, user profile |

**State Security Rule:**
- Access tokens (JWT) → React state or Context only. Never localStorage.
- Refresh tokens → set by backend into HttpOnly cookies. Never touch from JS.

**useEffect Rules — all three are mandatory:**
1. Always define a dependency array — empty `[]` or specific deps, never omit.
2. Never mutate state inside a useEffect that lists that state as a dependency
   — this creates an infinite loop.
3. Cleanup subscriptions and timers in the return function of useEffect.

**State-Aware Routing:**
- Authenticated user hitting `/login` or `/register` → redirect to dashboard.
- Unauthenticated user hitting `/dashboard` → redirect to `/login`.
- Never render protected views before auth state is confirmed.

## TypeScript Patterns

**Always define interfaces for:**
- API request payloads
- API response shapes
- Database document models
- Component prop types

```typescript
// Correct
interface Invoice {
  id: string;
  userId: string;
  amount: number;
  status: "draft" | "sent" | "paid";
  createdAt: Date;
}

// Wrong
const invoice: any = await fetchInvoice(id);
```

**Form validation:** Validate locally before posting to backend. Check
required fields, string formats (email), and matching values (confirm password)
client-side first — this avoids unnecessary API calls and 400 errors.

## Performance

**Lazy Loading:** Dynamically import heavy components and secondary views so
they only load when needed. Why: reduces initial bundle size and time-to-interactive.
```typescript
const HeavyChart = dynamic(() => import("@/components/dashboard/Chart"), {
  loading: () => <Skeleton />,
});
```

**Next.js Image Component:** Always use `<Image />` from Next.js instead of
raw `<img>`. Why: automatic WebP conversion, lazy loading, and prevents layout
shifts from unspecified dimensions.

**Avoid re-render triggers:**
- Do not update top-level state on every keypress in a localized text input.
- Group related form fields into a single state object — not individual useState
  per field. Why: individual field states trigger a re-render per character typed.

**Hydration Stability:** Server-rendered HTML must match client-rendered output
exactly. Common mismatch causes:
- Using `window` or `localStorage` during server render — wrap in `useEffect`.
- Rendering timestamps or random values differently on server vs client.

**Puppeteer Visual QA:** For catching layout bugs across viewports, write a
Puppeteer script that spins up the dev server, navigates key views, takes
screenshots across Mobile/Tablet/Desktop, and feeds them back to the AI for
self-correction. This is the professional alternative to manual visual testing.

## API Service Layer

All backend communication must go through `services/` — never call fetch
directly from a component file.

```typescript
// services/invoiceService.ts
export const fetchInvoices = async (token: string) => {
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/invoices`, {
    headers: { Authorization: `Bearer ${token}` },
  });
  if (!res.ok) throw new Error("Failed to fetch invoices");
  return res.json();
};
```

Why: centralizing API calls means one place to update base URLs, auth headers,
and error handling — not hunting through 20 component files.

## Decision Guide

| Situation | Correct action |
|-----------|---------------|
| Need same component in 2+ places | Abstract into `components/ui/` first |
| State needed by sibling components | Lift to parent or use React Context |
| Component causes infinite re-render | Check useEffect dependency array first |
| TypeScript throwing `any` errors | Define a proper interface — do not suppress |
| Mobile layout is broken | Sidebar must be a drawer; grid must stack — fix responsive classes |
| Slow initial page load | Add dynamic imports for heavy components |

## Anti-Patterns

| ❌ Never do this | ✅ Do this instead |
|-----------------|------------------|
| Call DB directly from a React component | Route all data through `services/` API layer |
| Store JWT in localStorage | Keep access token in React state; refresh in HttpOnly cookie |
| Use `any` type in TypeScript | Define interfaces for every payload and model |
| Omit useEffect dependency array | Always specify `[]` or exact deps |
| Hardcode hex in component className | Map to CSS root variable in `global.css` |
| Shrink desktop sidebar on mobile | Collapse sidebar to a drawer component |
| Use raw `<img>` tags | Use Next.js `<Image />` with explicit dimensions |

## Gotchas

- Hydration errors in Next.js are almost always caused by server/client output
  mismatch — check for browser-only APIs (`window`, `document`, `localStorage`)
  running during SSR. Wrap them in `useEffect`.

- `useEffect` with no dependency array runs on every render — this is almost
  never what you want and is a common source of infinite loops.

- TypeScript's `any` type is a silent failure — it compiles fine and crashes
  at runtime. Treat any use of `any` as a bug, not a shortcut.

- Grouping form fields into one state object reduces re-renders but requires
  spread syntax to update individual fields:
  `setForm(prev => ({ ...prev, email: value }))`.

---

**Handoff**
```
Skill: frontend-engineering
Type: Reference
Trigger phrases covered: "build a component", "why is my state looping",
  "how should I structure this", "fix the mobile layout", "useEffect issue"
DO NOT TRIGGER for: backend routes, database schemas, server logic, deployment
Test prompts to verify:
  1. "Build a reusable Button component with Primary and Ghost variants"
     → Should trigger; component goes in components/ui/, CSS vars used, cursor:pointer set
  2. "My useEffect is causing an infinite loop, what's wrong?"
     → Should trigger; dependency array rules applied immediately
  3. "Write a MongoDB schema for invoices"
     → Should NOT trigger; database schema is backend work
Pass/fail rubric score: 5/5
Suggested next iteration: add SWR or React Query patterns if user's projects
  use server-state management heavily beyond basic useEffect fetching
```
