Multi-File Projects

45 min intermediate Lesson 4

Learning Outcomes

  • Direct Claude to work across multiple interconnected files
  • Understand how Claude navigates and discovers code
  • Perform refactoring that spans file boundaries
  • Add features that require changes in multiple layers
  • Scaffold entire modules from a description

Lesson Plan

Segment Duration Topic
Intro 3 min Why multi-file work is the real power
Demo 12 min Adding a feature across 4 files
Explain 7 min How Claude discovers and navigates code
Demo 10 min Cross-file refactoring
Demo 8 min Scaffolding a new module
Wrap-up 5 min Key takeaways

Before You Begin

Pre-work:

  • Complete Lesson 3
  • Have a project with multiple interconnected files (e.g., a web app with frontend + API + database layers)

Shopping List:

  • Claude Code installed
  • A multi-file project (at least 10+ files across different directories)
  • Git initialised with a clean working state

1 How Claude Navigates Codebases

Claude doesn't read your entire project into memory at once. It navigates intelligently:

  1. Scans the file tree — knows what files exist
  2. Reads on demand — opens files as relevant to your question
  3. Follows imports — traces dependencies between files
  4. Uses grep/search — finds references across the codebase

When you ask Claude to do something, it:

  • Identifies which files need to change
  • Reads those files (and their dependencies)
  • Proposes changes across all affected files

This matters because it means you can work with projects that are far too large to fit in any context window. Claude only loads what it needs, when it needs it — similar to how a human developer navigates a codebase with an IDE rather than reading every file front-to-back.

You can see this navigation in action:

> Add a "created_at" timestamp to all API responses

Claude will:

  1. Search for API route files
  2. Read each one to understand the response format
  3. Identify where responses are constructed
  4. Propose changes to each file that sends a response

What this looks like in your terminal:

Claude: I'll search for files that construct API responses.

[Read] src/api/users.ts
[Read] src/api/orders.ts
[Read] src/api/products.ts
[Search] grep -r "res.json" src/api/

I can see 3 route files that return JSON responses. Each uses
a slightly different format. I'll add created_at consistently:
NOTE
Key Insight
You don't need to tell Claude which files to look at for most tasks. Describe the intent and let Claude find the relevant files. Only point to specific files when Claude looks in the wrong place.
TIP
Tip
If Claude searches broadly and reads many files, that's a sign you should narrow the scope of your request. A focused prompt like 'Add created_at to the user API responses' reads fewer files and produces faster results.

2 Adding a Feature Across Layers

A real feature typically touches multiple layers. Here's how to direct Claude:

The request:

> Add a "favourite items" feature:
> - Database: add a favourites junction table (user_id, item_id, created_at)
> - API: POST /api/favourites/:itemId to toggle, GET /api/favourites to list
> - Frontend: heart icon on ItemCard that toggles favourite state
> - Use optimistic UI updates on the frontend

Notice the prompt structure: it tells Claude exactly which layers are involved and what each layer needs to do. This is important because it prevents Claude from making architectural decisions for you — decisions like "where should the route file live?" and "should I use a new hook or modify an existing one?" are yours to make.

Claude's approach:

  1. Creates the database migration
  2. Creates or modifies the API route file
  3. Modifies the frontend component
  4. May create a custom hook for the favourite state

Guide it if needed:

> The API routes should go in src/routes/favourites.ts
> The database migration should use our Prisma schema
> The heart component already exists at src/components/icons/Heart.tsx

These hints save Claude from guessing. Without them, Claude might create a new heart icon when one already exists, or put routes in the wrong directory. The more established your project patterns are, the less guidance Claude needs — especially if those patterns are documented in CLAUDE.md.

Review strategy for multi-file changes:

  • Check each file individually (Claude shows them one by one)
  • Verify the connections between files (does the frontend call the right endpoint?)
  • Confirm the types match across layers
  • Check that error cases are handled at each boundary

Example — verifying cross-layer consistency:

