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
- Flutter 3.47.6: Material UI, Impeller, Wasm Updates
- Dart 3.13 Primary Constructors: New Syntax Explained
- Flutter Desktop Windowing API: Multi-Window Apps
Tested Versions
Version references verified against Flutter 3.47.6 stable, Dart 3.13.5, and web 1.1.1.

Leave a Reply