Flutter Web WebAssembly artwork

Flutter Web Wasm: Build Faster Apps with –wasm

Flutter Web can compile Dart to WebAssembly with the `–wasm` flag. Flutter reports that more than half of existing Flutter web apps can already compile to Wasm without code changes, while the main migration blocker is legacy JavaScript interop.

TL;DR

Upgrade Flutter, build with Wasm, then fix any dependency warnings:

flutter upgrade
flutter build web --release --wasm

The output is written to `build/web`, just like a normal Flutter web release build.

Prerequisites

  • Flutter 3.47.6 stable
  • Dart 3.13.5
  • A Flutter web project
  • A WasmGC-capable browser for the Wasm path
  • `web ^1.1.1` if your app needs browser APIs and is migrating away from `dart:html`

Step 1 – Check for Wasm Compatibility

A normal Flutter web build performs a Wasm dry run and warns about incompatible imports:

flutter build web

Look for warnings that mention `dart:html`, `dart:js`, or `package:js`.

Those APIs are not compatible with Dart’s Wasm compiler. Prefer `package:web` and `dart:js_interop`.

Step 2 – Replace Legacy Browser Imports

Add the modern browser bindings package when needed:

flutter pub add web:^1.1.1

For example, replace:

import 'dart:html';

With:

import 'package:web/web.dart';

A small DOM example looks like this:

import 'package:web/web.dart';

void updateTitle(String value) {
  document.title = value;
}

For direct JavaScript interop, use `dart:js_interop` instead of `dart:js` or `package:js`.

Step 3 – Build the Wasm Release

Build the production bundle:

flutter build web --release --wasm

Flutter also produces a JavaScript fallback. If the browser cannot use the Wasm path, the generated app can fall back to JavaScript.

For local development:

flutter run -d chrome --wasm

Step 4 – Add Server Headers for Multithreaded Rendering

Flutter Web Wasm can use multithreaded rendering when the server sends the required cross-origin isolation headers.

Configure these responses on your hosting platform:

Cross-Origin-Embedder-Policy: credentialless
Cross-Origin-Opener-Policy: same-origin

`Cross-Origin-Embedder-Policy: require-corp` is also supported, but it can require stricter handling for cross-origin resources.

Test fonts, images, analytics, authentication flows, and third-party embeds after enabling these headers.

Step 5 – Verify Wasm Is Actually Running

Use Dart’s compile-time environment value:

const isRunningWithWasm =
    bool.fromEnvironment('dart.tool.dart2wasm');

You can expose that value in a debug-only status widget:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

const isRunningWithWasm =
    bool.fromEnvironment('dart.tool.dart2wasm');

class RuntimeBadge extends StatelessWidget {
  const RuntimeBadge({super.key});

  @override
  Widget build(BuildContext context) {
    if (kReleaseMode) {
      return const SizedBox.shrink();
    }

    return Text(isRunningWithWasm ? 'Wasm' : 'JavaScript');
  }
}

This is more reliable than guessing from filenames or browser developer tools.

Step 6 – Add Source Maps for Production Errors

Release Wasm builds strip debug symbols by default. Generate a source map for error monitoring:

flutter build web --wasm --source-maps

For staging or QA, preserve Wasm function names:

flutter build web --wasm --no-strip-wasm

Flutter documents an approximately 47 percent Wasm binary size increase when `–no-strip-wasm` is used, so keep it out of normal production builds.

Verify It Works

  • Build with `flutter build web –release –wasm`.
  • Serve `build/web` over HTTP or HTTPS instead of opening `index.html` directly.
  • Check the app in a supported Chromium browser and confirm `dart.tool.dart2wasm` reports true.
  • Exercise animation-heavy screens and compare frame timing with the JavaScript build.
  • Check the network response headers when using multithreaded rendering.

Troubleshooting

  • Error – dart:html unsupported

Fix – migrate browser APIs to `package:web`.

  • Error – package:js or dart:js is imported

Fix – migrate the interop layer to `dart:js_interop`.

  • Error – a dependency blocks Wasm compilation

Fix – update the package first. If the package still uses legacy interop, inspect the compiler dependency chain to identify the exact import path.

  • Wasm build succeeds but the browser uses JavaScript

Fix – confirm the browser supports the required WasmGC path. Flutter includes JavaScript fallback behavior.

  • Multithreaded rendering does not activate

Fix – verify the COEP and COOP response headers on the deployed HTML and assets.

Variations / Alternatives

  • Keep the default JavaScript build if a critical dependency still relies on legacy interop.
  • Use conditional imports when an integration needs separate implementations for JavaScript and modern JS interop.
  • Test Wasm in staging first, then compare frame time, startup behavior, memory, and third-party integrations before production rollout.

Performance & Pitfalls

  • Flutter’s August 2026 benchmark showed up to 2x faster total frame times and about 2.5x faster widget build time in a specific Chrome benchmark. Treat those numbers as workload-specific, not a guarantee for every app.
  • The Flutter team reported that Wasm compressed bundle size was within 5 percent of the JavaScript version in its benchmark.
  • Chrome on iOS still uses WebKit, and the Flutter docs currently note Wasm renderer compatibility limitations in Safari and Firefox. Keep the JavaScript fallback tested.
  • Third-party scripts and cross-origin assets can be affected by COEP and COOP headers, so validate the deployed app rather than only the local build.

References

Related FlutterWire Posts

Tested Versions

Version references verified against Flutter 3.47.6 stable, Dart 3.13.5, and web 1.1.1.



Leave a Reply

Your email address will not be published. Required fields are marked *