Skip to main content

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:

Building 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

#

You might need to deploy a profile or debug build for testing. To do this, pass the --profile or --debug flag to the flutter build web command. Profile builds are specialized for performance profiling using Chrome DevTools, and debug builds can be used to configure dart2js to respect assertions and change the optimization level (using the -O flag.)

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):

  • Hashed immutable files (Cache-Control: public, max-age=31536000, immutable):
    • Compiled entrypoints: main.dart.<hash>.js, main.dart.<hash>.wasm, and main.dart.<hash>.mjs (plus main.dart.<hash>.js.map and main.dart.<hash>.mjs.map if --source-maps is enabled).
    • All files under assets/**: including AssetManifest.<hash>.bin, AssetManifest.bin.<hash>.json, FontManifest.<hash>.json, NOTICES.<hash>, fonts, images, and shaders/**.
  • Unhashed bootstrap and root files (Cache-Control: max-age=0, must-revalidate or no-cache):
    • index.html, flutter_bootstrap.js, flutter.js, manifest.json, version.json, 404.html, and icons/**.
    • Bundled canvaskit/** files when building with --no-web-resources-cdn.
    • main.dart.wasm.map when building with --source-maps (the .wasm binary's sourceMappingURL custom section references the unhashed .wasm.map filename).

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) hashed main.dart.* and assets/** files in your CDN or storage bucket alongside release N's hashed files (or prune old hashes after a 24–72 hour grace window).
  • Update index.html and flutter_bootstrap.js atomically last.
  • Exclude /assets/**, /canvaskit/**, /icons/**, and /main.dart.* from single-page application (SPA) /index.html catch-all rewrites so missing assets return a genuine 404 instead of 200 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:

build/web/precache_manifest.json
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).

Opting-in to WebAssembly

#

To compile and optimize your app for WebAssembly, use the --wasm flag when running or building.

For more information, see Support for WebAssembly (Wasm).

Deploying 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:

Deploying 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

#
  1. Enable the web frameworks preview to the Firebase framework-aware CLI:

    firebase experiments:enable webframeworks
    
  2. In an empty directory or an existing Flutter project, run the initialization command:

    firebase init hosting
    
  3. Answer yes when asked if you want to use a web framework.

  4. If you're in an empty directory, you'll be asked to choose your web framework. Choose Flutter Web.

  5. Choose your hosting source directory; this could be an existing flutter app.

  6. Select a region to host your files.

  7. Choose whether to set up automatic builds and deploys with GitHub.

  8. Deploy the app to Firebase Hosting:

    firebase deploy
    

    Running 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.

Handling 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.

For more information, see 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 buildCode minified?Tree shaking performed?
debugNoNo
profileNoYes
releaseYesYes

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):

web/index.html
html
<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.

Embedding a Flutter app into an HTML page

#

See Embedding Flutter web.