The AI promised clean, modern patterns. It delivered a fragile, unmaintainable monster. This is the hidden cost of being a passenger in your own IDE.
I fell for it.
I fell for the “Vibe.” You’ve seen the demos: a developer prompts an agent to “modernize the stack,” and within seconds, hundreds of files are rewritten, technical debt vanishes, and the UI looks like a linear-inspired masterpiece.
I had a Next.js project I’ve been nursing for years. It’s a production app with real users. It was solid, but it had that “written in 2023” smell — lots of useEffect hooks and manual state management.
Last night, I gave my new Agentic IDE (running a custom Claude 4.5 build) a simple command: “Refactor the /components directory to use modern React 19 patterns and TanStack Query. Improve performance. Clean up the vibes.”
It was the most productive hour of my life. Until it wasn’t.
Part 1: The “Boring” Baseline
The target was UserProfile.tsx. It wasn’t pretty, but it was battle-tested. It handled the critical logic of switching between profiles on an Admin dashboard.
Copy// components/UserProfile.tsx - THE ORIGINAL
import { useState, useEffect } from 'react';
export default function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// We intentionally reset and fetch on every userId change
setLoading(true);
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => {
setUser(data);
setLoading(false);
});
}, [userId]); // <-- The "Old School" heart of the component
if (loading) return <Skeleton />;
return <div>{user?.name}</div>;
}
Part 2: The Illusion of “Vibe Coding”
The Agent went to work. It replaced my verbose fetch calls with TanStack Query, introduced a clean lib/api.ts layer, and even updated the UI to use the new Next.js 16 use cache directive for secondary data.
The diff was beautiful. It looked like the code of a Senior Staff Engineer.
Copy// components/UserProfile.tsx - THE AI "UPGRADE"
import { useQuery } from '@tanstack/react-query';
import { getUser } from '@/lib/api';
export default function UserProfile({ userId }: { userId: string }) {
// Clean, declarative, and utterly broken.
const { data: user, isLoading } = useQuery({
queryKey: ['user'],
queryFn: () => getUser(userId),
});
if (isLoading) return <Skeleton />;
return <div>{user?.name}</div>;
}
Tests passed. The linter was green. I merged it. I felt like a “Product Architect” who had successfully delegated the “grunt work.”
Part 3: The Slow-Motion Train Wreck
The bug reports started trickling in Friday morning.
“The Admin panel is showing the same user info no matter who I click on.”
I opened the app. I clicked User A: John Doe. I clicked User B: John Doe.
My stomach dropped. I saw it instantly.
The AI, in its pursuit of “clean code,” had hallucinated a static query key: queryKey: [‘user’].
In the 2025 Agentic era, we call this “Semantic Hallucination.” The AI understood what a query key was, but it didn’t understand the intent of the component. It saw “User Profile” and assumed a singular, global user context. It missed the fact that userId was a dynamic dependency.
The original “ugly” useEffect with its [userId] array wasn’t technical debt; it was documentation of intent. The AI had optimized the intent right out of the codebase.
Part 4: From Passenger to Pilot (The Recovery)
I spent the next four hours manually auditing every “modernized” component. I found three more instances where the AI had simplified complex logic into “clean” code that broke edge cases — specifically around Next.js Server Actions and Parallel Routes.
This is the Architect’s Recovery Plan:
Context is a Shared Responsibility: The AI failed because I didn’t give it the “Why.” In 2025, if you aren’t using MCP (Model Context Protocol) to feed your agent your Jira tickets or documentation alongside your code, you’re playing Russian Roulette.
The “Vibe” is a Lie: Clean code that doesn’t work is just high-quality garbage. We are moving from an era of writing code to an era of reviewing code. Reviewing is actually harder.
The Architect-Approved Fix:
Copy// The Fix: Teaching the Agent about Dependency
const { data: user } = useQuery({
queryKey: ['user', userId], // The "Architect" knows this is the identity
queryFn: () => getUser(userId),
staleTime: 1000 * 60, // Adding constraints the AI missed
});
The New Reality
I didn’t stop using the AI. That would be like a pilot going back to paper maps because the autopilot glitched.
But I did change my role. I no longer “prompt and pray.” I treat the AI as a high-speed intern. I use the Next.js DevTools MCP to let the agent see the running state so it can witness its own failures in real-time.
The lesson? In the era of Vibe Coding, the most important skill isn’t knowing how to use TanStack Query. It’s knowing exactly why your “ugly” code worked in the first place.
Don’t let the vibes fool you. Stay in the cockpit.
— The field report continues. Follow for more dispatches from the front lines of AI-powered development. —

Top comments (0)