| name | development-guidelines |
| description | Reference for Dockside/Riza architecture patterns, TDD methodology, coding standards, data serialization, and code reuse practices. Use when creating new files, writing tests, making architectural decisions, or optimizing UI performance. Promotes Context7 usage for best practices. |
| allowed-tools | Read, Grep, Glob, mcp__context7__resolve-library-id, mcp__context7__query-docs |
Development Guidelines
Comprehensive reference for Test-Driven Development (TDD), data serialization best practices, architectural patterns, coding standards, and code reuse for the Dockside/Riza sailing club management system.
Core Philosophy: Test-first development with maximum code reuse, optimized data serialization, and library best practices via Context7.
When to Use This Skill
Automatically reference these guidelines when:
- Starting ANY new feature or bug fix (TDD required)
- Fetching data from database (optimize serialization)
- Passing props to components (check prop size)
- Creating new files or components
- Writing or updating tests
- Making architectural decisions
- Implementing RBAC or authentication
- Working with Prisma/database
- Using a library pattern for the first time (check Context7)
- Unsure of best practice (query Context7)
๐ Context7: Always Check Best Practices First
Critical Rule: Query Context7 Before Implementing
BEFORE writing code with ANY library, check Context7 for current best practices.
When to Use Context7
โ Always use Context7 when:
- Using a Next.js pattern for the first time
- Implementing React hooks or patterns
- Writing Prisma queries
- Setting up authentication flows
- Implementing form validation
- Using Tailwind patterns
- Working with any npm library
- Unsure if your approach is optimal
How to Use Context7
// Step 1: Resolve library ID (if not known)
const libs = await resolveLibraryId("next.js", "server components data fetching")
// Step 2: Query for best practices
const docs = await queryDocs("/vercel/next.js", "server component data fetching patterns")
// Step 3: Apply recommended patterns to your code
Common Queries by Feature Area
| Working on... | Library ID | Example Query |
|---|---|---|
| Server Components | /vercel/next.js |
"server component data fetching patterns" |
| API Routes | /vercel/next.js |
"app router api route best practices" |
| React Hooks | /facebook/react |
"useEffect cleanup function patterns" |
| Form Handling | /react-hook-form/react-hook-form |
"form validation with zod schema" |
| Prisma Queries | /prisma/prisma |
"query optimization with select and include" |
| Prisma Transactions | /prisma/prisma |
"transaction best practices error handling" |
| Authentication | /nextauthjs/next-auth |
"session management patterns" |
| Tailwind Components | /tailwindlabs/tailwindcss |
"responsive design patterns" |
| Testing React | /testing-library/react-testing-library |
"testing async components" |
| Vitest Mocking | /vitest-dev/vitest |
"mocking modules best practices" |
Context7 Integration in Workflow
Development Flow with Context7:
1. Understand requirement
2. ๐ Query Context7 for best practice
3. Apply recommended pattern
4. Write test (TDD)
5. Implement using best practice
6. Verify against Context7 recommendation
Example: Server Component Data Fetching
// โ BEFORE Context7: Might use outdated pattern
export default async function Page() {
const data = await fetch('/api/data').then(r => r.json())
return <div>{data.value}</div>
}
// โ
AFTER Context7 query: "next.js server component data fetching"
// Learned: Use Prisma directly, optimize with select
export default async function Page() {
const data = await prisma.model.findMany({
select: {
id: true,
name: true,
// Only fetch what's needed
}
})
return <div>{data.map(...)}</div>
}
Example: React Hook Pattern
// โ BEFORE Context7: Might miss cleanup
useEffect(() => {
fetchData()
}, [dependency])
// โ
AFTER Context7 query: "react useEffect cleanup patterns"
// Learned: Always clean up async operations
useEffect(() => {
let cancelled = false
async function fetchData() {
const result = await api.getData()
if (!cancelled) {
setData(result)
}
}
fetchData()
return () => {
cancelled = true
}
}, [dependency])
Example: Prisma Query Optimization
// โ BEFORE Context7: N+1 query problem
const events = await prisma.event.findMany()
for (const event of events) {
event.club = await prisma.club.findUnique({ where: { id: event.clubId }})
}
// โ
AFTER Context7 query: "prisma query optimization select include"
// Learned: Use include for relations, select for specific fields
const events = await prisma.event.findMany({
select: {
id: true,
name: true,
club: {
select: {
id: true,
name: true,
}
}
}
})
Context7 Best Practice Checklist
Before implementing any pattern:
- Query Context7 for current best practices
- Compare recommended pattern with your approach
- Apply best practice if different
- Document why you chose a pattern (if deviating from Context7)
- Update your knowledge of library patterns
Proactive Context7 Usage
Make it a habit:
- ๐ "Let me check Context7 for the latest pattern..."
- ๐ "Is this the recommended way? Let me verify..."
- ๐ "Before I implement, let me see what Context7 says..."
Remember: Library best practices evolve. Context7 ensures you're using current, recommended patterns, not outdated approaches.
โก Data Serialization & UI Performance (CRITICAL)
The 100MB Prop Problem
Issue: Passing large database objects (100MB) to UI components causes:
- Extreme slowdown during serialization
- Client-side memory bloat
- Poor user experience
- Network transfer overhead
Solution: Fetch only what you need, transform close to the source.
Data Serialization Rules (7 Essential)
| # | Rule | Example | Impact |
|---|---|---|---|
| 1 | โ
Use select to fetch only needed fields |
select: { id, name, email } |
90%+ size reduction |
| 2 | โ Transform data in server components | Filter/map before passing to client | No client-side overhead |
| 3 | โ Use pagination for large datasets | Limit queries to 50-100 records | Constant memory usage |
| 4 | โ NEVER pass entire database models | Don't pass raw Prisma results | Avoid nested relations |
| 5 | โ
Use include strategically |
Only include what UI needs | Control relation depth |
| 6 | โ Create view models for UI | Map DB โ UI-specific types | Clean separation |
| 7 | โ Monitor prop sizes in dev | Log prop size if user is debug user | Early detection |
Pattern: Optimized Data Fetching
// โ BAD: Fetching everything (100MB+)
const event = await prisma.event.findUnique({
where: { id },
include: {
club: true,
raceDays: {
include: {
registrations: {
include: {
sailor: {
include: {
user: true,
boats: true,
results: true, // ๐จ HUGE!
}
}
}
},
races: {
include: {
results: true, // ๐จ MASSIVE!
}
}
}
},
classes: {
include: {
class: true
}
}
}
})
// Passing 100MB+ to client component ๐ฅ
return <EventDetails event={event} />
// โ
GOOD: Fetch only what UI needs (90KB)
const event = await prisma.event.findUnique({
where: { id },
select: {
id: true,
name: true,
type: true,
startDate: true,
endDate: true,
club: {
select: {
id: true,
name: true,
timezone: true,
}
},
raceDays: {
select: {
id: true,
date: true,
status: true,
_count: {
select: {
registrations: true, // Just count, not full data
}
}
},
take: 10, // Paginate if needed
},
_count: {
select: {
registrations: true,
raceDays: true,
}
}
}
})
// Transform to view model
const eventViewModel = {
id: event.id,
name: event.name,
type: event.type,
dateRange: `${event.startDate} - ${event.endDate}`,
club: event.club,
upcomingRaceDays: event.raceDays.filter(rd => rd.status === 'PLANNED'),
stats: {
totalRegistrations: event._count.registrations,
totalRaceDays: event._count.raceDays,
}
}
// Passing ~90KB to client component โ
return <EventDetails event={eventViewModel} />
Pattern: Server Component Data Transformation
Use server components to filter and transform data close to the source:
// app/events/[id]/page.tsx - Server Component
export default async function EventPage({ params }) {
const { id } = await params
// Fetch optimized data
const eventData = await prisma.event.findUnique({
where: { id },
select: {
// Only fields needed for this page
id: true,
name: true,
description: true,
startDate: true,
endDate: true,
club: {
select: {
id: true,
name: true,
logo: true,
}
},
// Use _count for aggregates
_count: {
select: {
registrations: true,
raceDays: true,
}
}
}
})
// Transform to view model in server component
const viewModel = {
id: eventData.id,
title: eventData.name,
summary: eventData.description?.substring(0, 200),
dates: {
start: eventData.startDate,
end: eventData.endDate,
formatted: formatDateRange(eventData.startDate, eventData.endDate)
},
club: eventData.club,
metrics: {
participants: eventData._count.registrations,
raceDays: eventData._count.raceDays,
}
}
// Pass optimized, transformed data to client component
return <EventDetailsClient event={viewModel} />
}
Pattern: Pagination and Infinite Scroll
// โ
GOOD: Paginated data fetching
const getEvents = async (page: number = 1, pageSize: number = 50) => {
const skip = (page - 1) * pageSize
const [events, total] = await Promise.all([
prisma.event.findMany({
select: {
id: true,
name: true,
startDate: true,
club: {
select: { name: true }
}
},
skip,
take: pageSize,
orderBy: { startDate: 'desc' }
}),
prisma.event.count() // Separate count query
])
return {
events,
pagination: {
page,
pageSize,
total,
totalPages: Math.ceil(total / pageSize),
hasMore: skip + pageSize < total
}
}
}
Pattern: Use _count for Aggregates
Don't fetch relations just to count them:
// โ BAD: Fetching all registrations just to count
const event = await prisma.event.findUnique({
where: { id },
include: {
registrations: true, // Could be thousands!
}
})
const count = event.registrations.length // ๐ฅ Huge memory usage
// โ
GOOD: Use _count
const event = await prisma.event.findUnique({
where: { id },
select: {
id: true,
name: true,
_count: {
select: {
registrations: true, // Just returns number
}
}
}
})
const count = event._count.registrations // โ
Efficient
Pattern: View Models for UI
Create dedicated view model types for UI components:
// types/event.ts
// โ Don't use raw Prisma types in UI
import type { Event } from '@prisma/client/index'
// โ
Create UI-specific view models
export interface EventListItemViewModel {
id: string
name: string
dates: {
start: Date
end: Date
formatted: string
}
club: {
id: string
name: string
}
stats: {
participants: number
raceDays: number
}
status: 'upcoming' | 'ongoing' | 'completed'
}
export interface EventDetailsViewModel extends EventListItemViewModel {
description: string
location: string
upcomingRaceDays: {
id: string
date: Date
status: string
}[]
}
// services/eventViewModelMapper.ts
export function mapToEventListItem(event: PrismaEventResult): EventListItemViewModel {
return {
id: event.id,
name: event.name,
dates: {
start: event.startDate,
end: event.endDate,
formatted: formatDateRange(event.startDate, event.endDate)
},
club: {
id: event.club.id,
name: event.club.name
},
stats: {
participants: event._count.registrations,
raceDays: event._count.raceDays
},
status: determineEventStatus(event.startDate, event.endDate)
}
}
Debug Logging for Prop Sizes
Monitor prop sizes in development:
// components/EventDetails.tsx
'use client'
export function EventDetails({ event }: { event: EventViewModel }) {
// Debug logging for development
if (process.env.NODE_ENV === 'development') {
const propSize = JSON.stringify(event).length
const propSizeKB = (propSize / 1024).toFixed(2)
console.log('[EventDetails] Prop size:', `${propSizeKB} KB`)
// Warn if prop is large
if (propSize > 100_000) { // 100KB warning threshold
console.warn('โ ๏ธ Large prop detected:', `${propSizeKB} KB`)
}
}
return <div>...</div>
}
Special Debug User Logging
For jac.curran@gmail.com, add prop size logging:
// app/events/[id]/page.tsx
export default async function EventPage({ params }) {
const session = await getServerSession()
const eventData = await fetchEventData(id)
// Debug logging for specific user
if (session?.user?.email === 'jac.curran@gmail.com') {
const dataSize = JSON.stringify(eventData).length
const dataSizeKB = (dataSize / 1024).toFixed(2)
console.log('[DEBUG] Event data size:', `${dataSizeKB} KB`)
console.log('[DEBUG] Event data keys:', Object.keys(eventData))
}
return <EventDetailsClient event={eventData} />
}
Performance Checklist
Before fetching data:
- Use
selectto fetch only needed fields - Use
_countfor aggregates, not full relations - Use
takeandskipfor pagination - Transform data in server components before passing to client
- Create view models for UI-specific data shapes
- Monitor prop sizes in development
- Avoid deep nested includes (max 2-3 levels)
- Consider caching for frequently accessed data
๐งช Test-Driven Development (TDD) - MANDATORY
TDD Development Flow (Follow for ALL New Code)
CRITICAL: Always write tests BEFORE writing implementation code.
1. **Check Context7**: Query for best practices on pattern you're implementing
2. **Analyze First**: Study dependencies, business logic, and existing patterns
3. **Write Test**: Create failing tests that describe desired behavior
4. **Write Code**: Implement minimum code to make tests pass (using Context7 patterns)
5. **Refactor**: Improve code while keeping tests green
6. **Repeat**: Continue cycle for each feature increment
Essential TDD Checklist
Before writing ANY code:
- Analyze dependencies and business rules
- Read the implementation first (for modifications) to understand actual behavior
- Check existing utilities in
__tests__/utils/for reusable test code - Use centralized factories - NEVER create inline test data
- Mock exactly what the code uses (check imports and method calls)
- Use complete, realistic test data with future dates
- Test business behavior, not implementation details
- Ensure tests are isolated and repeatable
- Follow Vitest hoisting rules for mocks
๐ญ Code Reuse: Centralized Testing Utilities
Critical Rule: Always Check Existing Utilities FIRST
Before writing ANY test code, check __tests__/utils/ for:
testDataFactories.ts- Mock data factories for unit testsimport { createMockEvent, createMockUser } from '@utils/testDataFactories' const event = createMockEvent({ name: 'Custom Name' })testDatabaseHelpers.ts- Database setup for integration testsimport { setupTestDatabase, createTestEvent } from '@utils/testDatabaseHelpers' const context = await setupTestDatabase() const eventId = await createTestEvent(context, { name: 'Test Event' })testCleanupHelpers.ts- Cleanup helpers (respects foreign keys)import { cleanupTestContext } from '@utils/testCleanupHelpers' await cleanupTestContext(context)mockManagementUtilities.ts- Mock helpersimport { mockAuthSession, resetAllMocks } from '@utils/mockManagementUtilities'authTestHelpers.ts- Auth mocking helpersimport { createAuthContext, clearAuthMocks } from '@utils/authTestHelpers'
Code Reuse Benefits
- Schema changes: Update 1 file instead of 50+ files
- Test writing speed: 50% faster with factories
- File size: 50-70% smaller test files
- Consistency: Same patterns across all tests
- Maintainability: Single source of truth
Code Reuse Rules (7 Essential)
| # | Rule | Impact |
|---|---|---|
| 1 | โ ALWAYS use centralized factories | 80% less schema change impact |
| 2 | โ NEVER create inline Prisma structures | 50-70% smaller test files |
| 3 | โ ALWAYS use cleanup helpers | 100% correct foreign key handling |
| 4 | โ Reuse test contexts across related tests | 30% faster test execution |
| 5 | โ Use factory composition for complex setups | 50% faster test writing |
| 6 | โ Keep test files < 300 lines | Better maintainability |
| 7 | โ Extract setup to beforeEach | Reduced duplication |
Quick Decision Trees
Should I check Context7 first?
About to implement a library pattern?
โโ Using it for the first time?
โ โโ Query Context7 FIRST โ
โ โโ "/library-id" + "pattern description"
โ
โโ Unsure if it's the best approach?
โ โโ Query Context7 to verify โ
โ โโ Compare your approach to recommended
โ
โโ Getting unexpected behavior?
โ โโ Query Context7 for correct usage โ
โ โโ Check if you're using deprecated pattern
โ
โโ Already confident in the pattern?
โโ Proceed (but spot-check Context7 occasionally)
Should I optimize this query?
Fetching data from database?
โโ ๐ First: Query Context7 "prisma query optimization"
โ
โโ Count only?
โ โโ Use _count โ
โ
โโ List view (many records)?
โ โโ Use select + pagination โ
โ
โโ Detail view (single record)?
โ โโ Use select with strategic include โ
โ โโ Max 2-3 levels deep
โ
โโ Passing to client component?
โโ Transform to view model FIRST โ
โโ Monitor prop size in dev
Should I write tests first?
Are you writing code?
โโ Yes โ Write tests FIRST (TDD required)
โโ New feature
โ โโ ๐ Query Context7 for testing pattern
โ โโ Analyze โ Write test โ Implement
โ
โโ Bug fix
โ โโ Write regression test โ Fix bug
โ
โโ Refactor
โโ Tests already exist โ Refactor โ Tests still pass
Which test utility should I use?
Need test data?
โโ Unit test (mocked Prisma)?
โ โโ Use testDataFactories.ts โ
โ โโ createMockEvent(), createMockUser(), etc.
โ
โโ Integration test (real database)?
โ โโ Use testDatabaseHelpers.ts โ
โ โโ setupTestDatabase(), createTestEvent(), etc.
โ
โโ Need cleanup?
โโ Use testCleanupHelpers.ts โ
โโ cleanupTestContext(), cleanupEvent(), etc.
Should this be a Server or Client Component?
Does it need user interaction (clicks, forms, state)?
โโ No โ Server Component โ
โ โโ Fetch & transform data here
โ โโ Pass optimized props to client
โ
โโ Yes
โโ Can you use form actions instead?
โโ Yes โ Server Component + Form Actions โ
โโ No โ Client Component ("use client")
โโ Receive pre-optimized props from server
Where does this code belong?
Business Logic โ /server/services
Data Access (Prisma) โ /server/services (with select optimization)
Data Transformation โ Server components or /lib/mappers
UI Component โ /components (client)
Page/Route โ /app (server)
API Endpoint โ /app/api
Type Definition โ /types (view models + DB types)
Server Utility โ /lib or /server/utils
Test โ /__tests__
Architecture Patterns
Core Principles
- Best Practices First: Query Context7 before implementing library patterns
- TDD-First: Tests before code, always
- Data Optimization: Fetch only what you need, transform early
- Code Reuse: Use centralized utilities, never duplicate
- Server-First: Default to server components
- Service Layer: All business logic lives in
/server/services - Single Responsibility: Each module has one clear purpose
- Type Safety: TypeScript strict mode throughout
- Security by Default: RBAC checks, input validation, audit logging
Technology Stack
- Framework: Next.js 14+ (App Router)
- Database: PostgreSQL via Prisma ORM
- Authentication: NextAuth.js
- Validation: Zod
- Styling: Tailwind CSS (no inline styles)
- Testing: Vitest + React Testing Library + Playwright
File Organization
Directory Structure
/app # Next.js App Router (routes and server components)
/(auth) # Authentication routes
/api # API route handlers
/[club-slug] # Dynamic routes for clubs
/components # Reusable React components
/ui # Base UI components (shadcn/ui)
/forms # Form components
/[feature] # Feature-specific components
/server # Server-side business logic
/services # Business logic services (ALL PRISMA HERE)
/lib # Shared utilities and configurations
/auth.ts # Authentication utilities
/prisma.ts # Prisma client singleton
/mappers # Data โ View Model mappers
/types # TypeScript type definitions
/viewModels # UI-specific view model types
/prisma # Database schema and migrations
schema.prisma # Prisma schema
/__tests__ # Test files
/utils # Centralized test utilities โญ
testDataFactories.ts
testDatabaseHelpers.ts
testCleanupHelpers.ts
mockManagementUtilities.ts
/services # Service unit tests
/components # Component tests
/integration # Integration tests
/e2e # End-to-end tests
File Naming Conventions
- Pages:
page.tsx(Next.js convention) - API Routes:
route.ts(Next.js convention) - Components:
ComponentName.tsx(PascalCase) - Services:
serviceNameService.ts(camelCase + Service suffix) - Utilities:
utilityName.ts(camelCase) - Types:
domainName.ts(camelCase) - View Models:
featureViewModel.ts(camelCase + ViewModel suffix) - Tests:
*.test.tsor*.spec.ts - Custom Hooks:
use[A-Z]*.ts(use prefix)
File Length Limits
- Maximum file length: 600 lines
- Maximum component length: 300 lines
- Maximum test file: 300 lines (use centralized factories!)
- If exceeded, refactor into smaller modules
Import Organization (STRICT)
Import Order - Must Follow Exactly
Group imports in this exact order with newlines between groups:
- Third-party imports (React, Next.js, external libraries)
- Local imports (
@/absolute imports) - Component imports (relative imports
./) - Type imports (always use
import type)
Within each group: Sort alphabetically
Import Rules
- โ
Always use
import typefor TypeScript types - โ
Use absolute imports (
@/) for local modules - โ
Use relative imports (
./) for component-specific files only - โ No duplicate imports
- โ No unused imports
- โ Never use
require()- always useimport
Correct Import Example
// 1. Third-party imports
import { useState, useEffect } from 'react'
import { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
// 2. Local imports (@/)
import { prisma } from '@/lib/prisma'
import { hasRole } from '@/lib/auth'
import { mapToEventViewModel } from '@/lib/mappers/eventMapper'
import { Button } from '@/components/ui/button'
// 3. Component imports (./)
import { EventCard } from './EventCard'
import { EventList } from './EventList'
// 4. Type imports
import type { Prisma } from '@prisma/client/index'
import type { Event, Club } from '@prisma/client/index'
import type { EventViewModel } from '@/types/viewModels/eventViewModel'
Component Architecture
Server Components (Default)
Use server components by default for:
- Data fetching from database (with optimized
select) - Data transformation (DB โ View Models)
- Server-side business logic
- No user interactivity required
Server Component Rules:
- โ
Default choice - no
"use client"directive - โ Direct database access via services
- โ Async functions allowed
- โ Transform data here before passing to client
- โ
Use
selectto minimize data fetched - โ No useState, useEffect, or event handlers
- โ No browser APIs
Client Components
Use client components when you need:
- User interactivity (clicks, form inputs)
- Browser APIs (localStorage, window)
- React hooks (useState, useEffect)
- Real-time updates
Client Component Rules:
- โ
Mark with
"use client"directive at top of file - โ Keep components small and focused
- โ Receive optimized view models via props (not raw DB data)
- โ Use form actions for mutations
- โ Monitor prop sizes in development
- โ No direct database access
- โ No direct Prisma calls
- โ No server-side secrets
Service Layer Pattern
Service Responsibilities
ALL business logic must reside in /server/services
Services handle:
- Business logic and validation
- Database operations via Prisma (with optimized
select) - Transaction management
- Error handling
- Audit logging
- Email notifications
- Data transformations
Service Structure (TDD Pattern with Data Optimization)
// 1. WRITE TEST FIRST โญ
// __tests__/services/eventService.test.ts
import { createMockEvent } from '@utils/testDataFactories'
describe('EventService', () => {
it('should create event successfully', async () => {
// Arrange
const input = createMockEvent({ name: 'Test Event' })
// Act
const result = await createEvent(input, userId)
// Assert
expect(result.success).toBe(true)
expect(result.data.name).toBe('Test Event')
})
})
// 2. IMPLEMENT SERVICE WITH DATA OPTIMIZATION โญ
// server/services/eventService.ts
import type { Prisma } from '@prisma/client/index'
import { prisma } from '@/lib/prisma'
import { hasRole } from '@/lib/auth'
import { logAuditAction } from '@/server/services/auditLogService'
import { z } from 'zod'
// Input validation schemas
const createEventSchema = z.object({
name: z.string().min(1).max(200),
type: z.enum(['SERIES', 'SINGLE']),
clubId: z.string().uuid(),
})
// Return type definitions
interface ServiceResult<T> {
success: boolean
data?: T
error?: string
}
// Service functions with optimized queries
export async function createEvent(
input: z.infer<typeof createEventSchema>,
userId: string
): Promise<ServiceResult<Event>> {
try {
// Validate input
const validatedInput = createEventSchema.parse(input)
// Check permissions
const canCreate = await hasRole(userId, validatedInput.clubId, 'EVENT_MANAGER')
if (!canCreate) {
return { success: false, error: 'Insufficient permissions' }
}
// Business logic with optimized select
const event = await prisma.event.create({
data: {
...validatedInput,
createdById: userId,
},
select: {
id: true,
name: true,
type: true,
startDate: true,
endDate: true,
club: {
select: {
id: true,
name: true,
timezone: true,
}
},
_count: {
select: {
raceDays: true,
registrations: true,
}
}
}
})
// Audit logging
await logAuditAction({
userId,
action: 'CREATE',
resourceType: 'EVENT',
resourceId: event.id,
details: { eventName: event.name }
})
return { success: true, data: event }
} catch (error) {
console.error('Error creating event:', error)
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to create event'
}
}
}
Service Rules
- โ
Use
selectto fetch only needed fields - โ
Use
_countfor aggregates, not full relations - โ All Prisma operations in try/catch blocks
- โ Use Zod for input validation
- โ
Return structured result objects
{ success, data, error } - โ Log all mutations to audit log
- โ Check permissions before operations
- โ Use transactions for multi-step operations
- โ
Optimize queries (strategic
includeandselect) - โ Never expose raw errors to client
- โ No business logic in API routes or components
- โ No direct Prisma calls outside services
- โ Never fetch more data than needed
Prisma & Database Patterns
Prisma Type Imports (CRITICAL)
Always use type imports from @prisma/client/index:
// โ
CORRECT
import type { Prisma } from '@prisma/client/index'
import type { Event, Club, Registration } from '@prisma/client/index'
import { prisma } from '@/lib/prisma'
// โ INCORRECT
import { Prisma, Event } from '@prisma/client/index' // Not type import
import { prisma } from '@prisma/client' // Wrong path
Prevent N+1 Queries
// โ
GOOD: Single query with includes
const events = await prisma.event.findMany({
select: {
id: true,
name: true,
startDate: true,
club: {
select: {
id: true,
name: true,
}
},
_count: {
select: {
registrations: true, // Just count
}
}
},
take: 50, // Always paginate
})
// โ BAD: N+1 query problem
const events = await prisma.event.findMany()
for (const event of events) {
event.club = await prisma.club.findUnique({ where: { id: event.clubId }})
}
Type System
TypeScript Rules
- โ All functions have explicit return types
- โ All parameters have explicit types
- โ
No explicit
anytypes - โ
No implicit
anytypes - โ Use type imports for Prisma types
- โ Create view model types for UI props
Type Definitions
Use interface for object types:
interface User {
id: string
name: string
email: string
}
Use type for union/intersection types:
type EventStatus = 'DRAFT' | 'PUBLISHED' | 'COMPLETED'
type EventWithClub = Event & { club: Club }
Use Prisma's generated types:
type EventWithClub = Prisma.EventGetPayload<{
include: { club: true }
}>
Create View Models for UI:
// types/viewModels/eventViewModel.ts
export interface EventListItemViewModel {
id: string
name: string
dateRange: string
club: {
id: string
name: string
}
stats: {
participants: number
raceDays: number
}
}
Naming Conventions
Code Naming
- React Components: PascalCase (
EventCard,UserProfile) - Functions/Variables: camelCase (
getEventById,userName) - Constants: UPPER_SNAKE_CASE (
MAX_RETRIES,API_BASE_URL) - Types/Interfaces: PascalCase (
EventFormInput,ServiceResult) - View Models: PascalCase with
ViewModelsuffix (EventListItemViewModel) - Enums: PascalCase (
EventStatus,UserRole)
Performance Patterns
Database Query Optimization (CRITICAL)
- Use
selectfor Specific Fields - Always prefer select over full model fetch - Use
_countfor Aggregates - Don't fetch relations just to count - Use
takeandskipfor Pagination - Never fetch all records - Batch Queries with
Promise.all- Parallel queries when possible - Monitor Query Performance - Log slow queries in development
Component Performance
Code Splitting:
import dynamic from 'next/dynamic'
const HeavyComponent = dynamic(
() => import('@/components/HeavyComponent'),
{ loading: () => <div>Loading...</div> }
)
Image Optimization:
import Image from 'next/image'
<Image
src="/logo.png"
alt="Logo"
width={200}
height={100}
priority
/>
Special User Debug Logging
When user email is jac.curran@gmail.com, add debug logging:
if (session?.user?.email === 'jac.curran@gmail.com') {
console.log('[DEBUG]', {
context: 'eventCreation',
dataSize: `${(JSON.stringify(data).length / 1024).toFixed(2)} KB`,
data: eventData
})
}
Quick Reference Checklist
Before creating any new code, verify:
Context7 Best Practices Checklist
- Queried Context7 for library-specific best practices
- Compared my approach with Context7 recommendations
- Using current patterns (not deprecated/outdated)
- Verified with Context7 if unsure about implementation
Data Optimization Checklist
- Checked Context7 "prisma query optimization" patterns
- Used
selectto fetch only needed fields - Used
_countfor aggregates (not full relations) - Used pagination (
takeandskip) for lists - Transformed data in server component before passing to client
- Created view model type for UI props
- Monitored prop size in development
- Avoided deep nested includes (max 2-3 levels)
TDD Checklist
- Queried Context7 for testing patterns (if needed)
- Analyzed dependencies and business rules FIRST
- Checked
__tests__/utils/for reusable utilities - Written tests before writing implementation code
- Used centralized factories (no inline test data)
- Tests pass and are isolated
Code Quality Checklist
- Verified with Context7 if using library pattern for first time
- Imports organized correctly (third-party โ local โ component โ types)
- Using server component unless interactivity needed
- Business logic in
/server/services - Prisma types imported with
import typefrom@prisma/client/index - RBAC checks in place for protected operations
- Input validation with Zod
- Proper error handling with try/catch
- Audit logging for sensitive operations
- TypeScript types explicitly defined
- Following naming conventions (PascalCase, camelCase, etc.)
- Tailwind CSS only (no inline styles)
- File under 600 lines (300 for components, tests)
Documentation References
For detailed testing patterns, templates, and examples, see:
documentation/testing/TESTING-DOC.md- Main testing guidedocumentation/testing/TESTING_STRATEGY.md- Testing philosophydocumentation/testing/TESTING_PATTERNS.md- Detailed patternsdocumentation/testing/TESTING_TEMPLATES.md- Reusable templates
Remember:
- Context7 first - Always check best practices before implementing
- TDD-first development with maximum code reuse
- Data optimization at the source (use
select, transform early) - View models for UI components (not raw DB models)
- Monitor performance in development (prop sizes, query times)
- Tests are documentation of how the system actually works
When in doubt:
- Query Context7 for best practices
- Write a test first
- Use centralized utilities
- Optimize data fetching
- Follow existing patterns