Unity WebGL (Drift Hunters) on NGINX: correct headers for .br/.gz + COOP/COEP? Getting MIME & decode errors

Hi all,

I’m hosting a Unity WebGL build of Drift Hunters on NGINX (DriftHuntersPlus). Files are precompressed by Unity as Brotli (.data.br, .wasm.br, .js.br). I’m trying to serve them with the correct Content-Type and Content-Encoding and enable cross-origin isolation for multithreading/SIMD.

Environment

  • NGINX OSS 1.24.x (no dynamic brotli module)

  • Ubuntu 22.04

  • Unity 2022 LTS WebGL build
    Files: Build/DriftHunters.data.br, Build/DriftHunters.framework.js.br, Build/DriftHunters.wasm.br, Build/DriftHunters.loader.js

Symptoms

  • Browser console shows either:

    • TypeError: Failed to execute 'compile' on 'WebAssembly': Incorrect response MIME type. Expected 'application/wasm'.

    • or Uncaught (in promise) TypeError: Failed to decompress data / ERR_CONTENT_DECODING_FAILED

  • If I enable COOP/COEP, some assets are blocked with CORP unless I add headers.

Goal

  1. Serve .br files with correct Content-Type and Content-Encoding: br

  2. Fallback to .gz (if I rebuild with gzip) using gzip_static on;

  3. Set COOP/COEP + CORP for isolation

  4. Add long-cache for large assets

Current NGINX config (simplified)

server {
  listen 80;
  server_name game.example.com;
  root /var/www/drift;

  # Cross-origin isolation for WebGL multithreading/SIMD
  add_header Cross-Origin-Opener-Policy "same-origin" always;
  add_header Cross-Origin-Embedder-Policy "require-corp" always;

  # Default index
  location = / { try_files /index.html =404; }
  location = /index.html {
    # short cache to allow quick updates
    add_header Cache-Control "no-cache";
  }

  # Serve Unity precompressed Brotli files (.br)
  location ~* ^/Build/(.+)\.data\.br$ {
    types { application/octet-stream data; }  # or leave default
    default_type application/octet-stream;
    add_header Content-Encoding br;
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Cross-Origin-Resource-Policy "same-origin";
    try_files $uri =404;
  }
  location ~* ^/Build/(.+)\.wasm\.br$ {
    default_type application/wasm;
    add_header Content-Encoding br;
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Cross-Origin-Resource-Policy "same-origin";
    try_files $uri =404;
  }
  location ~* ^/Build/(.+)\.js\.br$ {
    default_type application/javascript;
    add_header Content-Encoding br;
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Cross-Origin-Resource-Policy "same-origin";
    try_files $uri =404;
  }

  # If I rebuild with gzip instead of brotli:
  gzip_static on;
  location ~* ^/Build/(.+)\.(data|wasm|js)\.gz$ {
    # Unity emits .gz files; serve with the base MIME and Content-Encoding gzip
    set $ext $2;
    if ($ext = wasm) { set $ct application/wasm; }
    if ($ext = js)   { set $ct application/javascript; }
    if ($ext = data) { set $ct application/octet-stream; }
    default_type $ct;
    add_header Content-Encoding gzip;
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Cross-Origin-Resource-Policy "same-origin";
    try_files $uri =404;
  }

  # Static assets (images, StreamingAssets)
  location / {
    try_files $uri $uri/ =404;
  }
}

Questions

  1. Is the approach above the recommended way to serve Unity’s precompressed .br assets when the brotli module is not enabled (i.e., just set Content-Encoding: br on static files)?

  2. For .wasm.br, is default_type application/wasm + Content-Encoding: br sufficient for all modern browsers?

  3. Any better way to avoid the Incorrect response MIME type error without relying on if blocks (e.g., using map for content types)?

  4. Are these COOP/COEP/CORP headers correct for Unity WebGL multithreading? Anything else needed (e.g., COEP credentialless)?

  5. For caching: is Cache-Control: public, max-age=31536000, immutable safe for .data/.wasm/.js as long as filenames are content-hashed?

If anyone has a canonical NGINX snippet for Unity WebGL (Brotli and gzip fallbacks), I’d love to see it. Thanks!

— Illia / DriftHuntersPlus

Hey @Drift_Hunters_Plus!

I am not familiar with Brotli myself – I assume you have already checked out the Unity docs covering how to use NGINX for web builds? The doc seems to follow a similar pattern to your config so that should hopefully help guide you in so far as questions 1/2/4. In so far as other purely NGINX specific questions:

The more straightforward solution would be to use the types directive to define your data extension, since both wasm and javascript are already defined by default within the NGINX mime.types file.

Whether this is safe or not depends on your definition of “safe”. This would be a good strategy for aggressive caching. But your file contents are still “easily” accessible since they won’t be hashed and will be cached by any intermediary CDNs.