How to Maintain Vibe-Coded Projects | Audit, Stabilize, and Scale AI-Generated Code
Professional guide to maintaining vibe-coded projects. Learn how to audit AI-generated codebases, fix common issues, and decide when to refactor vs. rewrite.
thelacanians
What Vibe Coding Actually Produces
The term “vibe coding” — popularized by Andrej Karpathy — describes the practice of building software by describing what you want to an AI and accepting whatever it produces, largely without reading the code. You stay in the flow, iterate fast, and let the AI handle implementation details.
It works remarkably well for prototyping. We have seen founders ship functional MVPs in days using Claude, Cursor, or Copilot in this mode. The problem is what comes after.
At The Lacanians, we have audited dozens of vibe-coded projects at this point. The patterns are consistent enough that we have built a systematic process for dealing with them. This post is that process.
The Common Issues
Vibe-coded projects fail in predictable ways. Not because AI is bad at writing code, but because AI optimizes for the immediate prompt, not for long-term maintainability. Here is what we typically find:
Structural Problems
Monolithic files. AI tends to keep adding to the same file rather than creating proper module boundaries. We have seen single React components exceeding 2,000 lines, with data fetching, business logic, and rendering all interleaved.
Duplicated logic. When you prompt for a feature in isolation, the AI does not know (or does not consider) that similar logic exists elsewhere. You end up with three different date formatting functions and two separate authentication checks.
Inconsistent patterns. Monday’s vibe-coding session used React Query. Tuesday’s used useEffect with fetch. Wednesday’s used a server action. The codebase has no coherent data-fetching strategy.
Security Issues
This is the category that keeps us up at night.
// Actual code found in a vibe-coded production app
const API_KEY = "sk-proj-abc123...";
// "Validation"
if (user.role === "admin") {
// Client-side only check, no server verification
return <AdminPanel />;
}
AI-generated code routinely contains hardcoded secrets, client-side-only authorization checks, unparameterized SQL queries, and missing input validation. These are not theoretical risks. We have found production applications with open database connections, exposed API keys, and no rate limiting on authentication endpoints.
Performance Issues
N+1 queries everywhere. AI-generated data access code almost never optimizes for query patterns. It fetches what it needs for the current component, one record at a time.
No caching strategy. Every page load hits every API. Every API call hits the database. There is no memoization, no HTTP caching headers, no CDN configuration.
Unnecessary client-side rendering. Modern frameworks like Astro and Next.js offer excellent server-side rendering. Vibe-coded projects frequently ignore this, shipping megabytes of JavaScript to render what could be static HTML.
The Audit Process
When a client brings us a vibe-coded project, we follow a structured audit before touching any code.
Step 1: Inventory
We map the codebase. What frameworks and libraries are used? What is the dependency tree? Where is state managed? How does data flow? We use tools like our own vecgrep for semantic code search to quickly identify patterns across the codebase.
# Quickly find all data-fetching patterns in the project
vecgrep search "data fetching, API calls, database queries"
# Identify authentication and authorization logic
vecgrep search "auth check, permission, access control"
Step 2: Security Scan
Before anything else, we identify critical security issues. This includes:
- Scanning for hardcoded secrets (API keys, database URLs, tokens)
- Reviewing authentication and authorization flows
- Checking for SQL injection and XSS vulnerabilities
- Auditing third-party dependencies for known vulnerabilities
- Verifying that environment variables are properly managed
We use tools like tinyvault for local secret management during development, ensuring secrets never end up in code.
Step 3: Architecture Assessment
We evaluate whether the current architecture can support the next 6-12 months of product development. This is the critical question: can we build on this, or do we need to rebuild?
The answer is usually “build on it, but with significant refactoring.” Full rewrites are almost never the right call, and we are suspicious of anyone who recommends one as a first option.
Step 4: Prioritized Report
We deliver a document that categorizes every issue by severity and effort:
- Critical: Security vulnerabilities and data-loss risks. Fix immediately.
- High: Architectural bottlenecks that will block feature development within 1-2 months.
- Medium: Code quality issues that slow development but do not cause failures.
- Low: Style inconsistencies and minor optimizations. Fix opportunistically.
Stabilization Strategies
Once the audit is complete, stabilization follows a specific order.
1. Add Tests Before Changing Anything
The single most important step. Before refactoring a single line, we write tests that capture the current behavior, including its bugs. This gives us a safety net for everything that follows.
// Capture existing behavior, even if it's wrong
describe('UserDashboard', () => {
it('displays the user profile with current data format', () => {
// This test documents the current date format
// even if we plan to change it later
const { getByText } = render(<UserDashboard user={mockUser} />);
expect(getByText('01/15/2025')).toBeInTheDocument();
});
it('handles the existing error state', () => {
// The current error handling is a generic alert
// We document this before improving it
const { getByRole } = render(<UserDashboard user={null} />);
expect(getByRole('alert')).toBeInTheDocument();
});
});
2. Extract and Centralize
Take duplicated logic and consolidate it. Create proper module boundaries. Establish a consistent project structure. This is where most of the refactoring effort goes.
3. Fix Security Issues
With tests in place, fix every critical and high-severity security issue. Replace hardcoded secrets with environment variables. Add server-side authorization. Parameterize queries. Add rate limiting.
4. Establish Patterns
Define the “right way” for the project going forward. Pick one data-fetching strategy. Pick one state management approach. Document these decisions so that future development (whether by humans or AI) follows consistent patterns.
5. Set Up CI/CD
Add linting, type checking, and automated tests to the build pipeline. This prevents regression and enforces the patterns established in step 4. Every PR should pass these checks before merging.
When to Rewrite vs. Maintain
After auditing many vibe-coded projects, here is our decision framework:
Maintain and refactor when:
- The core business logic is sound, even if the code is messy
- The application uses mainstream frameworks (React, Next.js, Vue, Astro)
- Users are actively using the product and you cannot afford downtime
- The team understands the current codebase
Consider rewriting when:
- The application has fundamental architectural flaws (wrong database type, wrong rendering strategy)
- Security issues are so pervasive that patching them individually is more work than starting fresh
- The codebase uses obscure or deprecated frameworks
- There are fewer than 1,000 active users and the product is pre-revenue
Even when rewriting, we advocate for incremental migration using the strangler fig pattern rather than a big-bang rewrite. Replace one route, one component, one module at a time.
The Path Forward
Vibe coding is not going away, and that is fine. It is a legitimate tool for rapid prototyping and exploration. The mistake is treating the output as production-ready without professional review.
If you are sitting on a vibe-coded application that has outgrown its origins, the path forward is not panic and it is not a full rewrite. It is a systematic audit, a prioritized remediation plan, and disciplined execution. That is exactly what we do.