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.
How to structure projects so AI tools generate better code with fewer bugs.
## Key Points
1. **Popularity** — More examples in training data means better generations
2. **Convention over configuration** — Less boilerplate for the AI to get wrong
3. **File-based routing** — AI understands file = route better than config-based routing
4. **Co-located code** — Keeping related code together reduces context the AI needs
5. **Good TypeScript support** — Types constrain the AI and catch hallucinations
- Next.js (Pages Router for simplicity, App Router if you need it)
- React with Vite (less magic than Next.js)
- Svelte/SvelteKit (simple mental model, good AI results)
- Avoid: Angular (too much boilerplate and decoration magic)
- Express.js or Hono (simple, well-known pattern)
- FastAPI (Python — strong types, simple routing)
- Next.js API routes (if already using Next.js)
## Quick Example
```
# The AI has seen thousands of Express apps
app.get('/users', async (req, res) => { ... });
# The AI has seen very few Elysia apps
app.get('/users', ({ query }) => { ... }); // Different conventions, more hallucinations
```skilldb get vibe-coding-workflow-skills/vibe-coding-architectureFull skill: 402 linesArchitecture Decisions When Vibe Coding
How to structure projects so AI tools generate better code with fewer bugs.
The Core Principle
AI generates better code when the architecture is simple, conventional, and well-constrained. Every clever abstraction, custom framework, or unusual pattern is a place where the AI will stumble.
Design for the AI's strengths: standard patterns, small files, explicit data flow, popular frameworks.
Avoid the AI's weaknesses: deep inheritance, implicit behavior, custom DSLs, global mutable state.
Keep Things Simple for AI
Why Simplicity Matters More Than Usual
In traditional development, you might choose a complex architecture because your team understands it. With vibe coding, the AI is part of your team, and it understands simple things reliably and complex things unreliably.
Complexity scale (AI reliability):
Simple CRUD route ████████████ 95%
REST API endpoint ███████████ 90%
Form with validation ██████████ 85%
State machine ████████ 70%
Pub/sub event system ██████ 55%
Custom ORM layer █████ 45%
Macro/codegen system ███ 25%
Rules for AI-Friendly Simplicity
One file, one purpose. A file that does one thing is easier for the AI to understand and modify than a file that does five things.
Explicit over implicit. Pass data as function arguments, not through context, middleware chains, or dependency injection containers. The AI cannot see implicit flows.
// Hard for AI — implicit context
export function getUser() {
const ctx = useContext(AppContext); // Where does this come from?
return ctx.auth.currentUser; // What shape is this?
}
// Easy for AI — explicit parameters
export function getUser(authToken: string, db: Database): Promise<User> {
return db.query('SELECT * FROM users WHERE token = ?', [authToken]);
}
Flat over nested. Deeply nested directory structures confuse AI tools. Three levels deep is a practical maximum.
# Hard for AI
src/
modules/
auth/
domain/
entities/
user/
User.ts
UserFactory.ts
UserRepository.ts
# Easy for AI
src/
auth/
user.ts
user-repository.ts
login.ts
Fewer abstractions, more duplication. A small amount of copy-paste is better than a clever abstraction the AI misuses. If you have a BaseController with 8 lifecycle hooks, the AI will override the wrong hook or forget one.
Choosing AI-Friendly Frameworks
What Makes a Framework AI-Friendly
- Popularity — More examples in training data means better generations
- Convention over configuration — Less boilerplate for the AI to get wrong
- File-based routing — AI understands file = route better than config-based routing
- Co-located code — Keeping related code together reduces context the AI needs
- Good TypeScript support — Types constrain the AI and catch hallucinations
Recommendations by Category
Frontend:
- Next.js (Pages Router for simplicity, App Router if you need it)
- React with Vite (less magic than Next.js)
- Svelte/SvelteKit (simple mental model, good AI results)
- Avoid: Angular (too much boilerplate and decoration magic)
Backend:
- Express.js or Hono (simple, well-known pattern)
- FastAPI (Python — strong types, simple routing)
- Next.js API routes (if already using Next.js)
- Avoid: NestJS (heavy decorators and DI confuse AI), custom framework wrappers
Database:
- Prisma (schema-driven, AI generates excellent Prisma code)
- Drizzle (TypeScript-first, lighter than Prisma)
- Raw SQL with a thin query builder
- Avoid: complex ORMs like TypeORM with heavy entity decorators
Full-stack:
- Next.js + Prisma + PostgreSQL (the most AI-friendly full-stack)
- SvelteKit + Drizzle + SQLite (simpler, faster to iterate)
- Remix (good conventions, less popular — slightly weaker AI output)
Anti-Pattern: Choosing Obscure or Cutting-Edge Frameworks
# The AI has seen thousands of Express apps
app.get('/users', async (req, res) => { ... });
# The AI has seen very few Elysia apps
app.get('/users', ({ query }) => { ... }); // Different conventions, more hallucinations
If the framework was released in the last 6 months, the AI's training data may not include it. Stick with established tools for vibe coding.
Monolith First
Why Monoliths Work Better for Vibe Coding
A monolith keeps everything in one place. The AI can see the database schema, the API, and the frontend in one project. With microservices, the AI can only see one service at a time and has no visibility into how services interact.
Monolith:
AI sees: database + API + frontend + business logic
Result: coherent code that fits together
Microservices:
AI sees: one service at a time
Result: API contracts drift, types diverge, integration breaks
When to Split
Do not split until you have a proven reason:
- The monolith deploy takes more than 10 minutes
- Two teams need to ship independently
- A component needs fundamentally different scaling
- You have outgrown a single database
Most vibe-coded projects never reach this point. A monolith serves hundreds of thousands of users just fine.
The Split Path
When you do split, extract one service at a time:
- Identify the boundary (usually a domain that is loosely coupled)
- Define the API contract explicitly (OpenAPI spec)
- Share the spec with the AI when working on either side
- Extract, deploy, and verify before extracting the next piece
File Structure That AI Understands
The Flat Feature Structure
Group files by feature, not by type. The AI works on one feature at a time and needs all related files nearby.
# By type (bad for AI — related code scattered)
src/
controllers/
userController.ts
postController.ts
models/
userModel.ts
postModel.ts
services/
userService.ts
postService.ts
# By feature (good for AI — related code together)
src/
users/
route.ts
schema.ts
queries.ts
posts/
route.ts
schema.ts
queries.ts
shared/
db.ts
auth.ts
File Naming Conventions
Use predictable, descriptive names. The AI infers purpose from file names.
route.ts — HTTP route handlers
schema.ts — Validation schemas and types
queries.ts — Database queries
helpers.ts — Pure utility functions
middleware.ts — Request middleware
types.ts — TypeScript type definitions
[name].test.ts — Tests for [name].ts
File Size Limits
Keep files under 300 lines. When a file exceeds this:
- The AI starts losing track of the full file
- Changes in one part of the file break another part
- Context window fills up with code the AI does not need
Split large files by extracting functions into focused modules.
Database Decisions
Use a Schema-First Approach
Define your schema explicitly. The AI generates dramatically better database code when it can see the schema.
// schema.prisma — AI reads this and generates correct queries
model User {
id String @id @default(cuid())
email String @unique
name String
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
content String
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
}
SQLite for Prototypes, PostgreSQL for Production
- SQLite: Zero config, single file, fast for prototypes. AI knows it well.
- PostgreSQL: Full-featured, scales, every AI tool generates excellent Postgres code.
- Avoid MySQL for new projects: PostgreSQL has better AI coverage and more modern features.
- Avoid MongoDB unless you need it: AI generates plausible but subtly wrong Mongo queries (wrong aggregation stages, missing indexes).
State Management
Keep State Simple
The most AI-friendly state management is React's built-in useState and useReducer. Every layer of abstraction you add (Redux, MobX, Zustand stores with middleware) is a layer the AI may misuse.
// Start here — AI handles this perfectly
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
// Graduate to this if state gets complex
const [state, dispatch] = useReducer(userReducer, initialState);
// Only add Zustand/Jotai if you have cross-component state
// that prop drilling cannot solve
Server State vs. Client State
Use a data-fetching library (TanStack Query, SWR) for server state. These libraries handle caching, refetching, and loading states — things AI tends to implement incorrectly from scratch.
// AI-generated manual fetch — often has bugs
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/users')
.then(r => r.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []); // Missing dependency? Race condition? Stale closure?
// Better — let a library handle the complexity
const { data, error, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
});
API Design for AI
Use REST, Not GraphQL (for Vibe Coding)
REST is simpler and the AI generates correct REST endpoints far more reliably than GraphQL resolvers. GraphQL's type system, resolvers, and N+1 query problems are common sources of AI bugs.
Consistent Route Patterns
// Predictable pattern — AI replicates this correctly
GET /api/users // List
GET /api/users/:id // Get one
POST /api/users // Create
PUT /api/users/:id // Update
DELETE /api/users/:id // Delete
// For nested resources
GET /api/users/:id/posts
POST /api/users/:id/posts
Validation at the Boundary
Validate all input at the API boundary with a schema library. This catches AI-generated client bugs early.
import { z } from 'zod';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
app.post('/api/users', async (req, res) => {
const parsed = CreateUserSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ errors: parsed.error.issues });
}
// parsed.data is typed and validated
});
Dependency Management
Fewer Dependencies, Fewer Problems
Every dependency is a potential version mismatch, a hallucinated API, or a security vulnerability. Prefer the standard library and built-in platform APIs.
// AI adds lodash for one function
import { debounce } from 'lodash';
// You can write it yourself in 5 lines
function debounce<T extends (...args: any[]) => any>(fn: T, ms: number) {
let timer: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
Pin Versions
When the AI adds a dependency, pin to a specific version. This prevents version drift between what the AI tested and what gets installed.
{
"dependencies": {
"zod": "3.22.4",
"hono": "4.1.0"
}
}
Anti-Patterns Summary
| Anti-Pattern | Why It Hurts | Fix |
|---|---|---|
| Microservices from day one | AI cannot see across service boundaries | Start with a monolith |
| Deep directory nesting | AI loses track of file locations | Three levels maximum |
| Custom abstractions | AI misuses abstractions it has not seen before | Use standard patterns |
| Implicit data flow | AI cannot trace context and middleware chains | Pass data explicitly |
| Cutting-edge frameworks | Not in AI training data | Use established, popular tools |
| Files over 300 lines | AI loses context within the file | Split into focused modules |
| GraphQL for simple apps | More moving parts, more AI bugs | Use REST |
| Global mutable state | AI generates race conditions | Local state, explicit passing |
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.
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.