DEV Community

kanta13jp1
kanta13jp1

Posted on

Flutter Web Performance: Real Measurements, Real Fixes

Flutter Web Performance: Real Measurements, Real Fixes

Flutter Web's initial load is slow by default. Without the right optimizations, you'll have high bounce rates. Here's what I measured and fixed in a production app.

Start with Measurement

# Chrome DevTools Lighthouse on production URL
# (local measurements are inaccurate)
LCP: 4.2s → before
FID: 180ms
CLS: 0.12
Enter fullscreen mode Exit fullscreen mode

Measure before you fix. Otherwise you won't know what actually worked.

1. CanvasKit → HTML Renderer Switch

Flutter Web has two renderers:

--web-renderer canvaskit  # high fidelity, large initial DL (~2.5MB)
--web-renderer html       # lightweight, small initial DL (~300KB)
--web-renderer auto       # mobile=html / desktop=canvaskit
Enter fullscreen mode Exit fullscreen mode

Result: switching to --web-renderer html cut initial load by 1.5s.

flutter build web --web-renderer html --release
Enter fullscreen mode Exit fullscreen mode

Caveat: html renderer restricts some CanvasKit-dependent APIs (BlendMode, etc.). Check your widgets first.

2. Lazy Loading — Deferred Routing

Don't import every page at the top level:

// Bad: all pages imported at startup
import 'pages/heavy_page.dart';

// Good: loaded only when the route is navigated to
GoRoute(
  path: '/heavy',
  builder: (context, state) {
    return const HeavyPage();
  },
),
Enter fullscreen mode Exit fullscreen mode

Keep heavy widgets (maps, charts, video) out of routes needed on first load.

3. Offload Heavy Work with compute()

// Bad: blocks the UI thread
List<HorseData> heavyParsing(String jsonString) {
  return (jsonDecode(jsonString) as List)
      .map((e) => HorseData.fromJson(e))
      .toList();
}

// Good: runs in an Isolate
final result = await compute(heavyParsing, jsonString);
Enter fullscreen mode Exit fullscreen mode

If JSON parsing takes more than 50ms, use compute(). The UI stays responsive.

4. Image Optimization

// Bad: raw network image, no caching
Image.network('https://example.com/large.png')

// Good: cached + size constraints
CachedNetworkImage(
  imageUrl: 'https://example.com/large.webp',
  width: 300,
  height: 200,
  memCacheWidth: 300,
)
Enter fullscreen mode Exit fullscreen mode

WebP conversion: -60% vs PNG. CachedNetworkImage eliminates duplicate requests.

5. ListView.builder for Virtual Scrolling

// Bad: builds all items at once
ListView(children: items.map((e) => ItemWidget(e)).toList())

// Good: builds only visible items
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) => ItemWidget(items[index]),
)
Enter fullscreen mode Exit fullscreen mode

For 1000-item lists, ListView.builder is mandatory. Plain ListView freezes.

6. const Constructors Prevent Rebuilds

// Rebuilds every frame
return Column(children: [
  Text('Static Label'),  // rebuilt every time
  DynamicWidget(),
]);

// Static widgets skip rebuild
return Column(children: [
  const Text('Static Label'),  // skipped
  DynamicWidget(),
]);
Enter fullscreen mode Exit fullscreen mode

flutter analyze warns about missing const via prefer_const_constructors. Follow it.

Measured Results

Fix LCP improvement
HTML renderer -1.5s
WebP images -0.8s
Lazy loading -0.4s
ListView.builder FID -80ms
compute() jank eliminated

Before: LCP 4.2s → After: LCP 1.5s

Lighthouse score: 41 → 78

Summary

Flutter Web performance work follows this order: renderer selection → images → lazy loading → computation cost. Measure first, fix second, measure again.

Top comments (0)