Flutter Pro Design

Small details that build taste in Flutter.

curated by Kamran BekirovKamran Bekirov

Show progress while Flutter web loads

Flutter web has to download main.dart.js, the CanvasKit (or skwasm) runtime, fonts, and your assets before it can paint a single pixel. Depending on the size of those, that's easily 3 to 5 seconds of staring at a blank white page wondering if the site is broken.

You can fix it without touching any Dart code. Flutter mounts into <body>, so anything you put there shows immediately and disappears on its own once the engine takes over. Drop a logo in there, or better, show a real progress bar that reflects how far along the boot actually is.

Start with the markup in web/index.html:

<body>
  <div class="progress-container">
    <div class="progress-bar"></div>
  </div>
  
  <script src="flutter_bootstrap.js" async></script>
</body>

Style it in web/style.css and link it from the head:

<link rel="stylesheet" href="style.css" />
body {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  margin: 0;
  background-color: #ffffff;
}
  
.progress-container {
  width: 120px;
  height: 8px;
  background-color: #FAFAFA;
  border-radius: 10px;
  overflow: hidden;
}
  
.progress-bar {
  height: 100%;
  width: 0;
  background-color: #B591FF;
  transition: width 0.4s ease;
}

Adjust to taste.

Now make it actually move. Drop a custom web/flutter_bootstrap.js next to index.html. If the file exists, Flutter uses it instead of the auto-generated one. Hook into the loader stages:

{{flutter_js}}
{{flutter_build_config}}
  
const bar = document.querySelector('.progress-bar');
const setProgress = (pct) => { bar.style.width = pct + '%'; };
  
setProgress(20); // bootstrap running
  
_flutter.loader.load({
  onEntrypointLoaded: async function (engineInitializer) {
    setProgress(50); // main.dart.js downloaded
    const appRunner = await engineInitializer.initializeEngine();
    setProgress(80); // engine ready
    await appRunner.runApp();
    // Flutter has taken over the page, nothing else to do
  },
});

These aren't fake percentages. Each one fires after a real milestone: the bootstrap script running, onEntrypointLoaded (main.dart.js parsed), initializeEngine resolving, and runApp mounting the app. A handful of discrete stages, not continuous progress, but enough to feel honest.

For a real example, visit app.userorient.com.

One thing to watch: everything here ships as plain HTML/CSS with the initial page request. Don't load a 2MB animated background, or you'll be solving one loading problem by creating another.