Technical
Frontend Patterns That Survived Two Years of Agent Generation
I have generated hundreds of frontend components with agents over two years. Most got deleted. The ones that survived share a handful of traits worth naming explicitly, because fighting the patterns that survive is a losing game.
Surviving Trait 1: Server Components Unless Proven Otherwise
Next.js 15 and 16 pushed server components hard, and the discipline paid off. Any component that does not need interactivity stays on the server. Smaller bundles, faster loads, cleaner data flow. The
'use client' directive is a cost, not a default.
Surviving Trait 2: One Source of Truth for Styling
Tailwind utility classes inline in JSX, shadcn primitives for complex components, no separate CSS files. Agents write this pattern cleanly. Mixing Tailwind with CSS modules or styled-components confuses every agent I have used and produces inconsistent output.
Surviving Trait 3: Typed API Client
I stopped writing fetch calls directly in components. Every API call goes through a typed client with explicit input and output types. The agent generates correct calls because the types prevent it from guessing.
export async function getPosts(): Promise<Post[]> {
const res = await fetch(`${API}/posts`, { next: { revalidate: 60 } });
if (!res.ok) throw new Error('failed to fetch posts');
return res.json();
}Boring. Reliable. Agents love it.
Surviving Trait 4: No Global State Unless Forced
Zustand, Redux, Jotai: I used to reach for these reflexively. Most of the time, props and URL state are enough. Global state is where agent-generated code rots fastest, because each component update leaks assumptions into the store.
Surviving Trait 5: shadcn Over Custom Components
For the local admin POC I use Tailwind-only shadcn patterns. For production I use full shadcn with Radix. Either way, the agent generates correct JSX because the API is stable. Custom design systems force the agent to guess, and the guesses decay fast.
What Keeps Getting Deleted
- Inline useState for things that belong in URL params
- Custom hooks that wrap a single fetch call
- Components with more than three props that should be two components
- useEffect chains where a derived value would do
Every one of these is an agent pattern I had to explicitly forbid in my project rules.
See the Next.js App Router docs for the opinionated defaults that make agent generation actually work.
The Cost of Clever
Every clever frontend abstraction I introduced in year one got deleted in year two. Custom render prop patterns, fancy context providers, dynamic polymorphic components. Agents struggle with all of them. The code that survives is the code a junior frontend developer would write after reading the official docs for a week. Clever patterns are an ego tax that agent generation exposes quickly.
The Maintenance Lesson
Two years in, the frontend I maintain is more boring than the frontend I shipped in year one. It is also faster, smaller, and more robust. Boring means the next agent session can understand it. Boring means the next consultant can maintain it. Boring is the feature, not the bug, on long-lived code.
RELATED READING
The Consulting Shift I Am Making In Year Two
After a year of writing and building, my consulting practice is changing shape. Shorter engagements. Sharper outcomes.
ReadThe Frontend Shift: Shipping Less JavaScript In Year Two
A year ago I reached for Next.js for everything. This year I often reach for nothing.
ReadThe Serverless Lesson I Would Write On A Sticky Note
After a year of shipping serverless projects, one rule explains most of the wins and all of the losses.
Read