initial commit
This commit is contained in:
327
todo/PERFORMANCE_OPTIMIZATION.md
Normal file
327
todo/PERFORMANCE_OPTIMIZATION.md
Normal file
@@ -0,0 +1,327 @@
|
||||
# Performance Optimization Checklist
|
||||
|
||||
**Current Status**: Largest Contentful Paint (LCP) = 2.6s
|
||||
**Target**: LCP < 1.2s (excellent), < 2.5s (good)
|
||||
**Priority**: 🔴 Critical - Blocking user experience
|
||||
|
||||
**Instructions**: Each task should be completed individually. AI should ask for user validation before marking as complete and moving to next task.
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Critical Issues (Must Fix)
|
||||
|
||||
### Task 1: Remove Blocking Startup Check from Layout
|
||||
|
||||
**Impact**: -1.5s LCP improvement
|
||||
**Status**: ❌ Pending
|
||||
**File**: `app/layout.tsx`
|
||||
**Lines**: 37-40
|
||||
|
||||
**Action**: Remove the following blocking code from root layout:
|
||||
|
||||
```typescript
|
||||
// REMOVE THIS:
|
||||
if (typeof window === 'undefined') {
|
||||
const { runStartupChecks } = await import('@/lib/startup')
|
||||
await runStartupChecks()
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**: Page should load immediately without waiting for database connections
|
||||
|
||||
- [ ] **TASK 1 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create Background Startup Instrumentation
|
||||
|
||||
**Impact**: Maintain startup checks without blocking
|
||||
**Status**: ❌ Pending
|
||||
**File**: Create `app/instrumentation.ts`
|
||||
|
||||
**Action**: Create new file with background startup checks:
|
||||
|
||||
```typescript
|
||||
export async function register() {
|
||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||
import('./lib/startup').then(({ runStartupChecks }) => {
|
||||
runStartupChecks().catch(console.error)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**: Startup checks run in background, visible in terminal logs
|
||||
|
||||
- [ ] **TASK 2 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create Redis User Caching Utility
|
||||
|
||||
**Impact**: -0.45s improvement
|
||||
**Status**: ❌ Pending
|
||||
**File**: Create `lib/auth-cache.ts`
|
||||
|
||||
**Action**: Create Redis caching utility:
|
||||
|
||||
```typescript
|
||||
import { connectRedis } from './redis'
|
||||
|
||||
const USER_CACHE_PREFIX = 'user:'
|
||||
const CACHE_TTL = 300 // 5 minutes
|
||||
|
||||
export async function getCachedUser(userId: string) {
|
||||
try {
|
||||
const redis = await connectRedis()
|
||||
const cached = await redis.get(`${USER_CACHE_PREFIX}${userId}`)
|
||||
return cached ? JSON.parse(cached) : null
|
||||
} catch (error) {
|
||||
console.error('Redis cache get error:', error)
|
||||
return null // Fallback to DB if Redis fails
|
||||
}
|
||||
}
|
||||
|
||||
export async function setCachedUser(userId: string, userData: any) {
|
||||
try {
|
||||
const redis = await connectRedis()
|
||||
await redis.setex(`${USER_CACHE_PREFIX}${userId}`, CACHE_TTL, JSON.stringify(userData))
|
||||
} catch (error) {
|
||||
console.error('Redis cache set error:', error)
|
||||
// Don't throw - caching is optional
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearCachedUser(userId: string) {
|
||||
try {
|
||||
const redis = await connectRedis()
|
||||
await redis.del(`${USER_CACHE_PREFIX}${userId}`)
|
||||
} catch (error) {
|
||||
console.error('Redis cache clear error:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**: File created with proper Redis integration
|
||||
|
||||
- [ ] **TASK 3 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Update /me Endpoint to Use Redis Cache
|
||||
|
||||
**Impact**: Reduce MongoDB calls by 80%
|
||||
**Status**: ❌ Pending
|
||||
**File**: `app/api/auth/me/route.ts`
|
||||
|
||||
**Action**: Replace the current GET function with:
|
||||
|
||||
```typescript
|
||||
import { getCachedUser, setCachedUser } from '@/lib/auth-cache'
|
||||
|
||||
export const GET = withAuth(async (request: NextRequest & { user?: any }) => {
|
||||
try {
|
||||
// 1. Check Redis cache first
|
||||
const cached = await getCachedUser(request.user.userId)
|
||||
if (cached) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { user: cached },
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Only hit MongoDB if not cached
|
||||
await connectDB()
|
||||
const user = await User.findById(request.user.userId).select('-password -refreshToken').lean() // Better performance
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: { message: 'User not found', code: 'USER_NOT_FOUND' } },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 3. Cache for next time
|
||||
await setCachedUser(request.user.userId, user)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: { user },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Get user info error:', error)
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: { message: 'Internal server error', code: 'INTERNAL_ERROR' } },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Validation**: /me endpoint returns cached data on subsequent calls
|
||||
|
||||
- [ ] **TASK 4 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Add Cache Invalidation to User Updates
|
||||
|
||||
**Impact**: Ensure cache consistency
|
||||
**Status**: ❌ Pending
|
||||
**File**: `app/api/auth/refresh/route.ts`
|
||||
|
||||
**Action**: Add cache clearing after user update:
|
||||
|
||||
```typescript
|
||||
// Add import at top:
|
||||
import { setCachedUser } from '@/lib/auth-cache'
|
||||
|
||||
// After user.save(), add:
|
||||
await setCachedUser(user._id.toString(), user.toJSON())
|
||||
```
|
||||
|
||||
**Validation**: User cache updates when refresh token is used
|
||||
|
||||
- [ ] **TASK 5 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Optimize Auth Context with localStorage
|
||||
|
||||
**Impact**: -0.25s improvement
|
||||
**Status**: ❌ Pending
|
||||
**File**: `contexts/AuthContext.tsx`
|
||||
**Lines**: 157-180
|
||||
|
||||
**Action**: Replace the restoreSession function:
|
||||
|
||||
```typescript
|
||||
const restoreSession = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
// 1. Load from localStorage immediately (sync)
|
||||
const cachedUser = localStorage.getItem('nextjs_user')
|
||||
if (cachedUser) {
|
||||
const userData = JSON.parse(cachedUser)
|
||||
setUser(userData)
|
||||
setHasCheckedAuth(true)
|
||||
setLoading(false)
|
||||
|
||||
// 2. Validate in background (async)
|
||||
apiCall('/me')
|
||||
.then((data) => {
|
||||
setUser(data.data.user)
|
||||
localStorage.setItem('nextjs_user', JSON.stringify(data.data.user))
|
||||
})
|
||||
.catch(() => {
|
||||
// Try refresh token if /me fails
|
||||
apiCall('/refresh', { method: 'POST' })
|
||||
.then((refreshData) => {
|
||||
setUser(refreshData.data.user)
|
||||
localStorage.setItem('nextjs_user', JSON.stringify(refreshData.data.user))
|
||||
})
|
||||
.catch(() => {
|
||||
// Both failed, clear cache and user
|
||||
localStorage.removeItem('nextjs_user')
|
||||
setUser(null)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Only do full auth check if no cached user
|
||||
const data = await apiCall('/me')
|
||||
setUser(data.data.user)
|
||||
localStorage.setItem('nextjs_user', JSON.stringify(data.data.user))
|
||||
setHasCheckedAuth(true)
|
||||
} catch (err) {
|
||||
// If /me fails, try refresh token
|
||||
try {
|
||||
const refreshData = await apiCall('/refresh', { method: 'POST' })
|
||||
setUser(refreshData.data.user)
|
||||
localStorage.setItem('nextjs_user', JSON.stringify(refreshData.data.user))
|
||||
setHasCheckedAuth(true)
|
||||
} catch (refreshErr) {
|
||||
// Both failed, user is not authenticated
|
||||
setUser(null)
|
||||
setHasCheckedAuth(true)
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Validation**: Page loads show cached user immediately, then validates in background
|
||||
|
||||
- [ ] **TASK 6 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 7: Clear localStorage on Logout
|
||||
|
||||
**Impact**: Proper cache cleanup
|
||||
**Status**: ❌ Pending
|
||||
**File**: `contexts/AuthContext.tsx`
|
||||
**Function**: logout
|
||||
|
||||
**Action**: Add localStorage clearing to logout function:
|
||||
|
||||
```typescript
|
||||
// In the logout function, add this line:
|
||||
localStorage.removeItem('nextjs_user')
|
||||
```
|
||||
|
||||
**Validation**: User data cleared from localStorage after logout
|
||||
|
||||
- [ ] **TASK 7 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
### Task 8: Add Database Query Optimization
|
||||
|
||||
**Impact**: -0.1s improvement
|
||||
**Status**: ❌ Pending
|
||||
**File**: `app/api/auth/login/route.ts`
|
||||
**Lines**: 23
|
||||
|
||||
**Action**: Optimize user lookup query:
|
||||
|
||||
```typescript
|
||||
// Replace:
|
||||
const user = await User.findOne({ email: validatedData.email.toLowerCase() })
|
||||
|
||||
// With:
|
||||
const user = await User.findOne({ email: validatedData.email.toLowerCase() })
|
||||
.lean()
|
||||
.select('+password')
|
||||
```
|
||||
|
||||
**Validation**: Login queries use optimized database access
|
||||
|
||||
- [ ] **TASK 8 COMPLETED** ✅
|
||||
|
||||
---
|
||||
|
||||
## 📊 Progress Tracking
|
||||
|
||||
**Completed**: 0/8 tasks
|
||||
**Expected LCP Improvement**: 0s of 2.2s total
|
||||
**Current Status**: Ready to start Task 1
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Criteria
|
||||
|
||||
After completing all tasks:
|
||||
|
||||
- **LCP**: Should improve from 2.6s to ~0.4s
|
||||
- **Redis Cache Hit Rate**: Should be >80% for /me endpoint
|
||||
- **Page Load**: Should render immediately without database waits
|
||||
- **Auth Restoration**: Should show cached user instantly
|
||||
|
||||
---
|
||||
|
||||
**Next Action**: Start with Task 1 - Remove Blocking Startup Check
|
||||
Reference in New Issue
Block a user