In recent years, my development philosophy has been built on 3 pillars: Clean Code, TDD (Test Driven Development), and Evolutionary Architecture. For me, code is not just a set of instructions for a machine; it's technical content that must be readable, maintainable, and robust — living documentation.
However, for the recent redesign of my personal website, I decided to do something that, on paper, goes against my instincts: embrace "Vibe Coding".
If you haven't heard the term, Vibe Coding is that recent trend of iterating software at breakneck speed using generative AI, prioritizing visual results and immediate functionality over the underlying implementation. Basically: "If it works and looks good, ship it".
Can a Software Crafter survive this without losing their soul (and code quality) in the process? Spoiler: Yes, but with nuances.
The Problem: Time Paralysis
My previous website was built in Astro. It was quite fast and I had taken care about code quality. But visually... well, let's say it needed a refresh and I couldn't dedicate much time to it.
Every time I wanted to change the design, I found myself fighting with CSS Grid, alignments, and color palettes. That friction made me postpone updates indefinitely. I had paralysis by analysis (and by CSS).
I needed a change of approach. I needed speed to break the blank page.
The Experiment Stack
I decided to try an "AI-First" workflow:
-
The Engine: Migration from Astro to Next.js. Why? Because integration with current AI tools is much smoother in the React ecosystem.
-
The Artist: v0 (by Vercel) to generate the user interface (UI).
-
The Copilot: Gemini and Claude for logic, content, and refactoring.
The "Vibe Coding" Phase: Magic and Chaos
I started by asking Gemini to help me write prompts for v0, something specific: "A Bento Grid style portfolio, dark, with orange accents, technical typography. Make it look like a developer's IDE."
In less than 60 seconds, I had a visually striking interface. Something that for me, writing code by hand, would have taken an entire weekend to lay out. The feeling of speed is addictive. You copy the component, paste it into your project and voilà: it works.
Although to get to this point I had to iterate several times with the prompts, adjusting colors, sizes, and layout. AI is not perfect, but its speed allows for a very agile trial-and-error cycle.
The Missing Link: Claude as a "Pair Programmer"
This is where the second piece of the puzzle came in. If v0 was the creative designer, Claude became the support engineer. Its role was crucial to bridge the gap between prototype and final product:
-
Cleaning up v0 errors: Sometimes, v0 generated styles that looked good on desktop but broke the responsive layout on mobile, or used libraries I didn't have installed. Instead of fighting with the CSS, I asked Claude: "This component has unwanted horizontal scroll on mobile, fix it using standard Tailwind classes".
-
Massive content migration: I had dozens of posts and talks on my old website (in Astro/Markdown format). Moving them manually to the new Next.js structure would have been a pain. I passed Claude my old files and the new v0 components, and he took care of transforming and adapting all the content to fit the new props automatically.
But this is where the magic ends and reality begins.
When reviewing the generated code, my developer self started seeing the cracks:
-
Monolithic components of 400 lines.
-
"Hardcoded" text inside JSX.
-
Bad HTML semantics (divs inside buttons, missing
aria-labels). -
Business logic mixed with presentation.
The AI had given me a beautiful prototype, but structurally fragile.
The Return to Clean Code: The Editor's Role
This is where I believe the future of our profession lies. It's not about rejecting AI as "dirty", nor accepting it blindly. It's about elevating the level of abstraction.
My role shifted from "Code Bricklayer" (writing every <div>) to Architect and Editor.
The process I followed was:
-
Acceptance: Use v0's code as a visual base. It saved me 80% of the tedious styling work.
-
Extraction and Componentization: Breaking that monolithic block into reusable pieces.
-
Strong Typing: Adding TypeScript interfaces. AI tends to be lax with types.
-
Separation of Concerns: Making sure UI components are "dumb" (only paint data) and the logic lives separately.
A Practical Example: Refactoring the Chaos
To show you what I mean, here's a real example. The AI generated me a visually perfect card component, but with this look in the code:
// ❌ The "Vibe" code (AI Generated)
// Works, but hard to maintain
<div className="w-full border rounded-lg p-6 bg-zinc-900">
<div className="flex justify-between mb-4">
<h3 className="text-xl font-bold">Lean Mind Tools</h3>
<span className="text-green-500 text-xs">Active</span>
</div>
<p className="text-gray-400 text-sm">Internal tools...</p>
{/* ... div soup ... */}
</div>It works, but if I want to change the design tomorrow, I'll suffer. Applying Clean Code, I extracted the UI into atomic components and defined a clear interface:
// ✅ The "Crafted" code (Refactored)
// Robust, reusable, and testable
interface ProjectCardProps {
title: string;
status: 'active' | 'archived';
description: string;
tags: string[];
}
export const ProjectCard = ({ title, status, description, tags }: ProjectCardProps) => {
return (
<Card className="hover:border-brand-orange">
<CardHeader title={title} status={status} />
<CardBody>{description}</CardBody>
<CardFooter tags={tags} />
</Card>
);
};The visual result is identical (pixel-perfect thanks to AI), but the underlying architecture is now solid.
Conclusion: Speed vs. Craftsmanship
This redesign has taught me a lesson in humility. Sometimes, our quest for technical perfection holds us back from delivering value (or simply, shipping things).
AI allowed me to break through the design block. Clean Code allowed me to sleep soundly knowing the project is maintainable.
I don't think "Vibe Coding" is the enemy of "Clean Code". If used with criteria, it's simply another tool in the craftsman's belt. The key is not forgetting that, at the end of the day, you are responsible for what reaches production, not the prompt.
Did you like the new design? You can see the code (already refactored and clean 😉) on my GitHub.