A Web Game Performance Checklist

Updated June 2026
This checklist covers every major performance optimization category for web games. Work through it systematically: establish a baseline first, optimize each category in order, and measure the impact after each change. No single optimization makes a game fast, but applying all of them together transforms a sluggish prototype into a polished, responsive experience.

Performance optimization is most effective when it follows a repeatable process. Developers who optimize reactively, fixing whatever problem is loudest at the moment, waste time on low-impact changes and miss systemic issues. This checklist provides a structured path through every optimization category so nothing gets skipped. Use it during development, not just before launch, because some optimizations are architectural decisions that become expensive to retrofit later.

Establish a Baseline

Before changing anything, measure your game's current performance on your target hardware. Record these numbers:

Load metrics: Total transfer size in MB. Number of HTTP requests. Time to first interactive frame in seconds. Time to fully loaded in seconds.

Runtime metrics: Median frame time in milliseconds. 95th percentile frame time (this captures stutter spikes). Draw call count per frame. Triangle count per frame. JavaScript heap size at rest and peak during gameplay.

Device matrix: Run these measurements on at least three device tiers: your development machine (high-end), a mid-range laptop or tablet (your average user), and the lowest-spec device you want to support (budget phone or old Chromebook). Performance bottlenecks are often different on each tier. Your development machine might be GPU-bound while a budget phone is CPU-bound.

Store these baseline numbers in a spreadsheet or performance dashboard. After each optimization pass, re-measure and compare. If a change did not improve the numbers that matter, reconsider whether it was worth the complexity it added.

Optimize the Loading Pipeline

Loading is the first thing every player experiences. A slow load is a lost player.

Code splitting: Break your JavaScript into a small bootstrap bundle (under 100 KB compressed) that initializes the game shell, and larger chunks that load on demand. Use dynamic import() for level-specific code. Verify with a bundle analyzer that no unnecessary dependencies inflate the initial bundle.

Transport compression: Verify that your server or CDN serves Brotli-compressed responses for HTML, CSS, JavaScript, and JSON. Check the Content-Encoding header in the Network panel. If it says gzip instead of br, upgrade your server configuration.

Tiered loading: Classify assets into three tiers. Tier 1 (critical) loads before the game starts. Tier 2 (current level) loads during the initial screen. Tier 3 (everything else) loads during idle time. Verify that Tier 1 is under 2 MB.

Caching: Use content-hashed filenames and set Cache-Control: max-age=31536000, immutable on static assets. Returning players should load from cache with zero network requests. Consider a Service Worker for pre-caching critical assets.

CDN configuration: Verify that your CDN has edge servers in regions where your players are concentrated. Check cache hit rates in your CDN dashboard. A cache hit rate below 90% indicates misconfigured caching headers.

Optimize Assets

Assets are 80 to 95 percent of your download. Compressing them is the highest-impact optimization.

Textures: Convert all textures to KTX2 with Basis Universal. Use UASTC for quality-critical textures and ETC1S for everything else. Right-size every texture to the minimum resolution that looks acceptable at its typical viewing distance. Power-of-two dimensions only. Generate mipmaps for all 3D textures.

Atlases: Pack 2D sprites into atlas sheets. Pack 3D material textures that share shaders into texture atlases. Target a maximum of one atlas bind per draw call batch. Add 1 to 2 pixel padding to prevent bleed at sub-image boundaries.

Meshes: Compress glTF models with Draco (for maximum compression) or Meshopt (for faster decompression). Remove unused vertex attributes. Simplify meshes for objects that will be viewed at a distance (LOD meshes).

Audio: Encode all audio as Opus. Music at 96 to 128 kbps, sound effects at 48 to 64 kbps. Stream music tracks instead of loading them entirely. Start from lossless source files, never transcode from one lossy format to another.

Channel packing: Combine grayscale maps (roughness, metallic, AO) into RGB channels of a single texture. This reduces texture count, bind count, and memory by up to 4x for PBR material workflows.

Optimize Rendering

Rendering optimization reduces GPU workload and CPU overhead from draw call submission.

Draw call batching: Merge static geometry that shares materials into combined vertex buffers. Use instanced rendering for repeated objects (particles, vegetation, crowd). Target under 200 draw calls per frame on mobile, under 1000 on desktop.

State change sorting: Sort draw calls by shader program first, then by texture, then by other state. This minimizes the number of state transitions per frame. Your engine may do this automatically, but verify with SpectorJS that consecutive draw calls actually share state.

Frustum culling: Verify that your engine skips objects outside the camera frustum. Add distance culling for small objects beyond a maximum draw distance. Consider occlusion culling for indoor and urban scenes with heavy occlusion.

