Optimization
Feature Tags
Godot allows you to detect the platform and adjust settings accordingly. Use Feature Tags (e.g. the "HTML5"/"Web" platform tag) to lower graphical settings or detail when running in a browser. For example, you might reduce shadow resolution, disable certain post-processing effects, or lower physics detail on the web build. This ensures high-end settings are only enabled on native platforms, keeping the web version efficient. You can set up conditional logic in your project or use multiple quality tiers that the game can switch between based on OS.get_name() or feature tags.
2D
- Even though 2D games are usually less demanding, you should still optimize draw calls and overdraw.
- Use sprite sheets or texture atlases to group sprites and minimize the number of separate textures in use (Godot will batch-render sprites that share the same texture/material).
- Avoid very large single-node nodes; instead, use TileMaps or split large images into smaller sections if possible.
- Limit the use of real-time 2D lighting/shadows or excessive shader effects in web exports.
- Profiling your 2D game with the Godot debugger and browser dev tools can help identify if any script or draw call is taking too long.
- Keep the node count reasonable and free nodes that are no longer needed to avoid unnecessary processing each frame.
- Godot 4’s WebGL2 renderer may compile shaders on the fly, causing stutters. A future version of Godot will implement Shader preloading to precompile shaders at build time, eliminating this issue.
3D
3D content is significantly heavier, so aggressive optimization is needed for web.
- Utilize Godot’s built-in culling techniques: view frustum culling is automatic, but you should set up occlusion culling using Occluder nodes in large scenes to avoid rendering objects that the player can’t see.
- Use Level of Detail (LOD) for models if possible, or manually show/hide far-away objects.
- Keep polygon counts and draw calls in check – prefer simpler collision shapes and fewer active rigid bodies if the physics engine struggles.
- Limit real-time lights and shadows; prefer baked lighting or GI Probe alternatives if supported in the Compatibility renderer.
- If your game uses many similar objects (like a forest of trees or an army of units), consider using MultiMesh for instanced drawing to drastically reduce draw calls.
- Finally, test the game on a range of hardware – what runs fine on a high-end PC might be slow on an average laptop browser, so optimize accordingly.
Threading
By default, Godot 4.3+ exports use a single-threaded WebAssembly build for maximum compatibility. Single-threaded mode avoids the need for special server headers, no SharedArrayBuffer isolation required, and works on platforms like macOS and iOS where multi-threaded web exports historically had issues.
If your game is CPU-intensive (especially in 3D), you can consider enabling thread support for better performance – but note that multi-threading on the web requires cross-origin isolation (COOP/COEP headers) due to browser security restrictions. Enabling threads means you must serve your game from an HTTPS domain with proper headers, or use a workaround like Godot’s PWA export, which can simulate the headers via a service worker. In summary:
For broad compatibility (e.g. itch.io or any site where you can't easily set HTTP headers), stick to the default single-threaded export. It's slightly less performant but far easier to deploy.
If you need multi-threading, ensure your hosting environment can send the following headers:
'Cross-Origin-Embedder-Policy': 'require-corp'
'Cross-Origin-Opener-Policy': 'same-origin'
For local testing, here's a working example of a Node.js server that provides the required headers for SharedArrayBuffer:
// server.js
const express = require('express')
const app = express()
const port = 3000
app.use((_req, res, next) => {
res.header('Cross-Origin-Embedder-Policy', 'require-corp');
res.header('Cross-Origin-Opener-Policy', 'same-origin');
next()
})
// Specify your game page directory here
app.use(express.static('./'))
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
})Many hosts like itch.io and Newgrounds provide an option to enable SharedArrayBuffer support. Alternatively, enable Progressive Web App support (discussed below) or use a third-party solution (e.g. a service worker script) to add these headers dynamically.
Profiling
Use Godot’s Profiler and Browser Dev Tools: Always measure performance to find bottlenecks. Godot’s built-in profiler can run during an HTML5 export (pressing F12 in the browser to open the dev console, if not captured by the game input).
Loading
Loading times are critical for web games, since players can get impatient with long waits or might even have data caps. Here are strategies to improve load speed:
Download Size
Godot’s web export produces a .wasm module and a .pck asset package, plus an HTML and JavaScript loader. Reducing the size of these files will directly improve loading times.
Remove any unused assets from your project before export, and consider Godot’s Export Mode options, such as exporting only selected scenes or using the “Export Selected Scenes” feature to include only necessary resource, located in the export settings menu.
Compression
Enable compression for your assets and use efficient formats. Godot automatically compresses textures and other resources in the PCK, but you can get additional gains by serving your files with gzip or Brotli compression on the web server.
Ensure your host is configured to send Content-Encoding: gzip for .wasm, .pck, and .js files. Godot’s official HTML5 exports are already gzip-compressed in some cases, but double-check.
On the asset side, prefer smaller formats: use OGG or MP3 for audio. Avoid WAV except for tiny sound effects, and optimize images. Consider reducing resolution of large background images, or using vector graphics for UI where possible. For textures in 3D, see the section on asset compression below – using GPU-compressed or supercompressed texture formats can massively cut down texture file sizes.
Loading Screen
Use a Loading Screen and Lazy Load Heavy Content. Godot’s HTML5 template shows a default loading bar while the engine initializes. You can customize this by providing a Custom HTML shell or by designing your own loading screen in Godot. During loading, only include the minimum content needed to start.
For a large game, consider splitting assets into multiple packs or using on-demand loading: for instance, load only the menu and first level initially, then stream in additional levels or content in the background once the game has started. Godot’s ResourceLoader.load() or background loading APIs can help here. If splitting the PCK is too advanced, you can at least defer non-critical resource initialization until after the first frame (e.g., load huge audio/music files a few seconds in, when needed). Progressive loading gives the player something to look at sooner and can make the perceived wait shorter.
Server and CDN
Host your game on a fast, global server or CDN if you expect players in various regions. High latency or slow servers will bottleneck your loading speed no matter how much you optimize the files. Also, consider enabling HTTP/2 or HTTP/3 on your server, as they handle multiple small requests more efficiently – though in Godot’s case most data is in one big PCK file and one WASM, which is ideal. If your PCK is extremely large (hundreds of MB), you might even benefit from splitting it (e.g., base game and DLC pcks) and allowing the player to start playing while optional content downloads.
Memory
WebAssembly has a finite memory heap, and browsers impose limits on how much memory a web page can use. Efficient memory usage in a Godot web game prevents crashes and slowdowns.
Limits
Most browsers currently cap WebAssembly memory around 2GB by default (and mobile browsers or 32-bit environments may allow much less). If your game approaches this limit, it will crash with an out-of-memory error. Monitor memory usage by checking Godot’s debug stats or use the browser’s performance/memory tools, and try to stay well below the cap.
Avoid loading huge data sets all at once – for example, don’t preload every level into memory if you can stream or load as needed. If you have a use case that genuinely needs more memory, Emscripten (which Godot’s export uses) can be configured for a higher maximum, but it requires a custom build and certain browser flags, so it’s best to simply optimize to fit standard limits.
Freeing Resources
Godot’s garbage collector will handle orphaned Objects in GDScript, but you as a developer should proactively free large resources when done. Use queue_free() on nodes that are no longer needed. Remember that the user://file system (for saves, etc.) might also consume memory if you write large files to it; though it primarily affects storage, not RAM, be mindful of not reading an entire 100 MB file into memory if you only need part of it.
Assets
3D games are often memory-heavy due to textures and meshes. Mitigate this by using compressed textures (see next section) so that they consume less VRAM and system memory. Unload or free sub-resources like large images, fonts, or meshes when they are not visible. For example, if you have cutscenes or levels that won’t repeat, you can free those textures/materials when the scene is over.
2D games generally use less memory, but very large sprites or numerous frames of animation can add up – consider using lower-resolution graphics for web or limiting the number of simultaneous large images.
The engine’s Monitoring (Debug > Monitor in Godot editor) can show you texture memory usage; use similar approaches during web runtime by printing RenderingServer.get_rendering_info() or other stats to see where memory is going.
Leaks
A memory leak in a web game is especially problematic, since the browser may not reclaim memory until the page is closed. Test your game for extended periods – if memory usage keeps climbing over time without leveling off, you might have a leak.
Common causes include:
- Not freeing dynamically added Nodes
- Constantly allocating new objects, e.g. creating new
ImageorPackedByteArrayeach frame - Creating dynamic Timers using
get_tree().create_timerand freeing the object before it times out - Other API misuses
If you suspect an engine bug, try updating to the latest Godot 4.x version as such issues may have been fixed. In rare cases, enabling thread support alleviates memory growth issues in Godot 4.3 HTML5 – indicating some leaks were present in single-thread mode. Keeping your engine version updated is a good practice for the best memory management.
Data Structures
When coding, be mindful that structures like arrays, dictionaries, or strings take memory. Using streams or iterators to process data chunk-by-chunk (instead of in one giant array) can save memory. And if you have large lookup tables or level data, consider compressing them or storing in a binary form instead of verbose JSON. You can always fetch additional data via HTTP requests if needed rather than bundling everything in-memory from the start.
Compatibility
One of the challenges of web deployment is ensuring the game works on all popular browsers. Godot’s web exports rely on WebAssembly and WebGL 2.0, so your players’ browsers must support those (virtually all modern desktop browsers do). However, there are some differences and gotchas:
- Godot 4.x uses the Compatibility renderer (an OpenGL ES 3.0 / WebGL2-based backend) for web exports. More advanced renderers like Forward+ are not available on the web. WebGL2 has broad support on Chrome, Firefox, Edge, and Safari – but Safari’s implementation lags behind. In fact, Safari (on macOS and especially older macOS or iOS versions) has known bugs and incomplete features in WebGL2 that can cause rendering issues that other browsers don’t have.
- As of Godot 4.4, there is no support for WebGPU yet, so WebGL2 is the only path. To maximize compatibility, test your game on Safari if you can, and consider advising players to use a Chromium-based browser for the best experience if you encounter Safari-specific problems. In some cases, you might need to simplify certain shaders or avoid features that Safari has trouble with.
Ensure that you communicate to players the minimum browser versions required. WebAssembly has been standard for years, so that’s not an issue except on very old browsers. But if you incorporate advanced JavaScript APIs via the GDNative JavaScript bridge, such as WebRTC or other web APIs, then you must consider browser support for those APIs. For example, the Godot documentation notes that features like the Clipboard API, Gamepad API, etc., often require a secure context (HTTPS) and certain browser versions. By sticking to Godot’s built-in capabilities, you mostly avoid these concerns, but if you branch out, check compatibility on MDN or CanIUse for the APIs you call.
C#
C# (Mono) is NOT SUPPORTED in Godot 4.4.1’s web exports as of this writing. If your project is in C#, you cannot export to HTML5 with Godot 4.4 as the Mono runtime isn’t available for WebAssembly yet for Godot 4. In such a case, the Godot docs recommend sticking with Godot 3.6 for C# web targets.
GDNative & GDExtension
For GDScript, C++ GDExtension, and VisualScript, web export works. GDNative/GDExtensions written in C++ need special handling – you’d have to compile them to WebAssembly as well, and enable Extension Support in the export settings. This is advanced; avoid native plugins for web unless necessary. Also remember that Godot’s HTML5 exports currently exclude things like VR (WebXR) unless specifically noted, so check the documentation if you plan to use such features.
Cross-Origin Isolation
As discussed under [[#Threading]], if you enable threads, you’ll need a cross-origin isolated environment. This not only affects your server setup but also means you cannot include certain third-party content that isn’t COI compatible.
For instance, if you embed your game in an iframe on a site that loads ads or third-party widgets, those could break COI and disable your threads. Godot’s PWA service worker can simulate isolation to allow threads even on hosts where you can’t control headers, but be mindful of limitations (e.g. some sites may still not work if they frame your game). If using threads, it’s best to host the game on its own page without cross-origin iframes, or use the "break out of iframe" strategies.
Saving Data
In a web game, you can’t simply write to the local file system. Godot abstracts file access with its user:// and res:// paths. On the web, res:// is read-only (your PCK data), and user:// is an in-browser persistent storage usually backed by IndexedDB. Understanding how to manage save data in this environment is key:
IndexedDB
When you save a file to user:// using Godot’s File API, Godot’s HTML5 platform stores it in the browser’s IndexedDB database. This is great because it’s sandboxed and will persist between sessions – effectively, it’s like writing to a save folder on desktop. However, the browser can decide to delete that data under certain conditions. Notably, incognito/private mode usually disables persistence, and if the user has cookies disabled, it may also block IndexedDB.
It’s good practice to call OS.is_userfs_persistent() at startup – this returns false if, for example, the game is running in a context where data won’t be saved.
[!WARNING]Be aware it can give false positives in some edge cases, but generally it’s a hint.
If you detect non-persistence, you might warn the player that progress won’t be saved in their current setup.
localStorage
Aside from Godot’s built-in system, you might consider using the browser’s localStorage (a key–value store) for very small pieces of data like settings or a single save string. Godot 4 has a JavaScriptBridge singleton which would let you call window.localStorage via a JavaScript snippet if you wanted. But for most cases, sticking with Godot’s file API is simpler and more powerful. One scenario for localStorage could be saving a tiny “was game completed” flag or similar that might survive even if IndexedDB is wiped – but generally, IndexedDB is reliable unless the user is explicitly blocking it.
Updated about 1 month ago
