Back to blog
·6 min read

How to Debug N+1 Queries in Claude Code Generated Next.js and Supabase Applications

Claude Code and AI agents write syntactically correct code that works locally—but hide performance disasters like N+1 queries. This guide shows you exactly how to spot and fix them.

The Silent Performance Killer in AI-Generated Code

You ask Claude Code to build a user dashboard. The agent returns clean, working code. You test it locally. Everything works. You deploy to production and watch your database connections spike to 100% usage on day one.

This isn't a fictional scenario—it's happening to developers building with Claude Code, GitHub Copilot, and other AI coding assistants every week. The problem: AI agents generate syntactically correct code that passes local testing but contains hidden N+1 queries and missing database indexes that only show up under real-world load.

Here's why this happens and how to catch it before your production database melts.

Why AI Agents Miss N+1 Queries

N+1 queries occur when your code runs one initial query, then runs additional queries for each row returned—a pattern that scales exponentially with data volume. A query that takes 50ms with 10 test rows takes 50 seconds with 1000 production rows.

AI agents struggle with this for three specific reasons:

  • Local development databases contain test data (usually 10-100 rows). N+1 patterns are invisible at this scale. An agent generating a for-loop over results looks fine when you're iterating 10 times.
  • AI training data includes plenty of examples of N+1 queries mixed with optimized patterns. Without explicit constraints, agents default to the simpler pattern they see most frequently.
  • Database observability isn't built into the prompt. If you don't explicitly ask the agent to add query logging or use database inspection tools, it won't. The generated code works, so it passes your local testing.
  • The Real-World Impact: Context Window Cost

    Using Claude Code with its 200k context window, many developers are hitting their weekly usage caps by Wednesday. A significant portion of this bloat comes from:

  • Iterating on N+1 queries that appear to work locally
  • Adding indexes after the fact (each iteration regenerates the whole schema)
  • Debugging production logs instead of catching issues pre-deployment
  • The math is brutal: if you're paying $20/month for Claude API and burning through your quota on unoptimized code, you're not actually saving development time—you're compounding the cost.

    How to Spot N+1 Queries in Claude-Generated Code

    Here's what to look for when reviewing AI-generated Next.js + Supabase code:

    ### Pattern 1: The Loop Query

    This is the most common N+1 pattern:

    ```javascript

    // BAD - This is N+1 if users have orders

    const users = await supabase

    .from('users')

    .select('*')

    .limit(10);

    for (const user of users.data) {

    const orders = await supabase

    .from('orders')

    .select('*')

    .eq('user_id', user.id);

    // Process orders

    }

    ```

    Every user triggers a separate database query. With 100 users, that's 101 total queries instead of 1.

    ### Pattern 2: Missing Indexes

    AI agents often skip indexes because they're not strictly required for code to work:

    ```javascript

    // This Supabase migration runs but will be slow

    create table orders (

    id uuid primary key,

    user_id uuid not null,

    created_at timestamp

    );

    // Missing: create index on user_id

    ```

    Query the same user_id repeatedly and you're doing full table scans every time.

    ### Pattern 3: Implicit N+1 in Select Statements

    In Supabase, forgetting to join relationships creates hidden N+1:

    ```javascript

    // INEFFICIENT - generates N queries for related data

    const users = await supabase

    .from('users')

    .select('*, orders(*)'); // If you have 100 users, this still iterates

    // BETTER - explicit join

    const users = await supabase

    .from('users')

    .select(`

    *,

    orders!inner(*)

    `)

    .eq('status', 'active');

    ```

    The Fix: Instrument Your Local Development Environment

    Before pushing Claude Code output to production, add visibility:

    ### Step 1: Enable Supabase Query Logging

    In your local environment, activate query logging:

    ```javascript

    // Create a Supabase client with query stats

    const supabase = createClient(url, key, {

    db: { schema: 'public' },

    auth: {

    persistSession: true,

    }

    });

    // Add logging middleware

    const originalFetch = supabase.from.bind(supabase);

    supabase.from = function(table) {

    console.time(`Query: ${table}`);

    const result = originalFetch(table);

    console.timeEnd(`Query: ${table}`);

    return result;

    };

    ```

    This reveals query counts and timing instantly.

    ### Step 2: Use Database Inspector Tools

    Run this query in Supabase SQL editor to spot slow queries:

    ```sql

    SELECT

    calls,

    total_time,

    mean_time,

    query

    FROM pg_stat_statements

    WHERE query NOT LIKE '%pg_stat%'

    ORDER BY total_time DESC

    LIMIT 10;

    ```

    This shows you which queries are running most frequently and taking the most time.

    ### Step 3: Load Test with Realistic Data

    The single most effective fix: populate your local database with production-scale test data.

    ```javascript

    // Generate 1000 test users instead of 10

    for (let i = 0; i < 1000; i++) {

    await supabase

    .from('users')

    .insert({ email: `user${i}@test.com` });

    }

    ```

    Now run your Claude-generated code. Watch the query counts climb. This is where N+1 patterns become visible.

    Preventing N+1 in Claude Code Prompts

    When asking Claude Code to generate features, be explicit about performance:

    Instead of:

    "Build a user dashboard that shows orders and order details."

    Try:

    "Build a user dashboard using a single Supabase query with joins to fetch users and their orders. Use select() with explicit relationships. Add database indexes on foreign keys. Log all queries to the console to verify no N+1 patterns exist."

    This constraint forces the agent to think about query optimization upfront.

    Why This Matters for AI-Assisted Development

    The future of development is AI-generated code—but AI-generated code without observability is a liability in production. The best practices are:

  • Always add logging to generated code
  • Load test with realistic data volumes before deployment
  • Review generated database schemas for missing indexes
  • Question any loop that touches the database
  • Many developers using Claude Code are hitting context limits and cost overruns not because the tool is inefficient, but because they're iterating on unoptimized code that passes local testing. A structured development process—like the one ZipBuild provides through AI-powered scaffold generation—includes performance validation and database optimization built into the initial generation step.

    Quick Action Checklist

    Before deploying Claude Code output to production:

  • Run query logging and verify you have fewer queries than you have rows of data
  • Check pg_stat_statements for unexpected query patterns
  • Load test with 10x your expected data volume
  • Verify all foreign key columns have indexes
  • Use EXPLAIN ANALYZE on your slowest queries
  • Catch these issues in development, and you'll preserve both your Supabase quota and your Claude Code context window for actual feature work instead of debugging.

    Try the free discovery chat at zipbuild.dev to see how production-ready scaffold generation can include performance optimization and database best practices from the start.

    Written by ZipBuild Team

    Ready to build with structure?

    Try the free discovery chat and see how ZipBuild architects your idea.

    Start Building