[Optimization]: Prisma Singleton + DB Connection Pooling + UX Improvements#39
Open
Eli5DeFi wants to merge 1 commit intofeature/psychic-consensus-oracle-uifrom
Open
Conversation
…g skeletons, ARIA
## Critical Fixes (Connection Pool)
- Replace new PrismaClient() with singleton in 11 files
- betting/platform-stats, trending, recent
- users/bets, users/performance
- badges, badges/[userId]
- analytics/leaderboard, analytics/stats
- share, lib/badges
- Remove prisma.$disconnect() from all API routes (singleton must stay alive)
- Remove empty finally{} blocks left behind
## Performance
- analytics/stats: N+1 query → single JOIN query for most popular story
(was: groupBy + separate findUnique = 2 DB round-trips)
(now: 1 with JOINs)
- analytics/leaderboard: add 2-min in-memory cache (was uncached)
- analytics/leaderboard: proper parameterised timeframe cutoff via $queryRaw
tagged template (Date param, not string interpolation)
## Security
- analytics/leaderboard: eliminate SQL injection via ORDER BY whitelist map
(never interpolate user sortBy param directly into SQL)
- analytics/leaderboard: use $queryRaw tagged template for timeframe param
(was string-interpolated ISO timestamp in $queryRawUnsafe)
## Config
- Merge next.config.js + next.config.mjs into single next.config.mjs
(Next.js 14 picks .mjs first; .js was silently ignored)
- All splitChunks, image optimisation, security headers preserved
- Web3 webpack polyfills (fs/net/tls: false) merged in
- stale .next cache cleared (removed stale insurance route types)
## Web3 Provider
- QueryClient moved into useState() (React 18 Concurrent Mode safe)
- wagmiConfig + rainbowTheme extracted as module-level constants (stable refs)
- React Query defaultOptions: staleTime=10s, skip retry on 4xx errors
## UX / Loading States
- Add dashboard/loading.tsx skeleton (DashboardStatsSkeleton + ChartSkeleton)
- Add leaderboards/loading.tsx skeleton
- Add story/[storyId]/loading.tsx skeleton (BettingPool + Chart + Activity)
## Accessibility
- BettingInterface: aria-pressed on choice buttons
- BettingInterface: aria-label on choice buttons (text + odds)
- BettingInterface: aria-label + aria-describedby on bet amount input
- BettingInterface: role=alert + aria-live=assertive on error message
## Code Quality
- Switch console.error → logger.error in 10 API routes
(uses existing logger util that strips debug logs in production)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Critical performance & correctness fixes discovered during optimization cycle. All changes target the
feature/psychic-consensus-oracle-uibase.🔴 Critical: Connection Pool Exhaustion Fixed
Problem: 11 API routes were calling
new PrismaClient()on every module load, creating independent connection pools that exhaust the database connection limit under any real load.Fix: All routes now use the shared singleton from
@/lib/prisma(which already existed but was unused). Also removedprisma.$disconnect()calls that were closing the shared pool mid-request.Affected files (11):
betting/platform-stats,betting/trending,betting/recentusers/bets,users/performancebadges,badges/[userId]analytics/leaderboard,analytics/statsshare,lib/badges⚡ Performance
N+1 Query Eliminated (
analytics/stats)groupBy+ separatefindUnique= 2 sequential DB round-trips$queryRawwith JOINs (stories → chapters → choices → bets)Leaderboard Caching Added
analytics/leaderboardwas uncached — now has 2-minute in-memory cachetrendingandplatform-stats🔐 Security
SQL Injection Risk Fixed (
analytics/leaderboard)timeframeCutoff(ISO string) interpolated directly into$queryRawUnsafe;LIMITalso string-interpolated$queryRawtagged template with typedDateparameter;ORDER BYuses a pre-validated whitelist map🏗️ Config
Single Next.js Config File
next.config.js(CJS, optimized) +next.config.mjs(ESM, basic) — Next.js 14 picks.mjsfirst, so all the splitChunks/image/caching config in.jswas silently ignorednext.config.mjswith everything merged: splitChunks, avif/webp images, security headers, Web3 webpack polyfills, bundle analyzer⚛️ Web3 Provider
QueryClientmoved from module scope intouseState()— fixes React 18 Concurrent Mode hydration edge caseswagmiConfigandrainbowThemekept as module-level constants (correct — they don't capture component state)defaultOptions:staleTime: 10s, skip retry on 4xx errors🖥️ UX / Loading States
Added Next.js 13+
loading.tsxfiles for instant skeleton display:app/dashboard/loading.tsx— DashboardStats + Chart skeletonsapp/leaderboards/loading.tsx— LeaderboardSkeletonapp/story/[storyId]/loading.tsx— BettingPool + Chart + Activity skeletons♿ Accessibility
BettingInterfaceimprovements:aria-pressedon choice selection buttons (screen readers know selection state)aria-labelon choice buttons (announces text + odds)aria-label+aria-describedbyon bet amount inputrole="alert"+aria-live="assertive"on error messages (announced immediately)📊 Metrics (Before → After)
Testing
pnpm type-checkpasses (exit 0, zero errors)new PrismaClient(), no$disconnect()Impact
Connection stability: System won't crash under moderate load anymore. The
new PrismaClient()pattern would have caused "too many connections" errors the moment 2-3 concurrent users hit different API endpoints simultaneously.Perceived performance: Loading skeletons make all 3 heavy pages feel instant instead of showing blank content while data fetches.
SEO / Accessibility: ARIA improvements let screen readers navigate the betting interface correctly.