Why HTML5 games load faster
Introduction: Speed = Conversion
For games in the browser, "loads faster" means: fewer bytes, fewer requests, less waiting for the first frame. The HTML5 stack (HTML/CSS/JS/WebGL/WASM) gives modern delivery and caching out of the box, which means short TTFB, low LCP and fast Time-to-First-Interaction (TTFI).
1) Network and transport: why the web is faster by default
HTTP/2 и HTTP/3 (QUIC)
Multiplexing: dozens of assets (sprites, chunks of code) come in one connection - there are no TCP "storms."
0-RTT и TLS 1. 3: quick handshake, shorter way to the first byte.
Thread prioritization: critical assets (engine initialization, master atlas) receive higher priority.
CDN and edge cache
POP nodes closer to the player cache immutable assets (hash names).
'stale-while-revalidate'allows you to show the old version and simultaneously pull up the new one.
Titles (recipe):
Cache-Control: public, max-age=31536000, immutable
Content-Encoding: br // для JS/CSS (Brotli)
Cross-Origin-Resource-Policy: cross-origin
Timing-Allow-Origin:
2) Bundling and code-split: delivering "just as much as you need"
ES modules and dynamic import
Divide the code into level packs: "lobby," "tutorial," "levels 1-3," "store."
The initial screen receives only the initialization chunk (50-150 KB gz/br). The rest is on demand.
Tree-shaking and minification
Remove unused engine/library APIs.
Terser/LightningCSS + module sideEffects = false to aggressively cut dead code.
Example of dynamic loading:js
//Load the battle renderer only at the start of the battle const {BattleRenderer} = await import ('./chunks/battleRenderer. js');
new BattleRenderer(). mount(canvas);
3) Assets: Main byte savings
Images
WebP/AVIF for UI/illustrations: minus 25-50% to size vs. PNG/JPEG.
Sprite lists and atlases reduce queries and overhead.
Device/Client Hints: 'Accept-CH: DPR, Width, Viewport-Width' → server/edge gives the desired size.
3D/Textures
Basis/UASTC (KTX2): universal compression of GPU textures (ETC1S/ASTC/BC) - load one file, unpack it in video card format.
Meep levels are loaded progressively: first low quality → then sample.
Audio
Opus instead of MP3/AAC - better at low bitrates; streaming tracks on demand (zone music - after entering the zone).
Video/cutscenes
WebCodecs/MediaSource and LL-HLS for short videos; poster and the first segment - preload, the rest - lazily.
4) Engine initialization: first "skeleton," then "meat"
Lazy scene graph
We load only critical scene nodes (UI, camera, background). Models/shaders - as needed.
Background asset jobs: The bootloader holds a priority queue.
Service Worker (SW) as a "warm cache"
Installed at the first visit and caches the client kernel, atlas manifest, shaders.
Upon re-login - offline readiness and TTFI ~ instantly.
Example of SW strategy (simplified):js self. addEventListener('fetch', e => {
e. respondWith(caches. open('game-v12'). then(async c => {
const cached = await c. match(e. request);
const fresh = fetch(e. request). then(r => { c. put(e. request, r. clone()); return r; });
return cached fresh;
}));
});
5) WebGL and WASM: "native" speed in the browser
WebGL/WebGPU: shaders and render - on GPU; CPU remains on logic.
WebAssembly (WASM): the heavy parts of the engine (physics, path, unpacking textures) work almost like native libraries.
Web Workers: texture/audio decode, level parsing, preparing meshes - no mainstream locks.
First Time to Frame (FTF) optimization:- For the sake of FTF, we sacrifice "beauty": load low-poly/low-res, stream high-res later.
6) Prioritizing loading: let the important pass first
HTML hints:html
<link rel="preconnect" href="https://cdn. example. com">
<link rel="preload" as="script" href="/app. a1b2c3. js" crossorigin>
<link rel="preload" as="image" href="/atlases/ui@1x. avif" imagesrcset="/ui@2x. avif 2x">
Fetch priorities and 'fetchpriority'
'fetchpriority =" high"' - initialization JS and UI atlases.
The rest of the assets are 'low' so as not to interfere with the critical path.
7) Metrics and SLO of "fast" HTML5 games
Targets:- TTFB <200-300 ms (with CDN).
- LCP (lobby) <1. 8–2. 5 s on mobile.
- Time-to-First-Interaction (TTFI) < 2–3 с.
- First Frame In-Game <1-2 s after the start of the scene.
- Total Download (first run): ≤ 1-3 MB per lobby, ≤ 5-10 MB up to the first battle/level.
- Restart: ~ 0-200 KB thanks to SW cache.
Observability: RUM events on networks/geo/devices, Web Vitals, bootloader progress, timeout failures.
8) Pipeline assembly: how to get a "thin first byte"
1. Bundle analysis (source-map-explorer, webpack-bundle-analyzer).
2. Code-split by scenes/features, removal of "thick" polyphylls (we target modern browsers).
3. Minification: Terser/ESBuild + CSS Minify, dev logic removal.
4. Images: 'sharp/squoosh' → AVIF/WebP, generation of 'srcset'.
5. Textures: envelope in KTX2 (Basis/UASTC), creating mips.
6. Audio: Opus VBR 48-96 kbps, clips - shortened previews.
7. Manifest assets (artifact) + hash names + 'immutable'.
8. PWA/SW: kernel pre-cache, runtime cache of atlases with 'stale-while-revalidate'.
9. CDN: Preload-hints, 'Surrogate-Control', Soft-Purge by tag.
9) Runtime performance: to make the game "fly" after loading
Main-thread budget: hold JS-taski <50 ms; heavy - in Workers.
Batch render: group draw-calls, use instancing.
GC pressure: rent arrays/buffers, avoid "garbage" in game tics.
Adaptive FPS: Reduce post-effects when FPS drops without touching gameplay.
10) Anti-patterns (which makes the game slow)
One monolithic bundle 5-15 MB "to start."
PNG textures without GPU compression, no KTX2/Basis.
'rng% N'in the asset loader (determination is more important - but this is also about PF).
Requests without cache headers or without hash names → each visit "cold."
Polyphylls for the whole world (IE, old Safari) - pulling megabytes in vain.
Lack of SW/preloads - repeated visits are as difficult as the first.
"Heavy" fonts (several styles without 'unicode-range' and 'font-display: swap').
11) Fast HTML5 game checklist
Network and CDN
- HTTP/3 enabled; 'preconnect' to CDN/providers.
- `Cache-Control: immutable` + hash-имена; `stale-while-revalidate`.
Code and bundles
- Code split by scene; ES modules; tree-shaking.
- Initialization JS ≤ 150KB br; code cards separately.
Assets
- WebP/AVIF for UI; KTX2 Basis/UASTC for textures.
- Atlases and mips; Opus audio; lazy videos/cutscenes.
PWA/Cache
- Service Worker: kernel pre-cache, runtime cache of atlases.
- A second visit is loaded "from the warm" cache.
Priorities
- 'preload' of critical chunks/atlases; 'fetchpriority' for important.
- Low priority to secondary scenes.
Metrics
- TTFB/LCP/TTFI/FTF/Download-budget on the dashboard.
- Alerts on weight gain, hit-ratio CDN fall.
12) Mini title recipes
Static (JS/CSS/Atlases):
Cache-Control: public, max-age=31536000, immutable
Content-Encoding: br
JSON scene manifestos (often changed):
Cache-Control: public, max-age=60, stale-while-revalidate=120
Surrogate-Control: max-age=60, stale-if-error=600
Fonts:
Cache-Control: public, max-age=31536000, immutable
Cross-Origin-Resource-Policy: cross-origin
HTML5 games load faster because the web platform combines efficient transport (HTTP/2-3 TLS 1. 3), smart delivery (CDN, cache, preload), light assets (WebP/AVIF, KTX2, Opus) and incremental initialization (code split, lazy scenes, SW cache). Add WebGL/WASM and strict metric discipline - and the first frame appears in seconds, and re-entry becomes almost instantaneous.