> Before I approve these changes, verify:
> 1. The frontend calls the exact endpoint paths you defined in the API
> 2. The types used in the frontend match the API response shape
> 3. Error responses from the API are handled in the frontend
TIP
Tip
For features spanning 4+ files, describe the feature once, let Claude generate everything, then review as a cohesive unit. Don't ask file-by-file — that loses the holistic view.
WARNING
Watch Out
When Claude creates multiple connected files, always check the 'seams' — the points where one layer calls another. Type mismatches, wrong endpoint paths, and missing error handling live at boundaries.

3 Cross-File Refactoring

Refactoring across files is where Claude saves the most time:

Rename with implications:

> Rename the UserService class to ProfileService. Update all imports, 
> references, variable names, and file names across the project.

Extract shared code:

> The date formatting logic is duplicated in Dashboard.tsx, 
> UserProfile.tsx, and ActivityFeed.tsx. Extract it into a shared 
> utility function in src/utils/dates.ts and update all three files.

Change a pattern everywhere:

> Convert all fetch() calls in src/api/ to use our new httpClient 
> wrapper from src/lib/http.ts. It handles auth headers and error 
> formatting automatically.

Migrate interfaces:

> We're changing our API response format from { result, error } to 
> { data, error, meta }. Update all API handlers and all frontend 
> code that reads responses.

These operations would take hours manually. Claude handles them in minutes because it can:

  • Find every reference programmatically
  • Apply the change consistently
  • Maintain type safety across the refactor

A real refactoring session:

Suppose you have date formatting scattered across your app:

> The date formatting logic is duplicated in Dashboard.tsx, 
> UserProfile.tsx, and ActivityFeed.tsx. Extract it into a shared 
> utility function in src/utils/dates.ts and update all three files.

Claude's output will look something like:

Created src/utils/dates.ts:

  export function formatRelativeDate(date: Date | string): string {
    const d = new Date(date);
    const now = new Date();
    const diffMs = now.getTime() - d.getTime();
    const diffMins = Math.floor(diffMs / 60000);
    
    if (diffMins < 1) return 'just now';
    if (diffMins < 60) return `${diffMins}m ago`;
    // ... full implementation
  }

Updated Dashboard.tsx:
  - Removed inline formatDate function (lines 12-25)
  + Added import { formatRelativeDate } from '../utils/dates'

Updated UserProfile.tsx:
  - Removed formatTimestamp function (lines 8-18)
  + Added import { formatRelativeDate } from '../utils/dates'

Updated ActivityFeed.tsx:
  - Removed duplicated date logic (lines 31-42)
  + Added import { formatRelativeDate } from '../../utils/dates'

The key value here isn't just saving typing — it's that Claude ensures all three call sites use the same function signature and that the extracted function covers all the edge cases that the three separate implementations handled differently.

NOTE
Key Insight
Before a cross-file refactor, ask Claude: 'Are these three implementations identical or do they have subtle differences?' If they differ, Claude will unify them intelligently rather than just picking one.
WARNING
Watch Out
For large refactors (20+ files), ask Claude to show you a summary first: 'List all files that would need to change for this refactor.' Review the list before asking it to make changes.

4 Scaffolding New Modules

Creating an entirely new feature module from scratch:

> Scaffold a complete "notifications" module following the same 
> patterns as our existing "messages" module:
> 
> Needed:
> - src/types/notification.ts (types)
> - src/api/notifications.ts (CRUD endpoints)
> - src/hooks/useNotifications.ts (React hook)
> - src/components/notifications/NotificationList.tsx
> - src/components/notifications/NotificationItem.tsx
> - src/components/notifications/NotificationBadge.tsx
> - prisma/migrations/[timestamp]_add_notifications.sql
> 
> The notification should have: id, userId, type (info|warning|error), 
> title, body, read (boolean), createdAt.

The "follow existing patterns" instruction is key. By pointing to an existing module, Claude will:

  • Match the file structure
  • Use the same coding style
  • Follow the same state management approach
  • Use consistent error handling

Why list the files explicitly? You might think Claude could figure out the file structure. It often can — but listing the files gives you two advantages:

  1. You control the architecture (where files go, how many components)
  2. You get exactly the deliverables you need to review (nothing extra, nothing missing)

