Have you ever deployed a new version of your Flutter Web app, only to find users still seeing the old one?
Browser caching can make it seem like your app isn’t updating, even though you’ve built and redeployed it correctly. But the fix is actually much simpler than we thought.
We're not the first people to talk about how we handle this problem – but hopefully this explanation with your brain!
What actually happens when you run flutter build web?
When you're deploying your Flutter Web app, you typically run the command flutter build web per the documentation. It's important to understand what this is actually doing though.
When you generate a Flutter Web project, it creates a folder called web/ in the top level directory. Effectively, this folder is the "template" that the Flutter compiler uses to create the web bundle.

The /web folder for one of our apps
In auto-generated web/ folders, there's no flutter_bootstrap.js file, or main.dart.js file. Unless you have custom functionality, its usually just manifest.json, icons/ and index.html.
The
flutter_bootstrap.jsabove (our own version),auth_google.html, andauth_microsoft.htmlare topics for another day!
What is flutter_bootstrap.js?
In a Flutter Web app, flutter_bootstrap.js is the small JavaScript entrypoint that initializes and loads your Flutter app in the browser.
In most cases, it's generated automatically by the Flutter build system (flutter build web).
It’s a lightweight loader script that:
Loads Flutter’s Web runtime (
flutter.jsandmain.dart.jsormain.dart.wasm).Configures how the app starts (assets, service workers, entrypoint, etc.).
Handles bootstrap logic — meaning, it decides how and when the compiled Dart app actually runs.
After running flutter build web in your console, take a look at your main directory structure in VSCode. There's likely a greyed out directory called build/ there and inside there's a folder called web/. One of the files you'll see in there, that you didn't see in your top level web/ directory is flutter_bootstrap.js, along with main.dart.js and flutter.js.
The trick to ensuring that your Flutter Web app always has the last build, is in these files.
How to Deploy Better with Flutter Web
In order to make sure that your Flutter Web app always has the latest deployed build, is to build a custom deploy script with a little inline filename manipulation.
Whenever someone requests your app, they receive the index.html that has <script src="flutter_bootstrap.js" async></script> at the end. This is literally just loading the entire Flutter runtime.
If your cache isn't invalidating, it's because your browser doesn't know that this has changed! It sees nothing different between the previous version, and this one.
In order to make the browser see something different here, you need to append a URL query parameter to the end of this value, such that the browser thinks that it's a different value altogether. What do I mean by that exactly?
Instead of flutter_bootstrap.js, we need to tell the browser to retrieve flutter_bootstrap.js?version=XXX (where XXX can be any random value). To a person, this might seem like the same file. To your browser, it sees these as separate requests.
The magic of bash scripts and sed
To make this easy for ourselves requires a little bit of command line work. To keep it simple, I'll outline the script below, then explain what it does after.
For those not familiar, sed is a bash command that stands for stream editor.
Per Wikipedia:
sed("stream editor") is a Unix utility that parses and transforms text, using a simple, compact programming language.
tl;dr – it's an inline way to edit text in files in bash.
The script:
BUILD_PATH="build/web/"
> Establish the path from our top-level directory, to anything compiled in the next command
flutter build web --release --wasm --pwa-strategy=none> Build your Flutter application
BUILD_AT=`date +%s`
> We use date timestamp as our version so every deploy will invalidate cache
sed -i "s/\"manifest.json\"/\"manifest.json?v=$BUILD_AT\"/g" "$BUILD_PATH/index.html"
> Look for any occurrence of the string "manifest.json" inside build/web/index.html
> If found, replace with "manifest.json?v=XXX"
sed -i "s/\"flutter_bootstrap.js\"/\"flutter_bootstrap.js?v=$BUILD_AT\"/g" "$BUILD_PATH/index.html"
> Look for any occurrence of the string "flutter_bootstrap.js" inside build/web/index.html
> If found, replace with "flutter_bootstrap.js?v=XXX"
sed -i "s/\"main.dart.js\"/\"main.dart.js?v=$BUILD_AT\"/g" "$BUILD_PATH/flutter_bootstrap.js"
> Look for any occurrence of the string "main.dart.js" inside build/web/flutter_bootstrap.js
> If found, replace with "main.dart.js?v=XXX"
sed -i "s/\"main.dart.mjs\"/\"main.dart.mjs?v=$BUILD_AT\"/g" "$BUILD_PATH/flutter_bootstrap.js"
> Look for any occurrence of the string "main.dart.mjs" inside build/web/flutter_bootstrap.js
> If found, replace with "main.dart.mjs?v=XXX"
sed -i "s/\"main.dart.wasm\"/\"main.dart.wasm?v=$BUILD_AT\"/g" "$BUILD_PATH/flutter_bootstrap.js"
> Look for any occurrence of the string "main.dart.wasm" inside build/web/flutter_bootstrap.js
> If found, replace with "main.dart.wasm?v=XXX"
And that’s it! Whenever someone visits your app, their browser will load the updated index.html file — which now includes version tags on your core Flutter files, ensuring it always fetches the latest build. It's as simple as that.
For people using Firebase Hosting, Flutter has official recommendations here on how to handle caching.
If you have any topics you want to hear for our next How To: Flutter Web guide, shoot an email over to jalen@videotape.ai.



