Flutter Web and WebAssembly: Faster Apps in the Browser
Flutter started as a mobile framework, but since the widgets you write are just Dart describing pixels, there’s nothing mobile-specific about the idea — the same widget tree can be rendered inside a browser tab. That’s Flutter Web. How well it performs, though, depends heavily on what your Dart code gets compiled into, and that’s where WebAssembly enters the picture.
If you’re new to the toolchain generally, start with our Flutter installation guide — everything below assumes you already have flutter on your PATH and web support enabled (flutter config --enable-web).
How Flutter gets into a browser at all
A browser doesn’t understand Dart, so the Dart compiler has to target something the browser does understand. Flutter Web has historically had two moving pieces:
- The compiler target — what your Dart code becomes: JavaScript or, more recently, WebAssembly.
- The renderer — how widgets actually get drawn to the
<canvas>/DOM: CanvasKit (a WebAssembly build of Skia, giving pixel-perfect consistency with mobile) or the HTML renderer (translates widgets to DOM/CSS/SVG, smaller download but less faithful for complex graphics).
These two are independent axes — you choose a compile target and a renderer separately — but the compile target is the newer, more interesting change.
Why WebAssembly instead of JavaScript
For years, flutter build web compiled Dart to JavaScript using dart2js, a mature, heavily optimized compiler. It worked, but Dart and JavaScript have a structural mismatch: Dart is a garbage-collected, statically typed language, and JavaScript engines have to do extra work to run code that wasn’t originally written in JavaScript’s own dynamic style efficiently.
WebAssembly (Wasm) is a low-level, binary instruction format designed to run at near-native speed in the browser. The missing piece for a GC language like Dart was WasmGC — a browser-level garbage collector built into the Wasm runtime itself. Before WasmGC, compiling a GC’d language to Wasm meant shipping your own garbage collector inside the Wasm binary, which bloated downloads and added overhead. With WasmGC, Dart’s objects can be managed by the browser’s native GC, the same one it uses for JavaScript — no bundled GC required.
The practical upshot, compared to the dart2js JavaScript target:
- Faster steady-state execution — Wasm is closer to the CPU, so tight loops (animations, layout, list scrolling) run faster.
- More predictable performance — less reliance on JIT warm-up heuristics that vary across JS engines.
- Smaller, more consistent GC pauses — because the GC is native to the runtime rather than emulated in JS.
Startup time and download size are more nuanced — they depend on your app’s size and the browser’s Wasm/WasmGC support — so “faster” mainly means runtime performance, not necessarily a smaller download.
Building for Wasm in practice
flutter build web --wasm
This produces a Wasm-targeted build alongside the tooling needed to serve it. A few things to plan for before shipping:
- Browser support: WasmGC requires a reasonably modern browser (recent Chrome/Edge and Firefox releases; Safari support has been catching up). Always ship a JavaScript fallback build for browsers that lack WasmGC support — Flutter’s tooling can generate both and let the browser pick.
- Plugin compatibility: any package that relies on
dart:jsinterop written against the old JavaScript-specific APIs may need updates to work under the Wasm compile target; check plugin changelogs before migrating a production app. - Renderer choice still matters: CanvasKit ships its own Wasm-compiled Skia, so a CanvasKit + Wasm-compiled-Dart app is “Wasm all the way down,” which is great for rendering fidelity but adds a sizeable download; the HTML renderer stays lighter but trades off some visual fidelity for complex effects. Note that the renderer question is separate from the Impeller rendering engine used on mobile and desktop — Flutter Web’s rendering story evolves on its own track.
- SEO and first paint: Flutter Web renders everything on a canvas (or a DOM tree it fully controls), not semantic HTML crawlers parse easily. It’s a poor fit for content-heavy, SEO-sensitive sites and a strong fit for app-like experiences (dashboards, internal tools, games) where you already control distribution.
A quick decision checklist
Before you commit to Flutter Web for a project, ask:
- Is this an application (interactive, stateful, behind a login) rather than a content site? Flutter Web fits the former far better than the latter.
- Can you tolerate a JS fallback bundle for older browsers, at least for now?
- Are your critical plugins verified to work under the Wasm compile target?
If the answers line up, flutter build web --wasm gets you a build that runs meaningfully faster than the legacy JavaScript target, using the same Dart codebase you’d ship to mobile.