Build and release a web app
How to prepare for and release a web app.
During a typical development cycle,
you test an app using flutter run -d chrome
(for example) at the command line.
This builds a debug version of your app.
This page helps you prepare a release version of your app and covers the following topics:
- Build the app for release
- Web content hashing and HTTP caching
- Deploy WebAssembly in production
- Deploy to the web
- Deploy to Firebase Hosting
- Handle images on the web
- Minification
- Mobile browser viewport and SafeArea
- Embed a Flutter app into an HTML page
Build the app for release
#Build the app for deployment using the flutter build web command.
flutter build web
To build your application with WebAssembly (Wasm), pass the --wasm flag:
flutter build web --wasm
This
generates the app, including the assets, and places the files into the
/build/web directory of the project.
To validate the release build of your app,
launch a web server (for example,
python -m http.server 8000,
or by using the dhttpd package),
and open the /build/web directory. Navigate to
localhost:8000 in your browser
(given the python SimpleHTTPServer example)
to view the release version of your app.
Additional build flags
#
Pass --release (default), --profile, or --debug to flutter build web
depending on whether you are deploying to production, profiling in Chrome
DevTools, or testing with assertions:
| Build mode | WebAssembly (--wasm) |
JavaScript (default) | Purpose |
|---|---|---|---|
--release (default) |
-O2 (minified, stripped symbols) |
-O4 (minified) |
Production deployment |
--profile |
-O2 --no-strip-wasm (readable function and type names) |
-O4 (unminified symbols) |
Performance and memory profiling |
--debug |
-O0 (assertions enabled) |
-O1 (assertions enabled) |
Development testing |
To override the default compiler optimization level, pass
--optimization-level=LEVEL (-O0 through -O4
for dart2wasm; -O1
through -O4 for dart2js).
For dart2wasm optimization details,
refer to Dart WebAssembly compilation.
Source maps
#
To generate source maps for symbolication in crash reporting services,
pass the --source-maps flag:
flutter build web --source-maps
Generating source maps creates separate .js.map or .wasm.map files
(or both, depending on your build target) in the build/web directory
without adding binary overhead to the compiled output.
Web content hashing (--web-content-hash)
#
To enable automatic content-based cache busting across deployments, pass
--web-content-hash to flutter build web:
flutter build web --wasm --web-content-hash
When --web-content-hash is enabled, Flutter embeds an 8-character truncated
SHA-256 content hash into compiled entrypoints and all files under
build/web/assets/**, and records the hashed paths in flutter_bootstrap.js
(_flutter.buildConfig):
| Artifact group | Files in build/web |
Recommended Cache-Control |
|---|---|---|
| Hashed immutable files |
main.dart.<hash>.js
,
main.dart.<hash>.wasm
,
main.dart.<hash>.mjs
(plus
.js.map
and
.mjs.map
), and all files under
assets/**
(
AssetManifest.<hash>.bin
,
FontManifest.<hash>.json
,
NOTICES.<hash>
, fonts, images, and
shaders/**
)
|
public, max-age=31536000, immutable |
| Unhashed bootstrap and root files |
index.html
,
flutter_bootstrap.js
,
flutter.js
,
manifest.json
,
version.json
,
404.html
,
icons/**
, bundled
canvaskit/**
(with
--no-web-resources-cdn
), and
main.dart.wasm.map
(with
--source-maps
)
|
no-cache (or max-age=0, must-revalidate) |
For a complete firebase.json example, refer to the Web FAQ.
Overlay deployments for active browser tabs
#
When you deploy a new release (N), users with an already-open browser tab
running the previous release (N-1) might lazily fetch an asset, font, or
image whose filename carries release N-1's content hash.
To avoid mid-session 404 errors during rollouts, use an overlay
deployment strategy:
-
Retain at least the previous release's (
N-1) hashedmain.dart.*andassets/**files in your CDN or storage bucket alongside releaseN's hashed files (or prune old hashes after a 24–72 hour grace window). - Update
index.htmlandflutter_bootstrap.jsatomically last. -
Exclude
/assets/**,/canvaskit/**,/icons/**, and/main.dart.*from single-page application (SPA)/index.htmlcatch-all rewrites so missing assets return a genuine404instead of200 text/html.
Custom service workers (precache_manifest.json)
#
When --web-content-hash is enabled, flutter build web writes a bill of
materials to build/web/precache_manifest.json:
{
"version": 1,
"entries": [
{
"url": "flutter_bootstrap.js",
"hash": "a1b2c3d4",
"size": 10432,
"urlHashed": false
},
{
"url": "main.dart.f215ce97.wasm",
"hash": "f215ce97",
"size": 1842104,
"urlHashed": true
}
]
}
Custom PWA service workers or Workbox pipelines can consume
precache_manifest.json directly (passing revision: entry.urlHashed ? null : entry.hash
so URL-hashed files aren't double-cached with query parameters).
Deploy WebAssembly in production
#
Running flutter build web --wasm produces a dual-target release bundle
in build/web:
-
WebAssembly target (
dart2wasm): Emitsmain.dart.wasmand its JS runtime modulemain.dart.mjs, paired with Flutter's WebAssembly rendering engine. -
JavaScript fallback (
dart2js): Emitsmain.dart.jsfor browsers without the required Wasm features.
At startup, flutter_bootstrap.js probes the browser's WebAssembly capabilities
and automatically falls back to main.dart.js when a browser doesn't support
the required Wasm features or isn't enabled in wasmAllowList.
For browser compatibility details,
refer to Support for WebAssembly (Wasm).
For dart2wasm compiler flags and generic Wasm server configuration,
refer to Dart WebAssembly compilation and
Serve WebAssembly in production.
Configure server headers and compression
#Configure the production web server or CDN with the following headers and compression settings:
-
Cross-origin isolation for multi-threaded rendering:
Return
Cross-Origin-Opener-Policy: same-originandCross-Origin-Embedder-Policy: credentialless(orrequire-corp) on the document response to enableSharedArrayBufferand multi-threaded WebAssembly rendering (refer to the COOP and COEP guide on web.dev). -
Wasm MIME type and static pre-compression:
Serve all
.wasmfiles (bothmain.dart.wasmand self-hosted engine binaries) withContent-Type: application/wasm, and pre-compress.wasm,.mjs,.js, fonts, andassets/**manifests with Brotli (br) and Gzip (gzip). For streaming compilation and compression details, refer to Serve WebAssembly in production.
Reduce cold-start latency
#To shorten time-to-first-frame on cold cache loads:
-
Inline the bootstrap script:
Replace the external
<script src="flutter_bootstrap.js" async></script>tag inweb/index.htmlwith an inline<script>containing{{flutter_bootstrap_js}}. Inlining eliminates an extra network round-trip before the browser begins downloadingmain.dart.wasmand the rendering engine. For template syntax details, refer to Bootstrap the app. -
Trim initial bundle weight:
Release builds enable font icon tree shaking automatically
(pass
--no-tree-shake-iconsonly when debugging dynamic icon code points). To split large features into on-demand.wasmchunks, build with the--enable-wasm-deferred-loadingflag. Keep in mind that--enable-wasm-deferred-loadingis incompatible with--web-content-hash. -
Render an HTML loading shell:
Include lightweight HTML and CSS inside
web/index.html(or update DOM state from theonEntrypointLoadedcallback influtter_bootstrap.js) so your app's users receive visual feedback while the engine initializes.
Serve assets from a CDN or self-hosted origin
#Customize where engine binaries, compiled modules, and assets load from to match the hosting architecture and privacy requirements:
-
Self-host engine binaries and fallback fonts:
Pass
--no-web-resources-cdnto bundle Flutter's rendering engine files directly intobuild/web/canvaskit/instead of fetching them fromgstatic.comat runtime. When you build with the--web-content-hashflag, bundled files undercanvaskit/**remain unhashed and require revalidating cache headers. -
Serve static assets from a dedicated CDN:
Configure the
assetBaseandentrypointBaseUrloptions in_flutter.loader.load({ config: ... })(andcanvasKitBaseUrlwhen self-hosting engine binaries on a CDN). To inject a CDN prefix intoweb/index.htmlat build time, reference the$FLUTTER_STATIC_ASSETS_URLplaceholder inweb/index.htmland pass--static-assets-url=https://cdn.example.com/(including the trailing/) toflutter build web. When you build with the--enable-wasm-deferred-loadingflag and custom asset routing, provide awasmDeferredModulesLoadercallback to fetch deferred.wasmmodules from the CDN. For configuration details, refer to Customize the Flutter loader.
Deploy to the web
#When you are ready to deploy your app, upload the release bundle to Firebase, the cloud, or a similar service. Here are a few possibilities, but there are many others:
Deploy to Firebase Hosting
#You can use the Firebase CLI to build and release your Flutter app with Firebase Hosting.
Before you begin
#To get started, install or update the Firebase CLI:
npm install -g firebase-tools
Initialize Firebase
#-
Enable the web frameworks preview to the Firebase framework-aware CLI:
firebase experiments:enable webframeworks -
In an empty directory or an existing Flutter project, run the initialization command:
firebase init hosting Answer
yeswhen asked if you want to use a web framework.-
If you're in an empty directory, you'll be asked to choose your web framework. Choose
Flutter Web. Choose your hosting source directory; this could be an existing flutter app.
Select a region to host your files.
Choose whether to set up automatic builds and deploys with GitHub.
-
Deploy the app to Firebase Hosting:
firebase deployRunning this command automatically runs
flutter build web --release, so you don't have to build your app in a separate step.
To learn more, visit the official Firebase Hosting documentation for Flutter on the web.
Handle images on the web
#
The web supports the standard Image widget to display images.
By design, web browsers run untrusted code without harming the host computer.
This limits what you can do with images compared to mobile and desktop platforms.
To learn more, refer to Displaying images on the web.
Minification
#
To improve app start-up the compiler reduces the size of the compiled code by
removing unused code (known as tree shaking), and by renaming code symbols to
shorter strings (e.g. by renaming AlignmentGeometryTween to something like
ab). Which of these two optimizations are applied depends on the build mode:
| Type of web app build | Code minified? | Tree shaking performed? |
|---|---|---|
| debug | No | No |
| profile | No | Yes |
| release | Yes | Yes |
Mobile browser viewport and SafeArea
#
On mobile browsers (such as iOS Safari and Android Chrome) with display cutouts,
notches, or home indicators, Flutter web defaults to rendering within the
browser's safe area (viewport-fit=contain).
To render edge-to-edge across the entire screen and populate Flutter's
SafeArea
and MediaQuery.paddingOf
with the browser's CSS
env(safe-area-inset-*) insets, add a <meta name="viewport">
tag with
viewport-fit=cover inside the <head> of your web/index.html
file (included
as a commented-out option in newly created web/index.html templates):
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
URL percent-encoding in browser history
#
In Flutter 3.50 and later, route strings passed to
SystemNavigator.routeInformationUpdated preserve percent-encoded sequences
(such as %2F, %26, %23, and %20) when written to the browser's URL bar
and history, rather than decoding them using Uri.decodeComponent.
Embed a Flutter app into an HTML page
#To embed Flutter into an existing web page, refer to Add Flutter to any web application.
Unless stated otherwise, the documentation on this site reflects Flutter 3.50. Page last updated on 2026-11-11. View source or report an issue.