scaling-past-vibe
Guides the transition from a vibe-coded prototype to a production-grade application. Covers identifying when the project has outgrown pure vibe coding, refactoring AI-generated code for production reliability, adding tests retroactively to an untested codebase, introducing CI/CD pipelines, establishing code ownership and review processes, and building the engineering practices needed to sustain a growing application. Use when a vibe-coded project is succeeding and needs to become a real product.
When and how to graduate from vibe coding to production-grade engineering. ## Key Points - Missing indexes (queries work but slow down with data) - No connection pooling - Raw string queries vulnerable to SQL injection - No transactions where they are needed 1. Valid user creation returns 201 and the user object 2. Duplicate email returns 409 3. Missing name returns 400 4. Email longer than 255 characters returns 400 5. Creating a user actually persists to the database - Testing that the mock returns what the mock was told to return (tautological) - Not resetting state between tests - Hardcoded IDs that conflict across tests ## Quick Example ``` Phase 1: Pure vibe coding (solo, prototype) Phase 2: Vibe coding + manual testing + basic deploy Phase 3: Vibe coding + automated tests + CI/CD Phase 4: Vibe coding for features + manual code for critical paths Phase 5: Traditional engineering with AI assistance ``` ```sql -- AI left these out. Add indexes for any column in a WHERE clause. CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_posts_author_id ON posts(author_id); CREATE INDEX idx_sessions_token ON sessions(token); ```
skilldb get vibe-coding-workflow-skills/scaling-past-vibeFull skill: 421 linesScaling Past Vibe Coding
When and how to graduate from vibe coding to production-grade engineering.
The Transition Point
Not every vibe-coded project needs to scale. Most prototypes serve their purpose and get archived. But some projects gain users, generate revenue, or become critical infrastructure. That is when you need to transition.
Signs You Have Outgrown Vibe Coding
Users depend on your app daily. If downtime causes people real problems, you need reliability engineering, not vibes.
You have paying customers. Revenue means expectations. Customers do not accept "I'll prompt the AI to fix it" as an incident response.
A second developer joins. Vibe-coded projects are legible to the person who prompted them. They are often illegible to everyone else.
Bugs are getting harder to fix. Each AI-generated fix introduces new issues. The codebase has accumulated layers of patches that nobody fully understands.
You are afraid to change things. If you avoid modifying a section of code because you are not sure what it does or what depends on it, you have a maintainability problem.
Deployment is manual. You are SSH-ing into a server, running commands by hand, or deploying from your laptop. One mistake and production goes down.
The Transition Is Gradual
You do not stop vibe coding overnight. The transition happens in phases:
Phase 1: Pure vibe coding (solo, prototype)
Phase 2: Vibe coding + manual testing + basic deploy
Phase 3: Vibe coding + automated tests + CI/CD
Phase 4: Vibe coding for features + manual code for critical paths
Phase 5: Traditional engineering with AI assistance
Most successful projects land at Phase 3 or 4. Phase 5 is for large teams building complex systems.
Refactoring AI Code for Production
AI-generated code has predictable weaknesses. Address these systematically.
1. Eliminate Dead Code
AI tools generate code they later replace but do not always remove. Accumulated dead code confuses both humans and future AI sessions.
// Common AI artifact: commented-out code from previous iterations
export async function getUsers(db: Database) {
// const users = await db.query('SELECT * FROM users');
// return users.filter(u => u.active);
// Updated version
// const result = await db.execute('SELECT * FROM users WHERE active = 1');
// Final version
return db.query('SELECT id, name, email FROM users WHERE active = 1');
}
// Clean version
export async function getUsers(db: Database) {
return db.query('SELECT id, name, email FROM users WHERE active = 1');
}
Action: Search for commented-out code blocks and remove them. You have version control for history.
2. Consolidate Duplicate Logic
AI does not track whether it already wrote a utility for something. You end up with three slightly different date formatters or two user validation functions.
// File: src/users/route.ts
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// File: src/auth/signup.ts (AI forgot it already wrote this)
function isValidEmail(email: string): boolean {
return email.includes('@') && email.includes('.'); // Weaker version
}
// Fix: one canonical function in a shared module
// File: src/shared/validation.ts
export function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Action: Search for similar function names and consolidate into shared modules.
3. Add Error Handling
AI code often has the happy path but skips error handling, or adds generic catch blocks that swallow errors silently.
// AI-generated: swallows errors
async function fetchData() {
try {
const res = await fetch('/api/data');
return await res.json();
} catch (e) {
console.log(e); // Logs and moves on — caller gets undefined
}
}
// Production: errors propagate or are handled meaningfully
async function fetchData(): Promise<DataResponse> {
const res = await fetch('/api/data');
if (!res.ok) {
throw new ApiError(`Failed to fetch data: ${res.status}`, res.status);
}
return res.json();
}
Action: Audit every try/catch and catch block. Ensure errors are either handled meaningfully or propagated.
4. Fix the Data Layer
AI-generated database code often has:
- Missing indexes (queries work but slow down with data)
- No connection pooling
- Raw string queries vulnerable to SQL injection
- No transactions where they are needed
-- AI left these out. Add indexes for any column in a WHERE clause.
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_posts_author_id ON posts(author_id);
CREATE INDEX idx_sessions_token ON sessions(token);
5. Harden Configuration
Replace hardcoded values with environment variables. AI frequently embeds localhost URLs, default ports, and development credentials directly in code.
// AI-generated
const db = new Database('postgresql://localhost:5432/myapp');
const jwtSecret = 'super-secret-key';
// Production
const db = new Database(process.env.DATABASE_URL!);
const jwtSecret = process.env.JWT_SECRET!;
if (!jwtSecret) throw new Error('JWT_SECRET is required');
Adding Tests Retroactively
Vibe-coded projects rarely have tests. Adding them after the fact requires a strategic approach — you cannot test everything at once.
Prioritize What to Test
Test the riskiest code first, not the easiest code.
Priority 1: Authentication and authorization
Priority 2: Payment and billing logic
Priority 3: Data mutations (create, update, delete)
Priority 4: Business logic with conditional branches
Priority 5: API input validation
Priority 6: UI rendering (lowest priority for retroactive tests)
Start with Integration Tests
Unit tests require well-structured code. AI-generated code is often not well-structured enough for easy unit testing. Start with integration tests that exercise the full stack.
// Integration test for the user API
import { describe, it, expect, beforeEach } from 'vitest';
import { app } from '../src/app';
import { resetDatabase } from './helpers';
describe('Users API', () => {
beforeEach(async () => {
await resetDatabase();
});
it('creates a user and retrieves it', async () => {
const createRes = await app.request('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Alice', email: 'alice@test.com' }),
});
expect(createRes.status).toBe(201);
const user = await createRes.json();
const getRes = await app.request(`/api/users/${user.id}`);
expect(getRes.status).toBe(200);
const fetched = await getRes.json();
expect(fetched.name).toBe('Alice');
});
it('rejects invalid email', async () => {
const res = await app.request('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Bob', email: 'not-an-email' }),
});
expect(res.status).toBe(400);
});
});
Use AI to Write Tests (Carefully)
AI is actually good at writing tests — if you give it clear instructions:
Write integration tests for the POST /api/users endpoint.
Test these cases:
1. Valid user creation returns 201 and the user object
2. Duplicate email returns 409
3. Missing name returns 400
4. Email longer than 255 characters returns 400
5. Creating a user actually persists to the database
Use vitest. The app is exported from src/app.ts.
Review the tests the AI generates. Common AI test mistakes:
- Testing that the mock returns what the mock was told to return (tautological)
- Not resetting state between tests
- Hardcoded IDs that conflict across tests
- Missing edge cases
Test Coverage Targets
Do not aim for 100%. Aim for confidence.
80% coverage on business logic
70% coverage on API routes
50% coverage on utility functions
Integration tests for every critical user flow
Zero coverage on generated boilerplate (that is fine)
Setting Up CI/CD
Minimum Viable CI/CD Pipeline
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
deploy:
needs: check
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run build
# Deploy step depends on your hosting
- run: npx wrangler deploy # or vercel deploy, etc.
What the Pipeline Should Catch
- Type errors —
npx tsc --noEmitcatches hallucinated types - Lint errors — ESLint catches common AI code smells
- Test failures — Your tests run on every push
- Build failures — Ensures the code compiles and bundles
Deploy Checklist Before First Production Deploy
[ ] Environment variables configured in hosting platform
[ ] Database migrations run automatically or documented
[ ] Error tracking set up (Sentry, LogRocket, etc.)
[ ] Health check endpoint exists
[ ] DNS and SSL configured
[ ] Backup strategy for the database
[ ] Rollback plan documented (how to revert a bad deploy)
Code Ownership
From Solo to Team
The hardest part of scaling past vibe coding is the cultural transition from "I prompted this" to "we maintain this."
Document decisions, not code. AI-generated code does not need line-by-line comments. But architectural decisions need explanation: why this database, why this structure, why this library.
# Architecture Decisions
## 2025-03-15: Chose SQLite over PostgreSQL
SQLite is simpler for our current scale (< 1000 users).
Will migrate to PostgreSQL when we need concurrent writes
or a second server instance.
## 2025-04-02: No ORM, using raw SQL with Drizzle
Prisma was too heavy for our deploy target (Cloudflare Workers).
Drizzle gives us type safety without a query engine.
Establish code review. When a second person joins, every change goes through review. This is non-negotiable. AI-generated code needs more review, not less.
Create a CLAUDE.md or equivalent. Document your project's conventions so the AI (and new developers) follow them consistently.
# CLAUDE.md
## Stack
- Next.js 14 (App Router)
- Drizzle ORM with SQLite
- TailwindCSS
- Vitest for testing
## Conventions
- API routes in src/app/api/[resource]/route.ts
- Database queries in src/db/queries/[resource].ts
- All API inputs validated with Zod
- Dates stored as ISO 8601 strings
- IDs are cuid2
## Do Not
- Do not use class components
- Do not add new dependencies without discussing
- Do not write raw SQL outside of src/db/
Onboarding a New Developer
- Walk them through the architecture (30 min)
- Have them read CLAUDE.md and the architecture decisions doc
- Start them on a small, well-tested feature
- Pair on the first PR review to establish standards
- Gradually expand their area of ownership
The Graduation Checklist
Work through this list as your project grows:
Stage 1: Getting Serious
[ ] Version control with meaningful commit messages
[ ] Environment variables for all config
[ ] Error tracking (Sentry or equivalent)
[ ] Basic monitoring (uptime check)
Stage 2: Production Ready
[ ] CI pipeline (lint, typecheck, test, build)
[ ] Automated deploys from main branch
[ ] Database backups
[ ] Integration tests for critical paths
Stage 3: Team Ready
[ ] CLAUDE.md with project conventions
[ ] Architecture decisions documented
[ ] Code review process
[ ] Development environment setup documented
[ ] API documentation (at minimum, typed schemas)
Stage 4: Scaled
[ ] Performance monitoring
[ ] Load testing for critical paths
[ ] Incident response process
[ ] On-call rotation (if applicable)
[ ] Feature flags for risky deployments
Anti-Patterns Summary
| Anti-Pattern | Why It Hurts | Fix |
|---|---|---|
| Never transitioning | Tech debt compounds until the app is unmaintainable | Follow the signs, start early |
| Big-bang rewrite | You lose momentum and ship nothing for months | Refactor incrementally |
| Testing easy code first | False confidence; risky code stays untested | Prioritize by risk |
| Skipping CI/CD | Manual deploys break production | Automate from Stage 2 |
| No code review for AI code | AI bugs slip through silently | Review everything, especially AI output |
| Documenting every line | Wasted effort on code that changes constantly | Document decisions, not code |
| Waiting for perfection | You will never feel "ready" to add tests or CI | Start imperfect, improve iteratively |
Install this skill directly: skilldb add vibe-coding-workflow-skills
Related Skills
ai-pair-programming
Teaches effective AI pair programming techniques for tools like Claude Code, Cursor, and Copilot. Covers when to lead versus follow the AI, providing persistent context through CLAUDE.md and .cursorrules files, breaking complex tasks into AI-manageable pieces, using git strategically with frequent commits as checkpoints, and recognizing when the AI is stuck in a loop. Use when working alongside AI coding tools in a collaborative development workflow.
debugging-ai-code
Teaches how to debug code generated by AI tools, covering the unique failure modes of AI-generated code including hallucinated APIs, version mismatches, circular logic, and phantom dependencies. Explains how to read error messages back to the AI effectively, provide minimal reproductions, diagnose when the AI is giving bad fixes, and use systematic debugging approaches on codebases you did not write by hand. Use when AI-generated code is not working and you need to find and fix the issue.
maintaining-ai-codebases
Covers the unique challenges of maintaining codebases built primarily through AI code generation. Addresses inconsistent patterns across AI-generated files, refactoring AI sprawl, establishing coding conventions after the code already exists, documentation strategies for AI-built projects, and managing the specific forms of technical debt that AI tools create. Use when a vibe-coded project needs ongoing maintenance or has grown unwieldy.
prompt-to-app
Guides the complete journey from an idea to a working application using AI code generation tools. Covers writing effective app specifications, choosing the right tool for the job (Claude Code, Cursor, Bolt, v0, Lovable, Replit Agent), the spec-first approach, iterating on generated code without losing coherence, and managing scope creep during AI-assisted development. Use when someone wants to build an app from scratch using vibe coding.
reviewing-ai-code
Teaches how to review, audit, and evaluate AI-generated code effectively. Covers common AI code smells like over-engineering, dead code, wrong abstractions, and hallucinated APIs. Includes security review checklists, dependency auditing, performance review techniques, and strategies for catching the subtle bugs that AI confidently introduces. Use when reviewing code produced by any AI coding tool.
vibe-coding-architecture
Covers architecture decisions optimized for AI-assisted development. Teaches how to choose frameworks and structures that AI tools work well with, why monolith-first is the right default for vibe coding, how to organize files so AI can navigate them, which abstraction patterns help versus hinder AI code generation, and how to keep complexity within the bounds of what AI can reason about. Use when making technology and architecture choices for a vibe-coded project.