Lesson 4: Multi-File Projects and Architecture
Course: Coding with AI Assistants
Estimated time: 25 minutes
Difficulty: Intermediate Builder
The promise of AI coding is clear when you're starting a new project. The AI sees everything. It builds the feature. It works.
Six months later, your project has 200 files, multiple layers of abstraction, and patterns that evolved as you shipped. Now when you ask the AI to add something, it generates code that imports a file that moved, uses a pattern you deprecated, and breaks three things you didn't mention.
The problem isn't the AI. It's that large codebases are hard for AI to navigate without help. This lesson is about how to structure your project and your workflow so AI stays useful as you scale.
Why AI Struggles with Large Codebases
Context windows have limits. Claude's is 200k tokens. Cursor sends a subset of your project. Neither can reliably work with every file in a large codebase simultaneously.
The result: the AI makes decisions with incomplete information. It doesn't know that your auth helper changed. It doesn't know you renamed that type. It doesn't know that the pattern you used in the old route was the wrong way to do it and you switched approaches in the newer ones.
This creates a specific failure mode: the AI produces code that would have been correct 3 months ago. It's not hallucinating. It's working from stale context.
You can't fix the context window. But you can make your project easier to navigate, provide targeted context instead of hoping the AI sees the right files, and break tasks down so each one fits cleanly within what the AI can see.
Project Structure That AI Can Navigate
The goal is a project where the purpose of every file is obvious from its location and name. You want the AI to be able to produce a correct file path on the first try, find related code without reading 20 files, and produce code that matches the project's patterns without reading all of them.
Principles:
Co-locate related code. Put the component, its styles, its tests, and its types in the same directory. When the AI is working on a component, it needs to know what types it uses and what tests exist. If those are scattered across a types/ directory and a __tests__/ directory at the project root, the AI has to look in multiple places.
components/
ProposalEditor/
ProposalEditor.tsx
ProposalEditor.test.tsx
ProposalEditor.types.ts
useProposalEditor.ts (hook, lives with the component)
Not:
components/ProposalEditor.tsx
types/ProposalEditor.types.ts
hooks/useProposalEditor.ts
__tests__/ProposalEditor.test.tsx
Use index files sparingly. Re-exporting everything through an index.ts looks clean in import statements but makes it harder for AI to find where something actually lives. Keep barrel files only at the top-level of major directories, not nested throughout.
Name files for their purpose, not their type. auth-middleware.ts is better than middleware.ts. stripe-webhook-handler.ts is better than handler.ts. When the AI is asked to "fix the Stripe webhook handler," it should find the right file from the name alone.
Keep environment-specific code centralized. All your API keys, all your third-party client initialization, all your config constants - in a single lib/ or config/ directory. When the AI needs to use Supabase, it knows to look in lib/supabase.ts, not search the codebase.
The AGENTS.md Pattern for AI Navigation
Here's something that works well and most developers don't do: write a brief file that describes your project's structure, conventions, and patterns. Put it at the root of your project. Call it CLAUDE.md, AGENTS.md, or AI-CONTEXT.md.
When you start a session with your AI editor, include this file in the context. The AI reads it first and then knows the rules.
What to put in it:
# Project: [Name]
## Stack
- Next.js 14 App Router (TypeScript)
- Supabase for database (client at lib/supabase-server.ts for server, lib/supabase-client.ts for browser)
- Clerk for auth (middleware configured in middleware.ts)
- Stripe for payments (webhook handling in app/api/webhooks/stripe/route.ts)
- Vercel AI SDK for AI features (streamText and generateText from 'ai')
## Conventions
- All API routes return { data, error } - never bare values or throwing HTTP errors
- Server components fetch their own data - no prop drilling from layout
- Client components are marked with 'use client' at the top
- All Supabase queries include error handling: const { data, error } = await supabase...
- User ID comes from Clerk: const { userId } = await auth()
## File naming
- Components: PascalCase (UserProfile.tsx)
- Utilities and hooks: camelCase (useAuth.ts, formatDate.ts)
- API routes: route.ts in the appropriate app/ directory
- Types: co-located with the component or in [ComponentName].types.ts
## Current patterns - use these, not the old ones
- For auth-gated API routes, use the helper in lib/auth-guard.ts
- For Supabase admin operations (bypass RLS), use the client in lib/supabase-admin.ts
- For error handling, use the AppError class from lib/errors.ts
## What to avoid
- Don't use the Pages Router - this project uses App Router only
- Don't import from @supabase/auth-helpers-nextjs (deprecated, we use @supabase/ssr)
- Don't use useEffect for data fetching - use server components or SWR
That file takes 30 minutes to write once. It saves you hours of correction over the lifetime of the project.
Every time your patterns evolve, update the file. It's documentation that actually gets used because the AI reads it.
Breaking Big Tasks into AI-Sized Chunks
The task "add user subscriptions to this app" is too large for one AI prompt. It touches the database, the auth system, the payment integration, the API routes, the frontend, and the email notifications. No single context window holds all of that coherently.
The task "add a subscriptions table to Supabase with these columns and row-level security" is the right size.
Breaking tasks into AI-sized chunks is just good engineering decomposition applied to AI-assisted work. The rule: each chunk should be implementable by the AI without needing to read more than 5-10 files.
How to break down a large feature:
- Schema first. Write the database migration. Review it. Run it. This is the contract that everything else builds on.
- API layer second. Write the backend routes that interact with the schema. Each route is its own chunk. Write, review, test each one before moving on.
- Types third. If you're in TypeScript, define the types that match your schema and your API responses. The AI uses these as guardrails.
- UI last. Build the frontend components against the API and types you've already defined and tested.
This order is deliberate. The AI builds on solid ground at each step because each previous step has been reviewed and confirmed correct.
Session-based approach for large features:
Start a new AI session (or clear context) when you move between major layers. A session that started working on the schema and accumulated a lot of context about the database model will generate more confused code when you switch to frontend work than a fresh session started with only the types and API shape as context.
It sounds counterintuitive. But fresh context with targeted information beats a long context with accumulated noise.
Refactoring to Be AI-Friendly
If you have an existing project that grew organically, it's probably not AI-friendly yet. Not all at once - but you can refactor incrementally toward a structure that works better.
The highest-impact refactors:
Consolidate your library initialization. If Supabase is initialized in 12 different files in slightly different ways, the AI will pick one randomly and might pick the wrong one. Consolidate to a single canonical client file and update all the imports.
Write your conventions file. Even if the codebase doesn't perfectly follow them yet. Describe what you want. Use the conventions file to guide new code and migration of old code.
Standardize your error handling. If some routes return errors as HTTP status codes, some throw exceptions, and some return { error: "message" }, the AI can't produce consistent new code. Pick a pattern, document it, and start migrating.
Break up god files. A 500-line component with 8 hooks, 3 async functions, and inline helper logic is hard for AI to work with. It sees all of it as context but can't easily isolate the piece you want changed. Break it up by responsibility.
Do these refactors one at a time. Each one is a small AI-friendly task: "Find all Supabase client initializations in the codebase. Consolidate them to use the client from lib/supabase-server.ts or lib/supabase-client.ts depending on whether they're server or client code."
Context Windows in Practice: What Actually Fits
To give you a concrete sense of context window limits:
| What it contains | Approximate tokens |
|--|--|
| A 100-line TypeScript component | ~1,500 tokens |
| A 50-line API route | ~750 tokens |
| A full Supabase schema (10 tables) | ~2,000 tokens |
| Your conventions file (as above) | ~500 tokens |
| Typical brief file | ~500-1,000 tokens |
Claude's 200k context window holds a lot. But Cursor sends a curated subset based on what you have open and what you've @-mentioned. Claude Code reads what you tell it to read.
The practical implication: for a 200-file project, the AI can only see 10-20% of your codebase in a single context. The structure and conventions file are how you make that 10-20% work correctly even when the other 80-90% isn't visible.
Exercise: Refactor an Existing Project to Be AI-Friendly
Take a project you're actively working on. Run this exercise:
Part 1: Write the conventions file (15 min)
Create CLAUDE.md at the project root. Write the sections from the template above. Be honest about what your project actually does, not what you wish it did. If your patterns are inconsistent, note the intended pattern and mark the inconsistencies as "legacy."
Part 2: Find your friction points (10 min)
Open your AI editor and ask it a question you've asked before that got a wrong or inconsistent answer. Something like "how does auth work in this project" or "how are API errors handled."
See what the AI says without the conventions file. Then add the conventions file to context and ask again. The difference shows you how much the file helps.
Part 3: Pick one structural refactor (15 min)
Based on what you've seen, pick the single highest-friction structural problem. Common ones: library client scattered across files, inconsistent error handling patterns, mixed naming conventions.
Write a brief for fixing it. Execute the brief with your AI editor. Review the output carefully - structural refactors touch many files and have high potential for subtle breakage.
Key Takeaway
The AI can only work well with what it can see. Structure your project so the right context is easy to provide, and write a conventions file that tells the AI what it can't read from the code alone.
What's Next
Your project is structured for AI collaboration. Lesson 5 tackles the last piece: how to review AI-generated code systematically, how to use AI to review AI code (it works), and what a testing strategy looks like in an AI-assisted workflow.