Overdraw: Sort opaque objects front-to-back for depth buffer rejection. Minimize transparent surface area. Use alpha testing instead of alpha blending for hard-edged cutouts. Render particles at reduced resolution. Check overdraw with SpectorJS frame stepping.

Shader optimization: Move per-pixel calculations to per-vertex where possible. Use shader LOD for distant objects. Avoid branching in fragment shaders. Remove unused shader features with preprocessor defines rather than runtime conditionals.

Resolution scaling: Implement dynamic resolution scaling that drops to 75% or lower when frame time exceeds the budget. Render UI at native resolution regardless of 3D render scale.

Optimize Memory

Memory optimization prevents GC stalls and device crashes.

Object pooling: Pool every frequently created and destroyed object type: projectiles, particles, enemies, UI notifications, event objects, temporary collision results. Pre-allocate pools at load time. Target zero allocations per frame during gameplay.

Per-frame allocations: Replace vector math that returns new objects with in-place operations. Replace map, filter, reduce with for loops in hot paths. Cache formatted strings. Avoid closures inside the game loop.

TypedArrays: Store game state in TypedArrays to keep data off the GC heap. Use Float32Array for positions and velocities, Int32Array for health and scores, Uint8Array for flags and states. This also improves cache locality.

GPU resource lifecycle: Dispose textures, geometries, and materials when transitioning between levels. Track references and release resources when their count reaches zero. Never rely on garbage collection to free GPU resources.

Memory budgets: Set explicit budgets (for example, 64 MB textures, 16 MB meshes, 16 MB audio, 32 MB heap). Monitor usage in your HUD. Investigate any category that exceeds its budget.

Leak testing: Play for 30 minutes and compare heap snapshots at start and end. Any growth indicates a leak. Common sources: event listeners, detached DOM nodes, undisposed GPU resources, growing arrays that are never trimmed.

Optimize CPU and Scripting

CPU optimization ensures game logic completes within the frame budget.

Game loop: Use requestAnimationFrame exclusively. Never use setInterval or setTimeout for the game loop. Use the timestamp parameter for delta-time calculations.

Web Workers: Offload physics, pathfinding, procedural generation, and AI to Web Workers. Use SharedArrayBuffer for high-throughput data sharing and transferable objects for one-time transfers. Keep the main thread for rendering and input handling.

WebAssembly: Compile CPU-intensive subsystems to Wasm for 2x to 10x speedups. Physics engines (Rapier, Ammo.js), pathfinding algorithms, and audio processing are common candidates. Load Wasm modules asynchronously so they do not delay startup.

Algorithms: Use spatial data structures (hash grids, quadtrees, BVH) for collision detection and visibility queries. These run in O(n) or O(n log n) instead of O(n squared) brute force. Profile to confirm the algorithmic change improved the specific bottleneck.

DOM avoidance: Never read DOM layout properties (offsetWidth, getBoundingClientRect) during the game loop. DOM reads trigger forced layout recalculation which can cost 5 to 15ms. Cache layout values at initialization and update them only on resize events.

Test and Monitor

Testing validates that your optimizations work on real hardware and real networks.

Hardware testing: Test on your lowest-spec target device. Run the full game at the same settings your players will use. If your game targets mobile, test on a phone that is two to three generations old, not the latest flagship.

Network testing: Throttle to "Fast 3G" in Chrome DevTools and load your game. If it takes more than 10 seconds, your initial payload is too large. Test from different geographic regions to catch CDN coverage gaps.

Automated testing: Set up a CI pipeline that runs your game in a headless browser, plays a scripted sequence, and records frame times. Fail the build if the 95th percentile frame time exceeds your budget. Track results over time to catch gradual regressions.

Performance HUD: Build an in-game HUD that displays frame time, draw calls, triangle count, and heap size. Keep it visible during development so you notice regressions immediately instead of discovering them weeks later during a dedicated profiling session.

User telemetry: If your game has analytics, report frame time percentiles, load time, and device info from real players. Aggregate data reveals the actual distribution of hardware your audience uses, which may differ from your assumptions.

Browser diversity: Test in Chrome, Firefox, and Safari. Each browser has different JavaScript engine performance, different WebGL driver behavior, and different memory management. A game that runs smoothly in Chrome might stutter in Safari due to different GC timing.

Key Takeaway

Follow this checklist systematically rather than chasing individual symptoms. Measure before and after every change. Test on the hardware and networks your players actually use. Performance optimization is a continuous process, not a one-time task.