
Here is a failure that almost nobody catches before shipping, because it does not look like a failure from the inside. Your game works perfectly on your machine. Every file returns 200. There is not a single error in the console. And a player who opens the link sits in front of a black rectangle for the better part of a minute, decides the site is broken, and closes the tab.
The short version. A blank canvas with no errors is almost never a crash. It is a game that is still loading, in a framework that draws nothing until loading finishes. We took apart one of our own games to show exactly how a project ends up there — 203 MB of art queued in front of 38 KB of game — and what to measure in your own build to make sure it hasn't happened to you.
It would be easy to write this article about somebody else's game. It would also be worthless, because you would have no way of checking any of it.
So the example here is Pastanation, a pixel-art pasta-cooking game built by SlowDen in Phaser. It is one of our own projects, it is live at slowden.com/games/pastanation/, and it currently has exactly the problem this article is about. If you open it, expect a long wait on a blank screen before anything appears — that is the bug, not a surprise. Every number below came from measuring that live page and the files it serves, on 31 July 2026, and you can re-measure all of them yourself.
Writing this up is more useful than quietly fixing it, because the shape of the mistake is extremely common and almost invisible while you are making it.
Here is the state of that page when a player loads it:
This is the trap. Every diagnostic that a developer instinctively reaches for — the console, the error log, the network panel's red rows — reports that everything is fine. A green network tab is not evidence that your game works. It is only evidence that your files exist.
What none of those tools shout at you is the number that actually matters: how many bytes must arrive before the first frame renders?
Pastanation is 162 files and 203.7 MB in total. Broken down by what those bytes are:
| What it is | Size | Share of total |
|---|---|---|
| The game's own code — every scene, every interaction, the whole design | 38 KB | 0.02% |
| The Phaser engine bundle | 1.13 MB | 0.6% |
| Sound effects and music | 9.1 MB | 4.5% |
| Art (PNG backgrounds, sprite sheets, props) | ~193 MB | ~95% |
Read the first row again. The entire game — every scene, every drag interaction, every line of logic — is 38 KB. The art in front of it is roughly five thousand times larger. That ratio is the whole story, and it is not unusual. It is what happens when art is exported at source resolution and dropped straight into an assets folder, which is the default behaviour of every art pipeline unless someone deliberately intervenes.
The individual files show where it went:
| File | Dimensions | Size | The problem |
|---|---|---|---|
grandma.png | 3375×3375 RGBA | 6.92 MB | A 9-frame sprite sheet at 1125×1125 per frame |
grandma-blinking.png | 3375×3375 RGBA | 6.15 MB | A second sheet of the same character |
bg.png | 2688×1792 RGB | 6.18 MB | A photographic background stored losslessly |
level-1-bg.png | 2688×1792 RGB | 5.33 MB | Same again |
bgm.mp3 | — | 4.91 MB | Full-length music loaded up front |
Two things are worth pulling out of that table.
The backgrounds are bigger than the screen they are drawn on. They are 2688×1792. The game canvas is 1920×1280, and on a normal desktop browser window it is displayed at around 1094×730. So roughly five times more pixels are being downloaded than are ever shown. Nobody decided this; it is just the size the file came out of the art tool.
PNG is the wrong format for most of this. PNG is lossless, which is exactly right for crisp pixel art at its native size and exactly wrong for a 2688×1792 painted background. Those two backgrounds have no transparency at all — they are stored as RGB — so nothing is gained by keeping them lossless.
203 MB is bad, but on its own it would not produce a blank screen, because a well-built game only loads what the current scene needs. The black screen comes from where the bytes sit.
Pastanation's preloader queues eleven things before it hands over to the menu. Nine of them are small. Three are not:
| Queued in the preloader | Size |
|---|---|
grandma.png sprite sheet | 6.92 MB |
grandma-blinking.png sprite sheet | 6.15 MB |
bgm.mp3 background music | 4.91 MB |
| Eight other sounds and icons | ~4 MB |
| Total before the first frame | 20.9 MB |
That 20.9 MB has to arrive in full before the loader's completion callback runs and the first scene draws anything. On a 25 Mbps connection that is about 7 seconds; on a 10 Mbps connection, about 18 seconds; on a mid-range mobile connection, considerably longer. (That's just arithmetic on the byte count — real-world times are worse once you add decode and decompression.)
And here is the part that turns a slow load into a lost player. The preloader contains no progress handler at all — nothing hooked to the loader's progress event, no bar, no spinner, no percentage, not even a "Loading…" label. There is nothing to draw the loading UI with, because the assets that would draw it are stuck in the same queue as everything else.
So the player gets the worst possible combination: a long wait, and no evidence that anything is happening.
The general rule. Your loading screen must be built from assets that are already loaded. If your progress bar is waiting in the same queue it is meant to be reporting on, you do not have a progress bar — you have a black screen with good intentions.
File size is the number everyone looks at. It is not the number that crashes phones.
Once an image is decoded and uploaded to the GPU, an uncompressed RGBA texture occupies width × height × 4 bytes of video memory, no matter how well it compressed on disk. So:
3375 × 3375 × 4 bytes = 45,562,500 bytes ≈ 43 MB
That 6.92 MB PNG becomes roughly 43 MB of texture memory. The second sprite sheet is another 43 MB. Before the menu has drawn a single pixel, the game is asking for something in the region of 86 MB of GPU memory for two pictures of the same character.
On a desktop this is merely wasteful. On a budget phone it is how you get a silent tab crash that never appears in anyone's error log, because the browser kills the page rather than reporting an exception.
None of this needs special tooling. Six steps, in order of how much they'll tell you:
du -sh build/ on macOS or Linux; right-click → Properties on Windows. If that number surprises you, stop and read it properly. Ours did.find . -type f -printf "%s\t%p\n" | sort -nr | head -20 is enough.In rough order of effort-to-payoff:
Cheapest fix, biggest perceived improvement. Have a tiny first scene that loads one small logo, draws the loading UI, and only then queues the real assets while hooking the loader's progress event. Every major framework exposes this — in Phaser it's this.load.on('progress', ...). A moving bar buys you an enormous amount of patience.
Free, and often the single biggest win. If your canvas is 1920×1280, a 2688×1792 background is pure waste. Export at the size you draw it, allowing a sensible margin for high-DPI screens — not five times over.
Keep PNG for crisp pixel art at native resolution. For painted or photographic backgrounds, WebP or AVIF will typically be a fraction of the size at a quality difference nobody can see in a moving game. Keep transparency where you genuinely need it — RGBA costs a third more memory than RGB, and plenty of "transparent" assets don't have a transparent pixel in them.
The menu does not need the level's artwork. Load what the current scene needs, and fetch the next scene's assets while the player is reading the menu. This alone can take a first-load figure from tens of megabytes to a couple.
A grid sprite sheet exported at source resolution wastes both bytes and texture memory on empty space. A proper texture atlas packs frames tightly and ships a small JSON map alongside.
A five-megabyte music loop does not need to be fully downloaded before the title screen appears. Load the short effects up front and stream the music.
Since we're being straight about our own site: there is a second failure on SlowDen right now, and it has the same shape — everything returns 200 and nothing works.
Most of the games listed on SlowDen are third-party titles embedded from an external provider. As of 31 July 2026 those embeds are being refused at the provider's end because slowden.com isn't registered with them as a publisher domain, so the embed returns a block-and-redirect page instead of a game. Every URL involved still answers 200. A link checker sees a healthy site. A player sees a message saying the game is not available here.
We're working on it, and we're saying so here rather than letting the blog imply a catalogue that plays. The reason it belongs in this article is the lesson it repeats: the only test that counts is loading the live page yourself, on the real domain, and pressing play. Not a status code. Not a link checker. Not your local build.
A black screen with no errors almost always means the game is still loading, not that it crashed. Most web game frameworks render nothing until their preload queue finishes, so if that queue is large the player sits in front of a blank canvas. Check the Network tab for the total transferred size before the first frame appears. If it is tens of megabytes, you have a payload problem, not a code problem.
There is no official limit, but the practical target most web developers work to is that something meaningful should be on screen within a few seconds on an average connection. That usually means getting your first-render payload into the low single-digit megabytes and streaming the rest afterwards. The exact number matters far less than the principle: never make the player wait in front of nothing.
PNG is lossless, so it stores every pixel exactly. A 3375×3375 RGBA sprite sheet is over 11 million pixels at four bytes each before compression, and photographic or painterly artwork compresses poorly in PNG. Backgrounds with no transparency are usually far smaller as WebP or AVIF, and sprite sheets with transparency are usually far smaller as WebP.
Yes, and this catches people out. Once decoded and uploaded to the GPU, an uncompressed RGBA texture occupies width × height × 4 bytes regardless of how well the file compressed on disk. A 3375×3375 sheet is about 43 MB of texture memory even if the PNG on disk is 7 MB. This is a common cause of crashes on phones.
Yes, and it should appear before the heavy assets are queued, not after. Load one tiny image in a first scene, draw your loading UI from it, then start the real queue and hook your loader's progress event. A player who can see a bar moving will wait. A player looking at a black rectangle assumes the game is broken and leaves.
So you can check them rather than take our word for it. All measured on 31 July 2026:
grandma.png reports Content-Range: bytes 0-0/7259947, i.e. 7,259,947 bytes.progress handler present.Measure it before you show it to anyone. Ten minutes with the Network tab will tell you more about whether strangers will play your game than another week of features will.
And when it loads the way you want it to, it deserves to exist somewhere other than your hard drive.
Where to publish free in 2026 Submit your game to SlowDen See Slow Cook