π― Data Architecture Fix: From Hybrid to Database-First
8/4/2025 Accurate
Problem: Hybrid Data Architecture
The Issue You Identified
Components were fetching from two different data sources without knowing the implications:
π΄ API Components (Stale Data) π’ Database Components (Fresh Data)
ββ AthleteHeader ββ RecentActivities
ββ useAthleteData() ββ ActivitiesDashboard
ββ Hits Strava API ββ ActivityFeed
ββ TrainingLoadChart
ββ useUserActivities()
What Happens:
- Sync Process β Updates database β
- Database components β Show new data immediately β
- API components β Still show old cached data β
Your 4-Hour Run Missing:
- β Sync worked β Run stored in database
- β
Dashboard shows it β Uses
useUserActivities()
- β Header shows old name β Uses
useAthleteData()
from API
Solution: Database-First Architecture
Fixed Components:
- β
AthleteHeader
β Now usesuseAthleteProfile(userId)
- β
RecentActivities
β Already usinguseUserActivities(userId)
- β
ActivitiesDashboard
β Already usinguseUserActivities(userId)
- β
TrainingLoadChart
β Already using database viauseUserActivities(userId)
Data Flow Now:
Strava API β Sync Service β Database β React Query β Components
β β
(Once per sync) (Cached, fast)
Benefits:
- Consistency: All components use same data source
- Speed: Database queries are faster than API calls
- No Rate Limits: No more 600 requests/day limit
- Real-time: Sync updates all components instantly
Migration Checklist
β Completed:
- Created
useAthleteProfile(userId)
hook - Updated
AthleteHeader
to use database - All activity components use
useUserActivities(userId)
- Training load components use database
π To Deprecate (Optional):
-
useAthleteData()
- Only used by old components -
useAthleteActivities()
- Superseded byuseUserActivities()
-
/api/strava/athlete
route - No longer needed
π― Result:
Single Source of Truth β Database stores everything, components read from there.
Testing the Fix
-
Sync your data:
# In browser console on /dashboard fetch('/api/strava/sync', { method: 'POST' })
-
Verify all components update:
- Athlete header shows your name β
- Recent activities show latest run β
- Training load includes new data β
- All data consistent across components β
Best Practices Going Forward
1. Always Use Database-First Hooks:
// β
Good: Database-first
const { data: activities } = useUserActivities(userId)
const { data: athlete } = useAthleteProfile(userId)
// β Avoid: API-first
const { data: activities } = useAthleteActivities(token)
const { data: athlete } = useAthleteData(token)
2. Sync Strategy:
- Sync once β Updates database
- Components read β From database (cached)
- Fast, consistent, no rate limits
3. Query Invalidation:
// When sync completes, invalidate related queries
queryClient.invalidateQueries({ queryKey: ['user', 'activities'] })
queryClient.invalidateQueries({ queryKey: ['athlete', 'profile'] })
Architecture Principle
"Sync to Database, Read from Database"
Never mix API and database reads in the same app. Pick one source of truth and stick to it.