My first app was 2,000 lines of code in one file. Finding anything took 10 minutes. Adding features broke three other things. I eventually deleted it and started over with proper structure. Good architecture is invisible until you need to change something. Then it's the difference between "fixed in 5 minutes" and "this will take all weekend."
Risk Radar: The Giant File Trap
The Mistake: Putting everything in one file because it's "simpler." It IS simpler... until day 3 when you can't find the button component buried in 800 lines of code.
The Fix: Follow the "150 Line Rule" - if a file exceeds 150 lines, split it up. Your LLM can do this for you: "Extract this login form into a separate component file."
Time saved: 15+ hours of frustrated searching and accidental deletions
LLM Conversation Starter
Ask your LLM to set up proper structure from day one:
I'm starting a [Next.js / React / etc.] project. Create a folder structure that: 1. Separates components, pages, and utilities 2. Groups related files together 3. Makes it easy to find things 3 months from now 4. Follows industry best practices for [your framework] Show me where each type of file should live with examples.
Why this works:
Your LLM will generate a battle-tested structure that scales. Save this as your project template.
File Organization That Scales
Here's the structure I use for every project now. It's based on Next.js App Router but the principles work for any framework:
my-app/
├── app/ # Pages (routes)
│ ├── page.tsx # Homepage
│ ├── about/
│ │ └── page.tsx # /about page
│ └── api/ # Backend endpoints
│ └── users/
│ └── route.ts # /api/users endpoint
│
├── components/ # Reusable UI pieces
│ ├── ui/ # Generic components (buttons, inputs)
│ │ ├── Button.tsx
│ │ └── Input.tsx
│ └── features/ # Feature-specific components
│ └── LoginForm.tsx
│
├── lib/ # Utilities & helpers
│ ├── db.ts # Database connection
│ ├── auth.ts # Authentication logic
│ └── utils.ts # Helper functions
│
└── public/ # Static files (images, fonts)
└── logo.pngThe Golden Rules of Organization
1. Group by Feature, Not File Type
❌ Bad:
components/LoginButton.tsx components/LoginForm.tsx styles/login.css utils/validateLogin.ts
✅ Good:
features/auth/ ├── LoginButton.tsx ├── LoginForm.tsx ├── login.css └── validateLogin.ts
2. If You Use It Twice, Extract It
First time: Write it inline.
Second time: Extract to a component.
Third time: You saved yourself copy-paste bugs.
3. One Thing Per File
If your file is named "utils.ts" and has 20 different functions, split it up.
Better: "formatDate.ts", "validateEmail.ts", "calculatePrice.ts"
4. Keep Related Things Close
If you always edit FileA when you edit FileB, they should be in the same folder. This is the "principle of locality" - it reduces mental overhead.
PM Insight: The 3-Click Rule
Professional teams optimize for: Any file should be 3 clicks or less from your starting point.
Test your structure:
- • "Where's the login button?" → Should take 3 clicks max
- • "Where's the user database code?" → Should take 3 clicks max
- • "Where's the API for posts?" → Should take 3 clicks max
If you're spending 30 seconds looking for files, your structure needs work.
When to Extract Components
This is the most important architectural skill: knowing when to split code into separate pieces. Too early and you have unnecessary complexity. Too late and you have unmaintainable spaghetti.
Extract When:
- You use the same UI pattern twice
- A component exceeds 150 lines
- You're scrolling to find things
- It has a clear, single purpose
- You want to test it separately
Don't Extract When:
- You've only used it once
- It's tightly coupled to parent state
- It's less than 20 lines
- You'd need to pass 10+ props
- It makes code harder to read
Quick Extraction Exercise
You have a 200-line page component with:
- • Navigation bar (30 lines)
- • User profile section (40 lines)
- • Posts feed (80 lines)
- • Footer (20 lines)
- • Helper functions (30 lines)
Solution:
Extract: Nav (reused on other pages), Posts feed (complex logic).
Keep inline: Profile section (unique to this page, not too complex).
Move to lib/: Helper functions (not UI-related).
Design Tokens (Colors, Spacing, Typography)
This was my biggest "aha" moment. Instead of using "blue-500" everywhere, define your colors ONCE and reference them. When you want to change your app's color scheme, you edit one file instead of 200.
Example: Design System File
// lib/design-system.ts
export const colors = {
primary: '#8B5CF6', // Purple
secondary: '#3B82F6', // Blue
success: '#10B981', // Green
danger: '#EF4444', // Red
text: '#F9FAFB', // Off-white
background: '#0A0A0A', // Near-black
}
export const spacing = {
xs: '0.25rem', // 4px
sm: '0.5rem', // 8px
md: '1rem', // 16px
lg: '1.5rem', // 24px
xl: '2rem', // 32px
}
export const typography = {
h1: '2.5rem', // 40px
h2: '2rem', // 32px
h3: '1.5rem', // 24px
body: '1rem', // 16px
small: '0.875rem' // 14px
}Usage in components:
import { colors } from '@/lib/design-system'
<button style={{ backgroundColor: colors.primary }}>
Click Me
</button>PM Insight: The Rebrand Test
Professional teams plan for change: Good architecture means you could rebrand your entire app (new colors, fonts, spacing) by editing one file.
Test yourself: Could you change your app's primary color in 5 minutes? If not, you're using hardcoded values instead of design tokens.
The fix: Ask your LLM: "Create a design system file with my current colors/spacing and refactor my components to use it."
The Abstraction Pattern (Don't Repeat Yourself)
If you copy-paste code three times, you've created three places where bugs can hide. Abstract it instead.
❌ Before Abstraction
// Three different forms
<input
className="border p-2 rounded"
onChange={handleChange}
/>
<input
className="border p-2 rounded"
onChange={handleChange2}
/>
<input
className="border p-2 rounded"
onChange={handleChange3}
/>✅ After Abstraction
// components/ui/Input.tsx
export function Input({ onChange, ...props }) {
return (
<input
className="border p-2 rounded"
onChange={onChange}
{...props}
/>
)
}
// Usage
<Input onChange={handleChange} />
<Input onChange={handleChange2} />
<Input onChange={handleChange3} />The Rule of Three
First time: Write it inline. No abstraction yet.
Second time: Copy-paste is fine, but make a note.
Third time: STOP. Extract it. Ask your LLM: "I'm using this pattern three times. Help me create a reusable component."
Phase 3 Complete Checklist
"Good architecture is like a good joke - if you have to explain it, it's not good enough."
– Every developer after their third refactor