Our codebase had grown to 162,688 lines across 1,494 nodes — pages, components, API routes, libraries, hooks, layouts, and models. And lurking within were 7 "god files": files exceeding 500 lines that had become maintenance nightmares. Here's how we systematically eliminated every one of them.
THE AUDIT
We built a custom data extractor (master-workflow-audit/extract-data.ts) that scans every file in src/, maps imports, exports, and fetch calls, and generates a complete dependency graph. This gave us a bird's-eye view of the entire codebase architecture.
The 7 god files we identified:
- campaign-sender.ts (680 lines) — Email campaign orchestration, SMTP connections, template rendering, and delivery tracking all in one file
- imap-client.ts (620 lines) — IMAP connection management, mailbox operations, message parsing, and search functionality
- webauthn.ts (550 lines) — WebAuthn registration, authentication, credential management, and challenge generation
- tenant-provisioner.ts (530 lines) — Database creation, K8s namespace setup, DNS provisioning, and SSL certificate generation
- admin/settings/page.tsx (510 lines) — Six tabs of settings UI in a single component
- admin/plans/[id]/page.tsx (505 lines) — Plan editing with 7 sections in one file
- admin/billing/components/invoices-tab.tsx (502 lines) — Invoice table, filters, modals, and actions
THE SPLITTING STRATEGY
We didn't just arbitrarily split files at line 250. Each refactoring followed the Single Responsibility Principle:
Backend Libraries: Extract by Concern
For campaign-sender.ts, we identified four distinct responsibilities:
- campaign-builder.ts — Template compilation and personalization
- campaign-executor.ts — Send queue management and rate limiting
- campaign-tracking.ts — Open/click tracking pixel generation
- campaign-sender.ts — Orchestrator that coordinates the above (now 180 lines)
The IMAP client split similarly: connection management, command execution, response parsing, and mailbox operations each got their own module.
Frontend Pages: Extract Sections into Components
For the settings page, each tab became its own component:
src/app/(control-center)/admin/settings/
├── page.tsx (120 lines — tab routing only)
├── tabs/
│ ├── branding-tab.tsx (90 lines)
│ ├── email-tab.tsx (85 lines)
│ ├── security-tab.tsx (80 lines)
│ ├── analytics-tab.tsx (75 lines)
│ ├── legals-tab.tsx (70 lines)
│ └── system-tab.tsx (65 lines)
└── types.ts (shared interfaces)
The plan editor page got a similar treatment: each section (basic info, pricing, features, resource limits, user limits, app assignments) became its own component with a shared types file.
SHARED HOOKS: ELIMINATING DUPLICATION
During the audit, we found the same patterns repeated across dozens of files. Three hooks eliminated hundreds of lines of duplicated code:
useDebounce
Search inputs across 15+ admin pages all had their own debounce implementation. One hook replaced them all:
const debouncedSearch = useDebounce(searchTerm, 300);
usePagination
Every table page had manual pagination state management. Now:
const { page, pageSize, offset, setPage, setPageSize, totalPages } =
usePagination({ totalItems: data.length });
useConfirmDialog
Delete confirmations were implemented inconsistently — some used window.confirm(), others had custom modals, some had no confirmation at all. The hook standardizes the pattern:
const deleteDialog = useConfirmDialog(handleDelete);
// ...
<Button onClick={() => deleteDialog.open()}>Delete</Button>
<ConfirmDialog {...deleteDialog} />
THE RESULTS
| Metric | Before | After |
|---|---|---|
| Files >500 lines | 7 | 0 |
| Files >400 lines | 12 | 4 |
| Duplicated fetch patterns | ~50 | 0 (useFetch hook) |
| Duplicated debounce logic | ~15 | 0 (useDebounce hook) |
| Inconsistent confirmations | ~20 | 0 (useConfirmDialog) |
| Job posting form duplication | 2 files (1040 lines) | 1 shared form (520 lines) |
WHAT WE DIDN'T CHANGE
Just as important as what we refactored is what we left alone. We didn't:
- Add premature abstractions — If a pattern appeared in only 2 places, we left it. Three or more warranted a shared component
- Refactor for the sake of it — Working code that was already under 400 lines stayed as-is
- Break the API surface — All refactoring was internal. API routes, component props, and hook signatures remained backward-compatible
THE TOOLING THAT MADE IT POSSIBLE
Our workflow visualization tool (master-workflow-audit/index.html) was invaluable. After every refactoring session, we regenerated the dependency graph to verify we hadn't introduced circular dependencies or orphaned modules. The data extractor scans all 1,494 nodes in under 3 seconds and produces a navigable visualization of the entire architecture.
If you're managing a growing codebase, invest in tooling that gives you architectural visibility. You can't fix what you can't see.



