Resolving follow status with a separate query per user card is a textbook N+1 problem. A feed page showing 50 user cards fires 50 individual isFollowing database calls — each a round-trip that adds 5–20ms of latency in a cloud deployment. At 100ms/page the cumulative cost is 5 seconds of blocked rendering. Under ISO 25010:2011 performance-efficiency, this is a structural scalability defect: response time degrades linearly with the number of items displayed, making any growth in user density directly punishing.
Medium because the performance impact is proportional to page size and grows predictably, but it degrades user experience gradually rather than causing immediate failures.
Preload follow status alongside the user list in a single query, or use DataLoader to batch all isFollowing checks for a given page render into one database round-trip.
// lib/db.ts — preload approach
export async function getUsersWithFollowStatus(viewerId: string, limit: number) {
const users = await db.user.findMany({
take: limit,
select: {
id: true,
name: true,
avatar: true,
followers: {
where: { followerId: viewerId },
select: { id: true }
}
}
})
return users.map(u => ({
...u,
isFollowing: u.followers.length > 0,
followers: undefined
}))
}
This collapses N+1 queries into a single JOIN, regardless of how many user cards are rendered.
ID: community-social-engagement.follow-graph.relationship-state-resolution
Severity: medium
What to look for: Enumerate all relevant files and Examine how the UI determines whether the current user follows another user. Look for queries in profile pages or user cards. Identify if there's an N+1 problem: does the component query isFollowing() for every user card displayed, or is there batch loading (DataLoader pattern) or a single optimized query?
Pass criteria: At least 1 implementation must be present. Relationship state is resolved efficiently. User cards in lists do not trigger individual follow-check queries. Either: (a) follow status is preloaded with the user list, (b) DataLoader batches queries, or (c) a single JOIN query fetches all follow status at once.
Fail criteria: Each user card triggers a separate query to check follow status (N+1 problem), or follow status requires additional round-trips to the server.
Skip (N/A) when: Follow relationships are not displayed in the UI.
Detail on fail: "User list page makes a separate API call to /api/users/[id]/is-following for each user card — will load slowly with 50+ users"
Remediation: Batch load or preload relationship state:
// Option 1: Preload in the list query
export async function getUsers(limit: number, viewerId: string) {
const users = await db.user.findMany({
take: limit,
select: {
id: true,
name: true,
followers: {
where: { followerId: viewerId },
select: { id: true }
}
}
})
return users.map(u => ({
...u,
isFollowing: u.followers.length > 0
}))
}
// Option 2: DataLoader to batch follow checks
import DataLoader from 'dataloader'
const followLoader = new DataLoader(async (userIds: string[]) => {
const follows = await db.follow.findMany({
where: { followerId: viewerId, followedId: { in: userIds } }
})
const followMap = new Map(follows.map(f => [f.followedId, true]))
return userIds.map(id => followMap.get(id) || false)
})