frontend · git:20261025.8aa06bd · 2026-10-25 · sha256 4f4d3e5954cf3879
frontend git:20261025.8aa06bdA
Immutable. This exact content never changes and is served at /api/v1/blob/4f4d3e5954cf3879.
---
name: frontend
description: Frontend architecture, state management, and lifecycle engineering skill. Governs React 19, Next.js, and Flutter with clean boundary isolation.
---
# Frontend Architecture & State Management (`/frontend`)
> **Philosophy**: *Keep components pure. Isolate side effects. Manage state deliberately. Handle errors at the boundary. Never leak business logic into render trees.*
This skill enforces high-performance client architecture across React 19, Next.js App Router, TypeScript, and modern web applications.
---
## 1. Architecture Tiers & Component Separation
```mermaid
flowchart TD
subgraph Server["Server Layer"]
A["React Server Component (RSC)"]
B["Server Actions (Zod-validated)"]
end
subgraph ClientData["Client Data & State"]
C["TanStack Query / SWR / URL State"]
D["Optimistic State (useOptimistic)"]
end
subgraph UI["Pure Presentation"]
E["Stateless Presentational Components"]
F["Error Boundary & Suspense Skeletons"]
end
A --> C
B --> D
C --> D
D --> E
E --> F
```
### 1.1 Clean Boundary Separation Rules
1. **Server Components by Default**: In Next.js App Router, all components are React Server Components (RSC) unless interactive hooks (`useState`, `useEffect`, `useRef`, browser events) are strictly required.
2. **Push `"use client"` to the Leaves**: Never mark an entire page or large container `"use client"`. Wrap only the interactive controls (buttons, inputs, dropdowns) at the leaf nodes.
3. **Zero Inline Fetches in JSX**: Never trigger raw HTTP requests directly inside presentation components. Use dedicated hooks or server functions.
---
## 2. State Management Decision Matrix
Choose the simplest state layer appropriate for the data lifecycle:
| State Scope | Recommended Tool | Example Use Case | Anti-Pattern |
| :--- | :--- | :--- | :--- |
| **URL State** | `useSearchParams` / `nuqs` | Filters, pagination, active tab, modal open status | Duplicating URL search params into local `useState` |
| **Server Cache** | `TanStack Query` or RSC | API data, entity collections, cached user profiles | Storing API responses in Redux/Zustand without cache invalidation |
| **Interactive Form** | `React 19 Actions` + `useActionState` | Form submission, password updates, checkout flow | Managing 15 separate `useState` variables for inputs |
| **Client UI Global** | `Zustand` (minimal stores) | Theme preference, audio player state, active sidebar drawer | Putting massive complex database entities into global client store |
| **Local Ephemeral** | `useState` / `useReducer` | Dropdown open/close, hover tooltip, slider value | Passing down 5 levels of props instead of local state |
---
## 3. React 19 Server Actions & Optimistic Updates
Standardize form mutations using React 19 Actions and `useOptimistic`:
```tsx
"use client";
import { useActionState, useOptimistic, startTransition } from "react";
import { updateProfile } from "@/actions/profile";
import { UserCheck } from "lucide-react";
interface ProfileFormProps {
initialName: string;
}
export function ProfileForm({ initialName }: ProfileFormProps) {
const [state, formAction, isPending] = useActionState(updateProfile, { success: true, error: null });
const [optimisticName, setOptimisticName] = useOptimistic(
initialName,
(current, newName: string) => newName
);
const handleSubmit = (formData: FormData) => {
const nextName = formData.get("name") as string;
startTransition(() => {
setOptimisticName(nextName);
});
formAction(formData);
};
return (
<form action={handleSubmit} className="flex flex-col gap-3 max-w-sm">
<label htmlFor="name-input" className="text-xs font-medium text-zinc-300">
Display Name (Current: {optimisticName})
</label>
<input
id="name-input"
name="name"
defaultValue={initialName}
disabled={isPending}
className="px-3 py-1.5 bg-zinc-900 border border-zinc-800 rounded text-sm text-zinc-100 focus:outline-none focus:ring-2 focus:ring-zinc-400"
/>
{state.error && <p className="text-xs text-rose-400">{state.error}</p>}
<button
type="submit"
disabled={isPending}
className="inline-flex items-center justify-center gap-2 px-3 py-1.5 bg-zinc-100 hover:bg-zinc-200 text-zinc-900 text-xs font-semibold rounded transition-colors disabled:opacity-50"
>
<UserCheck className="w-3.5 h-3.5" aria-hidden="true" />
{isPending ? "Saving..." : "Save Changes"}
</button>
</form>
);
}
```
---
## 4. Memory Safety & Effect Cleanup
Always wire an `AbortController` or cancellation signal to async operations inside `useEffect`:
```typescript
useEffect(() => {
const controller = new AbortController();
let isMounted = true;
async function loadData() {
try {
const res = await fetch("/api/v1/metrics", { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
if (isMounted) setMetrics(data);
} catch (err: unknown) {
if ((err as Error).name !== "AbortError" && isMounted) {
setError(err instanceof Error ? err.message : "Fetch failed");
}
}
}
loadData();
return () => {
isMounted = false;
controller.abort();
};
}, []);
```
---
## 5. Frontend Architecture Validator CLI
Scan frontend modules for architecture violations, inline fetches in JSX, and missing cleanup:
```bash
python3 skills/frontend/scripts/verify_frontend_architecture.py --path .
```