Developer Guides

How to Keep a Vibe-Coded Codebase from Rotting

Growth AutomationsMarch 21, 20264 min read

Vibe coding makes you fast. That speed has a cost: codebases grow faster than your ability to review them. Without deliberate hygiene, you end up with a project that works today and mystifies you tomorrow.

This post covers the four practices that keep a vibe-coded codebase from rotting: file size discipline, automated linting, strict TypeScript, and documented conventions.

Keep Files Small and Modular

Large files are kryptonite for AI-assisted development. Past 500 lines, the AI starts losing context of what’s at the top by the time it’s working at the bottom. It hallucinates functions that already exist, duplicates logic, and introduces subtle bugs outside its attention window.

Split immediatelyWatch closelyIdeal zoneRefactor logic outtypes/index.tsapi-handlers.tsDashboard.tsxAuthFlow.tsxLayout.astroButton.tsxHeroSection.astroutils.tsLow ComplexityHigh ComplexityFew LinesMany LinesFile Health vs AI Effectiveness

The danger zone is upper-right — high complexity and high line count. Those files need to be split.

This is a periodic cleanup effort, not a one-time rule. Files grow as you build. The discipline is scheduling consolidation passes:

Review the files in [folder]. Flag any over 400 lines,
identify duplicate logic, and suggest what to extract.
Don't make changes yet — give me a list first.

Review the list yourself before letting the AI refactor. Structural decisions belong to humans.

Signal Action
Multiple components in one file Extract each to its own file
Same logic in 2+ places Extract to a utility module
File does data-fetching AND rendering Separate concerns
Tailwind classes getting unwieldy Extract to a variants object
Types defined inline everywhere Consolidate to a types module

The file size guide:

Lines Status Action
< 200 Healthy No action needed
200–350 Watch Consider extracting if complexity is high
350–500 Review Plan extractions in next consolidation pass
500+ Split now Extract before adding more features

Run Lint and Typecheck Constantly

Linting and typechecking aren’t style preferences — they catch real bugs. ESLint catches patterns that produce runtime errors. TypeScript strict mode eliminates entire categories of bugs before they execute.

Automate it so you don’t have to remember:

# Install the quality stack
npm install -D eslint prettier husky lint-staged

# Husky pre-commit hook — nothing committed without passing
npx husky init
echo "npx lint-staged" > .husky/pre-commit
// lint-staged config — only lint changed files
"lint-staged": {
  "*.{ts,tsx}": ["eslint --fix", "prettier --write"],
  "*.{css,json,md}": ["prettier --write"]
}

Run lint, typecheck, and your framework’s own checks before every meaningful commit. Make it a reflex, not a decision.

Use Strict TypeScript

any is a lie you tell TypeScript so it stops complaining. The problem is TypeScript was complaining for a reason — and now that reason is invisible, waiting to surface as a runtime bug.

AI assistants love reaching for any when they’re uncertain about a type. This is one of the most common quality regressions in vibe-coded codebases.

Baseline tsconfig:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": true
  }
}

Periodic any-audit:

Search the entire codebase for `: any`, `as any`, or `@ts-ignore`.
List every occurrence with file path and line number.
Don't fix anything — just show me the list.

Usually unknown with proper narrowing is the right replacement. Sometimes a proper interface just needs to be written.

If you use Supabase, regenerate types after every migration:

supabase gen types typescript --local > src/types/supabase.ts

Stale Supabase types are one of the most common sources of TypeScript-passes-but-runtime-fails bugs.

Define and Document Your Conventions

Your project’s folder structure and naming conventions are a language. If you don’t define it explicitly, the AI invents its own dialect. Over time you get an inconsistent codebase where files contradict each other.

Decisions to make before you build anything substantial:

Decision Recommendation
Component location Categorized: ui/, layout/, sections/
Component naming PascalCase for components, kebab-case for files
Type definitions Centralize in src/types/
Utility functions Separate into src/lib/
Tailwind class order layout → spacing → color → state

Document these in conventions.md and reference it in your AI context file. The goal is a codebase that looks like one person wrote it — even if AI generated most of it.

The Consolidation Habit

The hygiene practices above aren’t one-time setup tasks. They’re habits you return to throughout the life of a project.

Every 5–10 features, schedule a consolidation pass:

  1. Run the file size audit — flag anything over 400 lines
  2. Run the any audit — fix or properly type every occurrence
  3. Check that new files follow naming conventions
  4. Verify lint + typecheck still pass clean
  5. Update your context file if AI behavior has drifted

This takes 30 minutes and prevents 3 hours of confused debugging later. The projects that ship are the ones where someone cared enough to keep them clean.

Stay in the loop

Get practical guides, workflows, and automation tips delivered to your inbox.

No spam, ever. Unsubscribe anytime.