<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: virtuarian</title>
    <description>The latest articles on DEV Community by virtuarian (@virtuarian).</description>
    <link>https://dev.to/virtuarian</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4245918%2F4bd6c942-d19e-40d5-af00-f43ebbb23404.png</url>
      <title>DEV Community: virtuarian</title>
      <link>https://dev.to/virtuarian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/virtuarian"/>
    <language>en</language>
    <item>
      <title>How to Publish the Game You Just Built on Free Tiers — Lessons From 7,000 Players and One Dead Database</title>
      <dc:creator>virtuarian</dc:creator>
      <pubDate>Sun, 15 Nov 2026 12:28:46 +0000</pubDate>
      <link>https://dev.to/virtuarian/how-to-publish-the-game-you-just-built-on-free-tiers-lessons-from-7000-players-and-one-dead-3ail</link>
      <guid>https://dev.to/virtuarian/how-to-publish-the-game-you-just-built-on-free-tiers-lessons-from-7000-players-and-one-dead-3ail</guid>
      <description>&lt;p&gt;Your game is finished. It runs, it's fun, you could show it to someone today.&lt;/p&gt;

&lt;p&gt;So — how do you publish it?&lt;/p&gt;

&lt;p&gt;That question stopped me for longer than I'd like to admit. Not the game part — the &lt;em&gt;publishing&lt;/em&gt; part. Where does it live? Does a leaderboard mean renting a server? What happens to the bill, and to the game, if people actually show up?&lt;/p&gt;

&lt;p&gt;I ship browser games as a solo developer, run them entirely on free tiers, and measure everything. This article is the best-practice path I can draw from those measurements — including the day ~7,000 players came, and the day one of my databases died.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing is only three parts
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static hosting&lt;/strong&gt; — the shelf that delivers the game itself (HTML, images, code). Free and effectively unlimited on the major platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The API (a Worker)&lt;/strong&gt; — the doorway that does the processing: returning the leaderboard, receiving scores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The DB&lt;/strong&gt; — the record book that stores scores and rankings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A visitor receives the game from static hosting. Only when the game needs the leaderboard does the browser call the API, and the API reads or writes the DB. That's the whole map.&lt;/p&gt;

&lt;h2&gt;
  
  
  The day it held: 7,000 players, 89.7% of the cap, zero errors
&lt;/h2&gt;

&lt;p&gt;One of my games had a ~7,000-player day on Cloudflare Workers' free tier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;89,720 invocations&lt;/strong&gt; of the 100,000/day free cap (89.7%)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Errors: 0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The database (TiDB Cloud) ended the month at ~22% of its free RU pool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The design rule that made it hold: &lt;strong&gt;only the APIs call the Worker — merely opening the game page never does.&lt;/strong&gt; If showing the game screen hits your server every time, raw page views eat your allowance. Restrict the server to API calls and consumption becomes proportional to how much the online features are used.&lt;/p&gt;

&lt;p&gt;That gives you the number that actually matters — the &lt;strong&gt;per-player unit cost&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;89,720 calls ÷ 7,000 players ≈ 12.8 calls per player
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with a unit cost, you can compute your ceiling &lt;em&gt;before&lt;/em&gt; you launch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100,000 (daily cap) ÷ 12.8 (calls per player) ≈ 7,800 players/day
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The day it died: the database counts what it scans
&lt;/h2&gt;

&lt;p&gt;Another game used Cloudflare D1 as its database. What I had missed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D1 does not count the rows it returns. It counts the rows it scanned to find them.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The culprit was the leaderboard query — a prefix-match lookup that returned a small board and, behind the scenes, scanned an average of &lt;strong&gt;~792 rows per call&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6,402 executions → &lt;strong&gt;5,070,000 rows scanned&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Total reads that day: ~6,000,000 — past the 5,000,000 rows/day free cap&lt;/li&gt;
&lt;li&gt;Writes: 143,000 — past the 100,000 rows/day cap as well&lt;/li&gt;
&lt;li&gt;Result: &lt;strong&gt;2,490 API calls (10.4%) ended in exceptions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here's the failure mode nobody warns you about: it didn't go down all at once. It failed &lt;em&gt;probabilistically&lt;/em&gt;, a few calls at a time — the kind of breakage you notice late.&lt;/p&gt;

