perf(practice): eliminate redundant getViewerId and user lookups

Refactor getPlayersWithSkillData() to return viewerId and userId
along with players, avoiding 2 redundant calls in Practice page:
- Previous: 3 calls to getViewer() (via getViewerId) + 2 user lookups
- Now: 1 call to getViewer() + 1 user lookup

This should reduce Practice page SSR time by eliminating duplicate
auth checks and database queries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2026-01-24 05:46:03 -06:00
parent dbc45b97b0
commit 1914bcf9d0
3 changed files with 22 additions and 33 deletions

View File

@ -8,8 +8,8 @@ import { getPlayersWithSkillData } from '@/lib/curriculum/server'
*/
export async function GET() {
try {
const players = await getPlayersWithSkillData()
return NextResponse.json({ players })
const result = await getPlayersWithSkillData()
return NextResponse.json({ players: result.players })
} catch (error) {
console.error('Failed to fetch players with skill data:', error)
return NextResponse.json({ error: 'Failed to fetch players' }, { status: 500 })

View File

@ -1,25 +1,6 @@
import { eq } from 'drizzle-orm'
import { db, schema } from '@/db'
import { getPlayersWithSkillData } from '@/lib/curriculum/server'
import { getViewerId } from '@/lib/viewer'
import { PracticeClient } from './PracticeClient'
/**
* Get or create user record for a viewerId (guestId)
*/
async function getOrCreateUser(viewerId: string) {
let user = await db.query.users.findFirst({
where: eq(schema.users.guestId, viewerId),
})
if (!user) {
const [newUser] = await db.insert(schema.users).values({ guestId: viewerId }).returning()
user = newUser
}
return user
}
/**
* Practice page - Server Component
*
@ -30,13 +11,8 @@ async function getOrCreateUser(viewerId: string) {
*/
export default async function PracticePage() {
// Fetch players with skill data directly on server - no HTTP round-trip
const players = await getPlayersWithSkillData()
// Returns players, viewerId, and userId in a single call to avoid redundant lookups
const { players, viewerId, userId } = await getPlayersWithSkillData()
// Get viewer ID for session observation
const viewerId = await getViewerId()
// Get database user ID for parent socket notifications
const user = await getOrCreateUser(viewerId)
return <PracticeClient initialPlayers={players} viewerId={viewerId} userId={user.id} />
return <PracticeClient initialPlayers={players} viewerId={viewerId} userId={userId} />
}

View File

@ -88,6 +88,15 @@ export async function getPlayersForViewer(): Promise<Player[]> {
return players
}
/**
* Result from getPlayersWithSkillData including context for page rendering
*/
export interface PlayersWithSkillDataResult {
players: StudentWithSkillData[]
viewerId: string
userId: string
}
/**
* Get all players for the current viewer with enhanced skill data.
*
@ -97,11 +106,13 @@ export async function getPlayersForViewer(): Promise<Player[]> {
* - skillCategory: Computed highest-level skill category
* - intervention: Intervention data if student needs attention
*
* Also returns viewerId and userId to avoid redundant calls in page components.
*
* Performance: Uses batched queries to avoid N+1 query patterns.
* - Single query for all skill mastery records across all players
* - Single query for session history across all players needing intervention
* - Returns viewerId/userId to avoid redundant getViewerId() calls
*/
export async function getPlayersWithSkillData(): Promise<StudentWithSkillData[]> {
export async function getPlayersWithSkillData(): Promise<PlayersWithSkillDataResult> {
const viewerId = await getViewerId()
// Get or create user record
@ -114,6 +125,8 @@ export async function getPlayersWithSkillData(): Promise<StudentWithSkillData[]>
user = newUser
}
const userId = user.id
// Get player IDs linked via parent_child table
const linkedPlayerIds = await db.query.parentChild.findMany({
where: eq(parentChild.parentUserId, user.id),
@ -135,7 +148,7 @@ export async function getPlayersWithSkillData(): Promise<StudentWithSkillData[]>
}
if (players.length === 0) {
return []
return { players: [], viewerId, userId }
}
// OPTIMIZATION: Batch query all skill mastery records for all players at once
@ -186,7 +199,7 @@ export async function getPlayersWithSkillData(): Promise<StudentWithSkillData[]>
// Intervention badges are helpful but not critical for initial render.
// They can be computed lazily on the client if needed.
// This avoids N additional database queries for session history.
return playersWithBasicSkills
return { players: playersWithBasicSkills, viewerId, userId }
}
// Re-export the individual functions for granular prefetching