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:
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:
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:
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:
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