Web Game File Size: Limits and How to Stay Under Them
Web Game File Size: Limits and How to Stay Under Them
Direct answer: Most HTML5 portals cap uploads at 20MB (some at 50MB), but the performance target should be far lower — under 2MB gzipped if you can. Our Phaser game ships at ~4.4MB raw / ~1.8MB gzipped, and it stays well under every portal limit while loading fast on 4G.
Here is the size math from our actual build, and the three settings that got us there.
1. The limits that actually exist
| Gate | Typical limit | Notes |
|---|---|---|
| CrazyGames upload | ~50MB | Generous, but load time still matters for retention |
| Poki | ~50MB | Same: limit ≠ good idea |
| Most portals | 20MB | A common hard ceiling |
| Player patience | ~2MB gzipped | The limit that decides whether people play |
Portals set limits so the file uploads; players set the real limit with their thumbs. A 20MB game on 4G takes several seconds before the first frame — that is a lost player, not a broken upload.
2. What our build ships (real numbers)
Phaser 3.90 + Vite, one texture atlas, ~40 audio files (OGG):
Total raw: ~4.4 MB
Gzipped: ~1.8 MB
Engine share: most of it — Phaser is the heavy dependency
The engine is the whale. Phaser core plus dependencies is often 1MB+ compressed by itself, before you add a single asset. That is normal — what matters is that your assets don’t double it.
3. The three Vite settings that matter
// vite.config.js
export default defineConfig({
base: './', // 1. deployable to any subdirectory
build: {
assetsInlineLimit: 0, // 2. keep assets as files, not base64
rollupOptions: {
output: {
manualChunks: { phaser: ['phaser'] } // 3. separate engine chunk for caching
}
}
}
});
base: './': relative paths mean the build works on any portal subdirectory, no path fixing per platform.assetsInlineLimit: 0: without this, Vite inlines small assets as base64 into JS, silently inflating the main bundle. Zero keeps images and audio as real files that the browser can cache.manualChunks: Phaser in its own chunk means returning players re-download only your game code, not the engine — for portals this also makes the “engine cached, game updated” pattern work.
4. Asset-side wins (bigger than config)
- One sprite atlas instead of 30 loose images: our fish sprites, icons, and UI live in a few atlases. Fewer requests, better compression.
- OGG audio over MP3: audio was the largest asset group; OGG at a reasonable bitrate cut it roughly in half.
- Compress source art: 2048px art downscaled to what the 1280×720 canvas actually shows saved megabytes. A 512px fish sprite at 4x is invisible to the player and huge to the network.
Common mistakes
- Inline-everything builds:
assetsInlineLimittoo high → base64 bloat in the JS bundle that gzip can’t fix. Keep it at 0 and let files be files. - Uploading debug builds: source maps, dev-only scenes, and unminified code ship weight you don’t need. Always upload the production
dist/output. - Chasing the portal limit instead of the player limit: a 19MB game passes review and loses the player who waits 6 seconds for frame one.
Related reading
- HTML5 Game FPS Boost: 9 Tips That Actually Help
- Web Game Load Time: Cut Seconds, Keep Players
- How to Publish a Game on CrazyGames
Written by ruofan, independent HTML5 game developer. Build numbers come from Merge Fish 2048 (Phaser 3.90 + Vite), measured from npm run build. Config reference: Vite build docs.