&lt;p&gt;That night I migrated the game from D1 to TiDB — 51,954 rows, ~10.2 MB — and verified the move three ways: row count, data size, and real play (a score submitted and appearing on the leaderboard).&lt;/p&gt;

&lt;h2&gt;
  
  
  The correction: counting method × reset unit
&lt;/h2&gt;

&lt;p&gt;Whether a free tier holds is &lt;strong&gt;not&lt;/strong&gt; decided by your expected player count. It's decided by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What the limit counts&lt;/strong&gt; — requests / scanned rows / workload units (RU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reset unit&lt;/strong&gt; — daily vs monthly&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;What it counts&lt;/th&gt;
&lt;th&gt;Reset&lt;/th&gt;
&lt;th&gt;Free cap (at time of writing)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;td&gt;Invocations&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;100,000/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare D1 (reads)&lt;/td&gt;
&lt;td&gt;Rows read, &lt;em&gt;including scanned&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;5,000,000/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare D1 (writes)&lt;/td&gt;
&lt;td&gt;Rows written&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;100,000/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloudflare KV (writes)&lt;/td&gt;
&lt;td&gt;Writes&lt;/td&gt;
&lt;td&gt;Daily&lt;/td&gt;
&lt;td&gt;only 1,000/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TiDB Cloud&lt;/td&gt;
&lt;td&gt;RU (converted workload)&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;50,000,000 RU/month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On my biggest day, Workers hit 89.7% of a &lt;em&gt;daily&lt;/em&gt; cap while TiDB used only ~3% of its &lt;em&gt;monthly&lt;/em&gt; pool. Daily-reset limits are the walls; monthly pools absorb spikes. Same players, completely different risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compute your ceiling before you launch
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;List your APIs&lt;/strong&gt; — only the calls that reach the server consume anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure unit costs&lt;/strong&gt; — play your own game, count calls per play session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure the scans&lt;/strong&gt; — for each main query, how many rows does one execution scan? Mark any query "unverified" until you know. Nothing ships as unverified.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down caps and reset units&lt;/strong&gt; — from the official docs, on launch week, not from memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Divide&lt;/strong&gt; — cap ÷ unit cost = your ceiling in players (per day or per month, matching the reset unit).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Design for the fall
&lt;/h2&gt;

&lt;p&gt;Free-tier insurance is boring and effective:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep results and progress in &lt;strong&gt;localStorage&lt;/strong&gt;, so a dead backend doesn't erase the player's records.&lt;/li&gt;
&lt;li&gt;Prepare &lt;strong&gt;fallback displays&lt;/strong&gt;: if the leaderboard can't be fetched, show a notice plus the player's own local records — never an error screen.&lt;/li&gt;
&lt;li&gt;Never make &lt;em&gt;starting the game&lt;/em&gt; depend on a server response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honest line: automatic re-sending of failed submissions is &lt;strong&gt;not&lt;/strong&gt; implemented in my setup. "Saved on the device" and "will sync later" are different promises — don't blur them in your UI copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pre-launch checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Displaying the game never calls the server; only APIs do&lt;/li&gt;
&lt;li&gt;[ ] Calls per player measured and written down&lt;/li&gt;
&lt;li&gt;[ ] Scanned rows checked for the main queries (especially the vague "search" ones)&lt;/li&gt;
&lt;li&gt;[ ] Headroom known for every daily-reset cap&lt;/li&gt;
&lt;li&gt;[ ] Fallback display and local saves tested by deliberately breaking the connection&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The full guide
&lt;/h2&gt;

&lt;p&gt;This article is the condensed version. The full guide — every calculation step shown, a copy-ready conversion sheet (filled example + blank) for your own game, the complete D1 → TiDB migration pattern, and the fallback design in detail — is on Gumroad for $5:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://virtuarian.gumroad.com/l/ship-on-free-tiers" rel="noopener noreferrer"&gt;How to Publish the Game You Just Built — Best Practices for Shipping an Online Browser Game on Free Tiers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It was originally published in Japanese; this is the English edition. If you run the backwards calculation for your own game, I'd genuinely like to hear what ceiling you got.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>webdev</category>
      <category>cloudflare</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
