God Files in Vibe-Coded Projects: When One File Does Everything - VibeDoctor 
← All Articles 🧹 Code Quality Medium

God Files in Vibe-Coded Projects: When One File Does Everything

AI generates 500-line files that handle routing, data, UI, and business logic. Learn how to detect and decompose monolithic files.

QUA-008 QUA-013

Quick Answer

A god file is a single source file that handles multiple unrelated concerns - routing, database access, business logic, and UI rendering all mixed together. AI tools like Bolt, Lovable, and v0 generate them because they satisfy the prompt in one pass. Files over 400–500 lines are a strong signal. The fix is to decompose by responsibility: one file per concern, named after what it does.

What Is a God File and Why Should You Care?

The term "god file" parallels the "god object" anti-pattern from object-oriented design. A god file knows everything and does everything. It imports your database client, defines your route handlers, contains your business logic, formats your responses, and sometimes renders JSX - all in one 600-line module.

God files are the file-level equivalent of god functions. Where a god function handles too many responsibilities in too many lines, a god file concentrates too many modules of concern in a single file. The result is the same: coupling that makes changes risky, a surface area so large that understanding the full impact of an edit is impossible, and tests that require mocking the entire world.

Stack Overflow's 2024 Developer Survey found that "maintaining or improving existing code" is the most time-consuming task for 57% of developers. God files are one of the primary reasons - navigating, understanding, and safely changing a monolithic 500-line file takes disproportionately long compared to the same logic organized into five 100-line files with clear boundaries.

How AI Tools Create God Files

The AI-generated god file pattern is predictable. You prompt: "Build a user dashboard with authentication." Cursor or Lovable generates one file with everything:

// ❌ BAD - pages/dashboard.tsx: routing + auth + data + UI in one file (500+ lines)

import { useState, useEffect } from 'react';
import { createClient } from '@supabase/supabase-js';
import Stripe from 'stripe';
import { useRouter } from 'next/navigation';

const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, ...);
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, ...);

// Auth logic mixed with component
export default function Dashboard() {
  const [user, setUser] = useState(null);
  const [subscription, setSubscription] = useState(null);
  const [invoices, setInvoices] = useState([]);
  const router = useRouter();

  useEffect(() => {
    supabase.auth.getUser().then(({ data }) => {
      if (!data.user) { router.push('/login'); return; }
      setUser(data.user);

      // Database access inside component
      supabase.from('profiles').select('*')
        .eq('id', data.user.id).single()
        .then(({ data: profile }) => {
          if (profile?.stripeCustomerId) {
            // Stripe calls inside component
            stripe.subscriptions.list({ customer: profile.stripeCustomerId })
              .then(subs => setSubscription(subs.data[0]));
            stripe.invoices.list({ customer: profile.stripeCustomerId })
              .then(inv => setInvoices(inv.data));
          }
        });
    });
  }, []);

  // 300 more lines of JSX, utility functions,
  // form handlers, and business logic...
}

This file violates every separation-of-concerns principle. The Stripe secret key is initialized at the module level in a client component. Authentication logic is entangled with rendering. Data-fetching is nested inside a component effect. Changing the Stripe integration requires touching the UI file.

The Refactored Structure: One File, One Concern

// ✅ GOOD - Separated by responsibility

// lib/supabase.ts - client initialization only
export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);

// lib/stripe.ts - Stripe server client only (never imported by client components)
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-06-20',
});

// hooks/useAuth.ts - authentication logic only
export function useAuth() {
  const [user, setUser] = useState(null);
  const router = useRouter();
  useEffect(() => {
    supabase.auth.getUser().then(({ data }) => {
      if (!data.user) router.push('/login');
      else setUser(data.user);
    });
  }, []);
  return user;
}

// services/billing.ts - Stripe data access only (server-side)
export async function getSubscription(stripeCustomerId: string) {
  const subs = await stripe.subscriptions.list({ customer: stripeCustomerId });
  return subs.data[0] ?? null;
}

// app/dashboard/page.tsx - rendering only, thin orchestrator
import { useAuth } from '@/hooks/useAuth';
export default function Dashboard() {
  const user = useAuth();
  if (!user) return null;
  return <DashboardView userId={user.id} />;
}

Each file now has a single, clear purpose. The Stripe client never reaches a browser bundle. Authentication is reusable across the entire app. The UI file is a thin orchestrator.

The Warning Signs: How to Spot God Files

Not every large file is a god file, and not every god file is large. The diagnostic is responsibility count, not line count. That said, line count is a reliable proxy. SonarSource data shows that files over 400 lines contain 3.2x more bugs per line than files under 200 lines.

Look for these patterns in Bolt, Lovable, and v0 output specifically:

Decomposition Strategies for Real Projects

What lives in the god file Extract to Directory
Database queries user.repository.ts lib/repositories/
Business logic / calculations billing.service.ts lib/services/
React state + side effects useUser.ts hooks/
Type definitions user.types.ts types/
Constants / config plans.config.ts config/
Utility / helper functions format.utils.ts lib/utils/
API route handler route.ts app/api/users/

How to Fix God Files Without Breaking Everything

Decomposing a god file safely requires a structured approach:

  1. Start by reading the full file and listing every distinct responsibility. Write them down. If you find more than three, you have a god file.
  2. Extract the lowest-risk piece first. Utility functions and constants have no dependencies on the rest of the file. Move them first to a new module and update the import. Run your tests.
  3. Extract data access next. Database queries and API calls can move to a service or repository module. This step usually reveals that the same query is duplicated several times - a second bug god files create.
  4. Extract hooks from components last. State management logic in React components is the most entangled. Extract it once the data layer is clean.
  5. Do not extract and refactor simultaneously. Move code first, verify behavior is unchanged, then clean up the extracted module.

Tools like VibeDoctor (vibedoctor.io) automatically scan your codebase for god files and flag specific file paths and line numbers where single-responsibility violations are most severe. Free to sign up.

FAQ

Is 500 lines always too long for a single file?

Not always - a complex algorithm or generated schema file may legitimately be long. The issue is mixed responsibilities, not length alone. A 500-line file with one clear responsibility is manageable. A 300-line file handling routing, auth, data access, and UI is a god file.

How do Bolt and Lovable handle file organization differently?

Lovable tends to generate Next.js projects with reasonable initial structure, but grows god files as you add features through follow-up prompts. Bolt is more likely to generate all-in-one files from the first prompt. Cursor is most controllable - you can use cursor rules to enforce file structure conventions.

Does Next.js App Router help or hurt?

App Router's file-based routing encourages separation because route handlers and page components live in separate files by convention. However, AI tools often ignore this and put server actions, data fetching, and UI all inside a single page.tsx. The convention helps only if the AI follows it.

Should I have one component per file?

As a default, yes. Small sub-components that are only used once by a parent can live in the same file temporarily. But if a sub-component grows, or if another component needs it, it should move to its own file immediately.

What is the fastest way to find god files in my project?

Run a line count on every file in your src/ directory and sort descending. Files over 400 lines are candidates. Then look for files importing more than 5 different category of modules (UI + database + auth + email + payments). Automated scanners like VibeDoctor produce this report automatically.

Scan your codebase for this issue - free

VibeDoctor checks for QUA-008, QUA-013 and 128 other issues across 15 diagnostic areas.

SCAN MY APP →
← Back to all articles View all 129+ checks →