Open your Next.js app. Navigate to any page with a few components. Now count how many times SELECT * FROM users WHERE id = ? runs.
You probably don't know. Nobody does. Because every request returns 200, every component renders correctly, and your app "works."
But behind that working page, the same query might be running 5 times. Once for the navbar. Once for the sidebar. Once for the main content. Once for the settings panel. And once more because React Strict Mode ran your effect twice.
That's 5 identical round trips to your database for data that hasn't changed in the last 200 milliseconds.
Here's a typical Next.js API route:
// app/api/user/route.ts
export async function GET() {
const user = await prisma.user.findUnique({
where: { id: session.userId }
});
return NextResponse.json(user);
}
Simple. Clean. Ships code review.
Now here's the problem. This endpoint gets called from:
| Component | Why it fetches /api/user |
|---|
| Layout navbar | Show the user's name |
| Dashboard page | Display user stats |
| Settings page | Pre-fill the form |
| Profile sidebar | Show the avatar |
| Notification bell | Check preferences |
Each component independently calls fetch('/api/user'). Each call hits the API route. Each route runs prisma.user.findUnique(). Each query goes to PostgreSQL and comes back.
Five components. Five requests. Five queries. One user record.
It's not bad code. It's the natural result of component-based architecture.
In React and Next.js, components are self-contained. A <UserAvatar> fetches its own data. A <PermissionGate> checks permissions independently. This is good software design.
But it means every component that needs user data makes its own request. And nobody sees the duplication because:
DevTools shows a flat list.
You see 5 requests to /api/user, but they're mixed in with 20 other requests. You'd have to manually notice they're all the same.
Each request succeeds.
200 OK, correct data, fast enough. No error to catch. No warning to trigger.
Different developers wrote different components.
Developer A built the navbar. Developer B built the sidebar. Neither knows the other fetches /api/user.
Code review doesn't catch it.
Each component's code is correct in isolation. The duplication only exists at runtime, when all components render on the same page.
"So what? Queries are fast."
Let's do the math:
| Metric | Value |
|---|
| Duplicate queries per page load | 5 |
| Time per query (warm database) | 30ms |
| Daily active users | 1,000 |
| Page loads per session | 10 |
| Unnecessary queries per day | 50,000 |
| Wasted latency per page | 150ms |
And if your database is under load, those "fast" 30ms queries start taking 200ms. Suddenly your app feels sluggish for no obvious reason.
Each duplicate also means an extra HTTP request, an extra API route invocation, an extra connection from the pool, and extra memory allocation. All for data you already have.
React Strict Mode (enabled by default in Next.js) runs every useEffect twice in development. Every fetch() in an effect fires twice.
Your 5 duplicate requests become 10. Your 5 database queries become 10.
The confusing part: you see 10 requests to /api/user and think "my app is broken." But 5 are Strict Mode ghosts that don't happen in production. And the other 5 are real duplicates. You can't tell which is which from DevTools.
"Use React Query / SWR" — Deduplicates on the client side. Works well, but requires refactoring every component to use the same query key. Doesn't help with Server Components.
"Lift the fetch to a parent" — Pass data down as props. Works, but breaks component encapsulation.
"Use a shared layout loader" — Fetch in layout.tsx, share via Context. Good pattern, but requires architectural planning upfront.
"Add caching" — Redis? In-memory? What TTL? Now you have cache invalidation problems.
All valid. But they all require you to already know which queries are duplicated.
That's the hard part. You can't fix what you can't see.
This is where observability at the API level becomes critical. Not application-level logging (too verbose). Not infrastructure monitoring (too high-level). You need to see your API as a connected system — which endpoints are called together, which queries they share, and how they relate to what the user actually did.
Brakit is an open-source dev tool that does exactly this. You add one line to your Next.js app, and it captures every HTTP request, database query, and external fetch — grouped by user action.
Instead of a flat list of 10 requests in DevTools, you see everything nested, timed, and connected:

Duplicates are flagged automatically. SELECT users running across multiple endpoints is highlighted. And React Strict Mode duplicates are marked separately — "React Strict Mode duplicate — does not happen in production" — so you know which to fix and which to ignore.
Once you can see the duplication, the fixes are straightforward:
Use React Query or SWR so multiple components share one request:
// hooks/useUser.ts
export function useUser() {
return useQuery({
queryKey: ['user'],
queryFn: () => fetch('/api/user').then(r => r.json()),
staleTime: 30_000,
});
}
Every component that calls useUser() shares the same request and cache.
Extract shared queries to a data access layer:
// lib/data/user.ts
import { cache } from 'react';
export const getUser = cache(async (userId: string) => {
return prisma.user.findUnique({ where: { id: userId } });
});
React's cache() deduplicates within a single server render. For cross-request caching, use unstable_cache or a dedicated cache layer.
Fetch once, share via context:
// app/layout.tsx
export default async function Layout({ children }) {
const user = await getUser(session.userId);
return (
<UserProvider user={user}>
{children}
</UserProvider>
);
}
Duplicate queries are the most common backend performance issue in component-based architectures. They're invisible in code review, undetectable in unit tests, and only manifest at runtime when real components compose on real pages.
The fix isn't complicated. The hard part is seeing the problem.
If you want to see what your Next.js app actually does behind every page load — which queries run, which are duplicated, and which components are responsible — try Brakit. One command, zero config, and your duplicate queries become impossible to miss.
It's open source, runs locally, and does nothing in production. Your data never leaves your machine.
Get started in 2 minutes →