Connection List Scalability
Why it matters
Fetching entire follower lists in a single unbounded query is a time-bomb proportional to your most-followed user. At 10,000 followers, a findMany with no take limit loads tens of thousands of joined records into memory, blows through Supabase row limits, and produces multi-second response times. This is a performance-efficiency failure under ISO 25010:2011 — the system cannot maintain acceptable response times as data volume grows. Power users are exactly the accounts that drive retention; making their profiles unusably slow is a direct business impact.
Severity rationale
High because unbounded follower queries fail visibly at scale, producing timeouts and errors precisely for the high-profile users whose profiles receive the most traffic.
Remediation
Switch follower and following list endpoints to cursor-based pagination in app/api/users/[id]/followers/route.ts. Cap the maximum page size at 100 and return a nextCursor so the client can load more incrementally.
// app/api/users/[id]/followers/route.ts
export async function GET(req: Request, { params }: { params: { id: string } }) {
const url = new URL(req.url)
const cursor = url.searchParams.get('cursor') ?? undefined
const limit = Math.min(parseInt(url.searchParams.get('limit') ?? '20'), 100)
const rows = await db.follow.findMany({
where: { followedId: params.id },
take: limit + 1,
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
orderBy: { createdAt: 'desc' },
select: { id: true, follower: true }
})
const hasMore = rows.length > limit
const items = hasMore ? rows.slice(0, limit) : rows
return Response.json({
followers: items.map(r => r.follower),
nextCursor: hasMore ? items[items.length - 1]?.id : null
})
}
Detection
-
ID:
connection-list-pagination -
Severity:
high -
What to look for: Enumerate all relevant files and Examine routes that display followers/following lists (e.g.,
/users/[id]/followers,/users/[id]/following). Check whether queries use pagination: cursor-based pagination (Relay-style) or limit/offset. Verify that the implementation does NOT fetch all followers at once (which would fail for power users with thousands of followers). -
Pass criteria: At least 1 conforming pattern must exist. Followers and following lists use pagination. Queries include
limitand eitheroffsetor cursor parameters. The UI supports loading more results incrementally. -
Fail criteria: Followers lists fetch all results in a single query, or no pagination mechanism exists, or the API returns unlimited results.
-
Skip (N/A) when: Follower/following lists are not displayed to users.
-
Detail on fail:
"GET /api/users/[id]/followers returns all followers without pagination — query fails for users with 10k+ followers"or"No limit parameter enforced on followers endpoint" -
Remediation: Implement cursor-based or offset pagination for relationship lists:
// app/api/users/[id]/followers/route.ts export async function GET(req: Request, { params }) { const { limit = '20', cursor } = Object.fromEntries( new URL(req.url).searchParams ) const followers = await db.follow.findMany({ where: { followedId: params.id }, take: Math.min(parseInt(limit), 100), skip: cursor ? 1 : 0, cursor: cursor ? { id: cursor } : undefined, orderBy: { createdAt: 'desc' }, select: { follower: true } }) return Response.json({ followers: followers.map(f => f.follower), nextCursor: followers[followers.length - 1]?.id || null }) }
External references
- iso-25010:2011 · performance-efficiency — Time Behaviour
Taxons
History
- 2026-04-18·v1.0.0·Initial import from community-social-engagement·automated