Making a game that works with no internet

21 October 2026 · 6 min read

A game you cannot play on a train is a game that gets uninstalled. Candy Mass is a browser-based game wrapped as an Android app, which normally means it needs a connection to load. It does not, because of a service worker.

What a service worker is

A service worker is a script that sits between your page and the network. Every request the page makes passes through it first. That means the worker can decide to answer from a local cache instead of going to the network at all.

Register it once, and the game can load with the phone in aeroplane mode.

The caching strategy, layer by layer

Not everything should be cached the same way. The game uses three different strategies for three different kinds of file.

HTML: network first

The page itself is fetched from the network first, and only falls back to the cache if the network fails. This matters because the HTML is what changes between versions. If it came from cache first, players would keep loading an old build long after an update shipped.

Code: network first, too

This one was originally wrong and it caused a bug that took a while to trace. The JavaScript and CSS were cached first, with the network updating in the background. That sounds efficient. What it actually means is that the player runs last update's code, every time, forever one version behind.

Worse, if a bad build ever got into the cache, it stayed. The player could not fix it by reinstalling because the cache lived in the browser profile, not in the app package.

The fix was to treat code as network-first as well: fetch fresh, fall back to cache only when offline. It costs a few kilobytes on each launch and it guarantees the player is running the build you actually shipped.

Images: cache first

Sprites, backgrounds and icons never change between builds. They are fetched from the cache immediately and only downloaded once. This is what makes the game open instantly on the second launch.

The thing that bit us

The stale-code problem is worth describing in detail because it is invisible.

A player reported that the game froze on a particular round. Reproducing it locally was impossible: the current build worked fine, every test passed, and nothing in the logic could freeze there.

The cause was that the player's device was running a build from several versions earlier, served silently from the cache. The bug they were hitting had already been fixed. Their phone simply never received the fix.

Nothing in the app said "you are running an old version". There was no error. It just behaved like a bug in the current build, which is the most confusing kind of bug report.

The version bump

Cache entries are stored under a name. When the strategy changed, the cache name was bumped as well, and the install handler deletes any cache whose name is not the current one. That is what forces every existing install to drop the old copy instead of keeping it around.

const CACHE = 'candymass-v4-10';

self.addEventListener('activate', e => {
  e.waitUntil(
    caches.keys().then(keys =>
      Promise.all(
        keys.filter(k => k !== CACHE)
            .map(k => caches.delete(k))
      )
    )
  );
});

Without the delete step, the old cache sits there forever and the bump accomplishes nothing.

What "offline" actually means here

The game plays fully offline. Rounds, scores, coins and unlocked baskets all live in local storage and work with no connection.

Three things do require a connection: signing in with a Google account, syncing a save to the cloud, and submitting a score to the global leaderboard. All three degrade quietly. If the network is down, the score is saved locally and the cloud write is skipped. Nothing is lost; it just does not travel.

That behaviour is deliberate. A game should never block on a network call it does not need.

Is a service worker worth it

For a browser-based game, yes, and for three reasons. It makes the game playable offline, which matters on commutes and flights. It makes the second launch nearly instant. And the Play Store treats a proper offline experience as a sign that the app is more than a bookmark, which matters when packaging a web game as an Android app.

The cost is one extra file and one extra thing to get right. The one thing to get right is that code must not come from the cache first, and the cache must be deleted when it changes. Everything else is straightforward.

← Back to all articles

▶ Play Candy Mass
© 2026 Mass Interactive Games · massinteractivegames@gmail.com