Bolymarket recreates Polymarket’s homepage and event-detail experience as a read-only Next.js app. Structural market data flows through TanStack Query (persisted to IndexedDB); live outcome probabilities update via Polymarket’s activity WebSocket into per-outcome Jotai atoms, with client-side simulation when the socket is unavailable.
Problem
Polymarket’s product surface is complex (category navigation, live odds, charts, sports, mobile chrome), and building a faithful read-only experience requires careful separation of static event data from high-frequency price updates without melting the React tree.
Frontend work
- Built 7 App Router pages: /, /crypto, /sports, /sports/live, /politics, /event/[slug], /api-docs
- Implemented Polymarket-faithful app chrome: TopBar, CategoryNav, Footer, MobileBottomNav, search toolbar
- Split state: Jotai for filters/bookmarks/theme/live prices; TanStack React Query for events and chart history
- Rendered BinaryCard, MultiOutcomeCard, SportsMatchCard, featured carousel, and category page layouts
- Built event detail with Recharts PriceChart, timeframe toggles, related news, share/embed, visual OrderTicket
- Wired live prices via shared livePriceEngineManager with leaf-only subscriptions and reduced-motion support
- Styled with Tailwind CSS v4 semantic tokens, dark mode, and Motion for modal transitions
- Added debounced client search, localStorage bookmarks, and URL-synced category navigation
Backend & system work
- Implemented 7 Next.js BFF routes proxying Gamma, CLOB, Data API, sports live, and related news
- Normalized all upstream responses with Zod schemas; thin route handlers delegate to lib/api modules
- Added server-side caching with optional Redis (REDIS_URL) and in-memory fallback
- Built GDELT news fetch with OkSurf fallback and 5-minute server cache
- Published OpenAPI 3.0 spec at /api/openapi with Swagger UI at /api-docs
- Configured Next.js image remotePatterns for Polymarket CDN assets
- Deployed on Vercel (bun install --frozen-lockfile, bun run build); no CI workflow in repo
- Env vars: REDIS_URL (optional), NEXT_PUBLIC_LIVE_PRICE_MODE (auto | websocket | simulation)
Key decisions
- React Query for structural event data, Jotai atom families for live prices — prevents tick-driven query invalidation
- BFF API routes instead of direct browser calls — centralizes validation, caching, and upstream shape handling
- Polymarket activity WebSocket with 2s simulation fallback in auto mode — UX without full CLOB order book
- Per-outcome outcomePriceAtomFamily with RAF coalescing — minimizes re-render scope on price ticks
- Recharts fed by CLOB history with generateChartData simulated fallback when upstream is sparse
- IndexedDB persistence for React Query via @tanstack/react-query-persist-client and idb-keyval
Challenges
- High-frequency price updates without grid re-renders — Jotai leaf subscriptions, React.memo, documented profiler targets
- WebSocket reliability — reconnect, ping, subscription index by event_slug, simulation engine fallback
- Sports live complexity — separate sportsWebSocketEngine, game state atoms, league aggregation (phase in progress)
- CLOB chart sparsity — merge logic plus simulated series; latest point synced to live atoms
- Chrome fidelity vs. scope — many nav/footer links are visual-only placeholders
Tech stack
Frontend
- Next.js 16.2.9
- React 19.2.4
- Tailwind CSS v4
- Jotai ^2.20.1
- TanStack React Query ^5.101.0
- Recharts ^3.8.1
- Motion ^12.40.0
- Lucide React ^1.18.0
Backend
- Next.js Route Handlers (BFF)
- redis ^6.0.0 (optional)
Data
- Polymarket Gamma REST API
- Polymarket CLOB /prices-history
- Polymarket Data API /trades
- @polymarket/real-time-data-client ^1.4.0
- Polymarket sports WebSocket
- GDELT + OkSurf news
- Zod ^4.4.3
- idb-keyval ^6.2.5
Infrastructure
- Vercel
- Bun (recommended runtime)
- Optional Redis (REDIS_URL)
Testing
- Vitest ^4.1.9
- React Testing Library ^16.3.2
- jsdom ^29.1.1
- 119 Vitest test files
Tooling
- TypeScript ^5 (strict)
- ESLint ^9 + eslint-config-next 16.2.9
- Prettier ^3.8.4
Browser APIs
- WebSocket
- IndexedDB
- localStorage
- Web Share API
- prefers-reduced-motion
- requestAnimationFrame