If you don't list files, Claude might create 4 files or 12 files depending on its judgement. Being explicit makes the output predictable and reviewable.

The result:

After running this prompt, you'll have 7 new files that all follow the same conventions as your existing messages module. The types will match your Prisma schema patterns, the hook will use the same fetching library, and the components will use the same state management approach. Consistency without manual effort.

TIP
Tip
When scaffolding, create the types/interfaces first, then the data layer, then the UI. This natural order means each file can reference the ones created before it.
NOTE
Key Insight
The phrase 'following the same patterns as [existing module]' is one of the most powerful instructions you can give Claude. It turns your existing code into a style guide that Claude will follow exactly.

5 Managing Complexity

When multi-file changes get complex, use these strategies:

Strategy 1: Types first

> First, create the TypeScript types for the entire feature in 
> src/types/. Don't implement anything yet — just the interfaces.

Review and approve. Then:

> Now implement the API layer using those types.

Strategy 2: One layer at a time

> Let's add the database schema first. Just the migration, nothing else.

Approve. Then:

> Now add the API endpoints that use this schema.

Strategy 3: Plan then execute

> I want to add user roles and permissions. Before making changes, 
> tell me which files you'd modify and what changes you'd make to each.

Review the plan. Then:

> That plan looks good. Proceed with the implementation.

When to use which:

  • Types first: when you want to validate the data model before building
  • Layer by layer: when each layer is complex enough to review separately
  • Plan then execute: when you're unsure about the scope

Worked example — scaffolding a module with "types first":

Say you need a complete invoicing module. Start with just the types:

> Create src/types/invoice.ts with these interfaces:
> - Invoice (id, customerId, items, total, status, issuedAt, dueAt)
> - InvoiceItem (id, description, quantity, unitPrice, lineTotal)
> - InvoiceStatus (draft | sent | paid | overdue | cancelled)
> 
> Don't implement anything else yet.

Claude creates one clean file. You review it:

> Looks good, but add a "currency" field to Invoice (ISO 4217 code)
> and a "taxRate" field to InvoiceItem.

Now your types are locked. Every subsequent step references these types, and you know the data model is correct before writing a line of business logic. This prevents the painful situation where you build 6 files and then realise the types are wrong — requiring changes everywhere.

TIP
Tip
The 'types first' approach works especially well with TypeScript because the compiler will catch any implementation that doesn't match your types. You get a free verification layer.

Questions & Answers

Q: How many files can Claude modify in one go?
There's no hard limit, but aim for under 10 file changes per request. Beyond that, review becomes difficult and context pressure increases. Break larger changes into stages.
Q: What if Claude misses a file that needs updating?
Tell it: "You missed the import in App.tsx — that also needs to reference the new module." Claude will pick up where it left off and fix the gap.
Q: Should I point Claude to specific files or let it discover?
Let it discover first. If it looks in the wrong place, then point. Claude's search is usually accurate, and letting it discover teaches you how it thinks about your codebase.
Q: How do I know if Claude's scaffolded code follows my project's patterns?
Spot-check one file against an existing equivalent. If the coding style, error handling, and import patterns match your existing code, the rest will too. The "follow existing patterns" instruction is remarkably consistent — if one file is right, they all are.
Q: What if my multi-file change breaks something Claude didn't touch?
This usually happens at integration boundaries. Ask Claude: "Run the tests" or "Check if anything else imports from the files I changed." Claude can trace dependencies and find files that depend on your changes but weren't in the original edit set.

Key Takeaways

  1. Claude navigates automatically — describe intent, let it find the files
  2. Multi-layer features: describe the full scope, let Claude plan the changes
  3. Refactoring shines — renames, extractions, and pattern changes across files
  4. "Follow existing patterns" — point to a reference module for consistency
  5. Manage complexity: types first → API → UI, or plan then execute
  6. Keep batches reviewable — under 10 files per change ideally

Next Steps: In Lesson 5 — Debugging with Claude, you'll learn how to describe bugs effectively and use Claude's analysis to find and fix issues fast.