DEV Community

gogecmaestrotib92-cmyk
gogecmaestrotib92-cmyk

Posted on

How I Built an AI Sports Prediction Platform That Tracks Every Bet Publicly

I spent the last year building SportBot AI — an AI-powered sports analytics platform that generates predictions for 18+ sports, tracks every prediction publicly, and currently sits at +28.5% ROI across 231 verified bets.

This isn't a "how to train a model" tutorial — it's the real engineering story of shipping a production AI product as a solo developer. The messy parts included.

The Stack
Frontend: Next.js 14 (App Router, RSC, ISR)
Styling: Tailwind CSS
Database: PostgreSQL + Prisma ORM
Hosting: Vercel (Edge Functions + Blob Storage)
Monitoring: Sentry
Data: API-Sports, The Odds API, ESPN
AI/ML: Custom models + LLM orchestration
Why I Built This
I was tired of "AI prediction" sites that:

Never show their track record
Hide behind vague "80% accuracy" claims
Charge $200/month for basic stats
I wanted to build something that's publicly accountable. Every prediction is logged, tracked, and the results are displayed on a live performance dashboard. No cherry-picking.

Architecture: The Parts That Were Hard

  1. Real-Time Odds Pipeline Sports odds change every second. I needed a system that:

Polls multiple odds providers every few minutes
Detects "value" (when our model disagrees with the market)
Alerts users before the line moves
typescript
// Simplified edge detection logic
const edge = modelProbability - impliedProbability;
if (edge > EDGE_THRESHOLD) {
await createAlert({
match, edge, confidence: modelProbability,
timestamp: Date.now()
});
}
The tricky part wasn't the logic — it was caching. With thousands of matches across 18 sports, hitting the API on every page load would bankrupt me. I built a multi-layer cache:

Request → Edge Cache (60s) → Redis → API-Sports

  1. 870+ Auto-Generated Blog Articles This is where it gets interesting. I built an automated content pipeline that generates:

Match previews before every game (form analysis, H2H stats, AI predictions)
Tool reviews of competing products
Educational guides about betting strategy
Each article is generated through an LLM pipeline, but the data is real — pulled from our stats engine. The content isn't generic; it includes actual probabilities, historical matchups, and value assessments.

The ISR Challenge:

With 870+ articles, I couldn't statically build everything. Next.js ISR was the answer:

typescript
// Revalidate every 60 seconds for ISR
export const revalidate = 60;
// Pre-render top 100 posts, rest on-demand
export async function generateStaticParams() {
const posts = await prisma.blogPost.findMany({
where: { status: 'PUBLISHED' },
select: { slug: true },
take: 100,
});
return posts.map(post => ({ slug: post.slug.split('/') }));
}
This gives me the best of both worlds — top pages are pre-rendered, everything else is generated on first request and cached.

  1. The Prediction Model I won't pretend the ML is groundbreaking. It's an ensemble of:

Historical team performance metrics
Player availability signals
Market movement patterns
Venue and schedule factors
The real insight was: you don't need to predict winners. You need to find where the odds are wrong. A team can lose 60% of the time and still be a profitable bet if the odds overcompensate.

Model Output: Team A wins 45% of the time
Market Odds: Team A at +250 (implied 28.6%)
Edge: 45% - 28.6% = +16.4% value
→ Flag as value bet

  1. Multi-Language Support The app serves both English and Serbian markets. I went with a dictionary-based i18n approach instead of next-intl:

/blog/nba-betting-guide → English
/sr/blog/nba-betting-guide → Serbian (same slug, different content)
Each blog post has title, titleSr, content, contentSr fields in the database. Prisma makes this clean:

typescript
const displayTitle = post.titleSr || post.title;
const articleContent = post.contentSr || post.content;
Mistakes I Made

  1. Accidentally Noindexing My Entire Blog Last month I added a "noindex" directive to blog posts thinking it would "save crawl budget." It did the opposite — Google deindexed 1,700+ pages. Zero organic traffic for weeks.

Lesson: Don't outsmart yourself with crawl budget optimization until you actually have a crawl budget problem.

  1. Over-Engineering the Caching Layer
    My first caching implementation had 5 layers. It was impossible to debug. I stripped it down to 2 layers and everything got faster and more reliable.

  2. Not Tracking Accuracy From Day 1
    I started tracking prediction accuracy months after launch. I lost months of provable data. If your product makes claims, instrument the proof from day one.

Numbers After 10 Months
870+ published articles
231 publicly tracked predictions
+28.5% ROI (verifiable on the site)
18 sports covered (Soccer, NBA, NFL, NHL, Tennis, and more)
$19.99/month pricing (vs competitors at $100-200)
What's Next
Expanding the accuracy tracking system to cover O/U and BTTS markets
Building a proper backtest engine for strategy validation
Exploring WebSocket for real-time odds streaming
Try It
The platform is live at sportbotai.com. There's a free tier — you can see predictions, match analysis, and the AI chat without paying anything.

If you're interested in the technical side, I'm happy to answer questions about the Next.js architecture, the Prisma schema design, or the ML pipeline in the comments.

Built with Next.js, Prisma, Tailwind CSS, and deployed on Vercel. Monitored with Sentry.

nextjs #ai #webdev #startup

Top comments (0)