The Requirements
Before picking any tools, I listed what I actually needed. PriceGuess would require server-side rendering for SEO and initial page loads, interactive React components for the game itself, a database for products and scores, scheduled tasks for daily product rotation, and eventually real-time WebSocket support for multiplayer. Oh, and it had to cost basically nothing to run.
Why Astro 5
I considered Next.js first since it's the default choice for most React developers. But Astro caught my attention with its island architecture. Most of PriceGuess is static content: the layout, navigation, footer, blog. Only the actual game component needs to be interactive. Astro lets me ship zero JavaScript for static parts and hydrate only the React islands that need interactivity.
// In Astro, only the game component ships JavaScript
// Everything else is pure HTML
---
import GameBoard from '../components/GameBoard';
import Layout from '../layouts/Layout.astro';
---
<Layout title="PriceGuess Israel">
<GameBoard client:load />
</Layout>
Astro 5's SSR mode also runs natively on Cloudflare Workers, which was a huge plus. The adapter is first-class, and the developer experience is smooth.
Why React 19
The game UI is genuinely interactive. Players type guesses, see animated feedback, get confetti on wins, and share results. React 19 was the natural choice for these components. I briefly considered Svelte (smaller bundle) and SolidJS (faster reactivity), but React's ecosystem and my familiarity with it won out. When you're building a side project, going with what you know well is often the smartest move.
Why Tailwind CSS 4
Tailwind CSS 4 dropped the config file requirement and introduced CSS-native configuration. For PriceGuess's Hebrew RTL interface with gradient animations and responsive design, Tailwind made prototyping incredibly fast:
// Purple/pink gradient theme with RTL support
<div className="bg-gradient-to-br from-purple-600
to-pink-500 text-white p-6 rounded-2xl
rtl:text-right shadow-xl">
<h2 className="text-2xl font-bold">{productName}</h2>
</div>
Why Cloudflare Over Everything Else
This was the decision that shaped the entire project. I compared three hosting approaches:
- Vercel + Supabase: Great DX, but costs add up. Serverless function cold starts worried me for a game that needs to feel instant.
- AWS (Lambda + DynamoDB): Powerful but complex. Way too much infrastructure management for a solo project.
- Cloudflare (Pages + Workers + D1): Free tier covers everything. Workers run at the edge with near-zero cold starts. D1 gives me SQLite in the cloud. And Durable Objects would later enable real-time multiplayer.
Cloudflare's free tier is absurdly generous: 100,000 Worker requests per day, 5GB D1 storage, and unlimited Pages bandwidth. For a daily game that most players visit once a day, this was more than enough.
// wrangler.toml - the entire backend config
name = "priceguess-israel"
compatibility_date = "2024-09-23"
[[d1_databases]]
binding = "DB"
database_name = "priceguess-db"
database_id = "7b71d847-2934-4467-98ff-83c6b4699ea1"
[triggers]
crons = ["0 0 * * *"] # Daily scrape at midnight
The Serverless Decision
Going fully serverless meant no servers to manage, no Docker containers, no SSH-ing into boxes at 2am. The entire backend is just functions that run on demand. The database is managed. The CDN is automatic. I could focus entirely on building the game instead of managing infrastructure.
Looking back, this stack was the right call. Astro's island architecture kept the bundle small, React handled the interactive bits beautifully, and Cloudflare's edge network made everything fast. In the next post, I'll dive into the actual game logic and how the guessing system works.