DEV Community

ShengXing Chi
ShengXing Chi

Posted on

Building a bilingual browser game with Astro and Cloudflare (and making the HTML actually cache)

I recently shipped Cá Viên Chiên, a small cooking game about Vietnamese fried fish ball street carts. It runs in the browser, works best on phones, and ships in Vietnamese and English. Here are the parts that were more interesting than I expected.

Why Astro for a game

Most of the site isn't the game — it's the landing page, guide, tips, menu and FAQ in two languages. Those pages matter for search, so they should be plain static HTML. Astro gives me that, and the game itself is a single island of vanilla JS (~26 KB) with no framework runtime. CSS is under 5 KB.

i18n routing: /vi/ and /en/

Each language lives under its own prefix, with hreflang alternates pointing at each other. The root / redirects based on Accept-Language, which turned out to be the one thing I couldn't cache cleanly (more below).

Saving without accounts

The game saves to localStorage after every purchase and at the end of each day. To move to another device, players copy a save code — the serialized state — and paste it on the other side. No backend, no sign-up.

Getting HTML to actually hit the edge cache

Static assets under _astro/* are hashed and served with max-age=31536000, immutable, so those were fine from day one. HTML was the slow part: every request showed cf-cache-status: DYNAMIC and ~250 ms TTFB going back to origin.

Adding a Cloudflare Cache Rule for /vi/* and /en/* flipped the status to HIT — but TTFB barely moved. The origin was still sending cache-control: public, max-age=0, must-revalidate, and with the rule set to respect origin headers, the edge TTL was effectively zero: every request revalidated.

The fix I'm rolling out is CDN-Cache-Control, which Cloudflare honours ahead of Cache-Control, while browsers keep seeing max-age=0:

/vi/*
  CDN-Cache-Control: max-age=86400
/en/*
  CDN-Cache-Control: max-age=86400
Enter fullscreen mode Exit fullscreen mode

Then purge /vi/ and /en/ in the deploy step so new releases show up immediately.

What I'd do next

  • Move the language redirect on / to the edge so it can be cached per language
  • Self-host the font to drop two extra origins

If you want to see the result, the game is at cavienchien.net. Feedback on the difficulty curve is very welcome.

Top comments (1)