Skip to main content
From 7 God Files to Zero: Refactoring a 162K-Line Codebase

From 7 God Files to Zero: Refactoring a 162K-Line Codebase

Andrius LukminasAndrius LukminasFebruary 11, 20269 min read36 views

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:

  1. campaign-sender.ts (680 lines) — Email campaign orchestration, SMTP connections, template rendering, and delivery tracking all in one file
  2. imap-client.ts (620 lines) — IMAP connection management, mailbox operations, message parsing, and search functionality
  3. webauthn.ts (550 lines) — WebAuthn registration, authentication, credential management, and challenge generation
  4. tenant-provisioner.ts (530 lines) — Database creation, K8s namespace setup, DNS provisioning, and SSL certificate generation
  5. admin/settings/page.tsx (510 lines) — Six tabs of settings UI in a single component
  6. admin/plans/[id]/page.tsx (505 lines) — Plan editing with 7 sections in one file
  7. 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

MetricBeforeAfter
Files >500 lines70
Files >400 lines124
Duplicated fetch patterns~500 (useFetch hook)
Duplicated debounce logic~150 (useDebounce hook)
Inconsistent confirmations~200 (useConfirmDialog)
Job posting form duplication2 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.

Related Articles

Comments

0/5000 characters

Comments from guests require moderation.