Flutter Web: When It Makes Sense and When It Doesn't
An honest look at Flutter web: where it shines (dashboards, internal tools, app-like PWAs), where it struggles (SEO, content sites), and how to ship it well.

“We already have a Flutter app, so the website is basically free, right?”
Sometimes, yes. Sometimes it’s the most expensive shortcut in the project. Flutter web is a real, stable platform, and I like it. But it’s built for a specific kind of product, and knowing which kind saves a lot of pain.
I also build for the web with Vue.js and Next.js, so I don’t have a stake in Flutter winning every argument. Here’s how I actually think about it.
What Flutter web really is
Flutter on the web doesn’t turn your widgets into HTML elements the way a typical web framework does. It paints your UI itself, onto a canvas, using a rendering engine compiled for the browser. The page your users load is mostly a single surface that Flutter controls.
That one fact explains nearly everything good and bad about it:
- Good: pixel-identical UI to your mobile app, the same widgets, the same state management, the same business logic. One codebase really does run on iOS, Android and the browser.
- Bad: the browser has much less to work with. No natural HTML document for search engines to read, no native text selection unless you opt in, and an engine that has to download before anything appears.
Where it shines
Internal tools and admin dashboards. This is the sweet spot. Your users are logged in, they come back every day, nobody is finding the page through Google, and nobody cares if the first load takes a moment longer. If your mobile app needs a back office for managing users, content or orders, building it in Flutter lets you reuse your models, API clients and validation logic directly.
App-like products that happen to run in a browser. Think booking tools, editors, field-service portals, anything where people sign in and do things rather than read things. Flutter web can be installed as a PWA, and for many B2B products that’s all the “desktop app” a client needs.
Companion web versions of a mobile app. If customers want to use the same product on a laptop, sharing the codebase is a big win, as long as you treat the web as its own platform and adapt the layout. The same thinking I apply in responsive layouts for phone and tablet applies here, with a mouse and keyboard added.
Where it struggles
Marketing sites, blogs and anything that lives on search traffic. Search engines want real HTML with headings, links and text. Flutter web gives them very little. You can generate an accessibility tree for screen readers, but that’s not a substitute for server-rendered content. If organic search matters, I build that part with a regular web framework. This blog is a static site for exactly that reason.
Pages where first impressions are measured in milliseconds. A landing page that people reach from an ad has to appear instantly. Flutter web has to load its engine and your compiled app before painting anything, and that cost is hard to hide on a slow mobile connection.
Text-heavy content. Selection, find-in-page, browser translation and reader mode all expect a document. You can wrap content in SelectionArea to make text selectable, but you’re always working against the grain.
My usual setup for clients who need both: a fast, SEO-friendly marketing site built with a web framework, and a Flutter web app behind the “Log in” button.
Renderers: CanvasKit and skwasm
Flutter web has two renderers today:
- CanvasKit is the default for a standard
flutter build web. It runs Skia compiled to WebAssembly, with your Dart code compiled to JavaScript. It works across all modern browsers. - skwasm is used when you build with
flutter build web --wasm. Your Dart code is compiled to WebAssembly too, and the renderer is smaller and can use multiple threads. It needs a browser with WebAssembly garbage collection support; when that’s missing, the build falls back to CanvasKit and JavaScript automatically.
The old HTML renderer has been removed, so if you find a tutorial telling you to pass --web-renderer html, it’s outdated.
I try the Wasm build on every new Flutter web project. Startup and frame performance are usually better. The catch is that your dependencies must be Wasm-compatible, which mostly means packages need to use package:web and dart:js_interop instead of the older dart:html. The build tells you which ones don’t.
Initial load size
This is the cost you can’t fully avoid, but you can manage it:
- Measure first. Open the network tab and see what’s actually being downloaded. Fonts and images are often bigger than people expect.
- Show something immediately. Put a lightweight loading indicator in
web/index.htmlso users see your brand instead of a blank page while Flutter starts. - Split big features. Dart’s deferred imports let you load rarely used parts of the app later:
import 'reports/reports_screen.dart' deferred as reports;
Future<void> openReports(BuildContext context) async {
await reports.loadLibrary();
if (!context.mounted) return;
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => reports.ReportsScreen()),
);
}
- Watch your dependencies. Every package ships in the bundle. The habits from reducing Flutter app size apply to the web even more than to mobile.
- Cache properly. Serve the build with long cache headers for hashed assets, so returning users skip most of the download.
URL strategy
By default, Flutter web URLs use a hash: example.com/#/orders/42. It works anywhere without server configuration, but it looks dated and some tools treat it badly. Switching to clean paths takes one line:
import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
void main() {
usePathUrlStrategy();
runApp(const MyApp());
}
flutter_web_plugins comes with the Flutter SDK. Add it to the dependencies in pubspec.yaml with sdk: flutter, the same way flutter itself is listed.
The part people forget: your server must rewrite every unknown path to index.html. Otherwise, refreshing /orders/42 returns a 404 from the host before Flutter ever loads. Every static host has a setting for this; set it up on day one.
Pair this with a router that understands URLs. With go_router, the same route table drives deep links on mobile and the browser address bar on the web, which I cover in deep linking with go_router.
Plugins and platform code
Not every package supports the web. Before adding a dependency, check its platform badges on pub.dev. Common surprises:
dart:iodoesn’t exist in the browser. Code that touchesFile,Platformor sockets directly will fail to compile for web. Use conditional imports or checkkIsWebbefore calling platform-only code.- Some features simply don’t exist on the web, like background location, many Bluetooth APIs and certain biometric flows. Hide those features on web instead of half-supporting them.
- Browser rules apply: CORS on your API, permission prompts that need a user gesture, and storage that the user can clear at any time.
I keep platform checks out of widgets where I can, behind small service interfaces with a web implementation and a mobile implementation. That keeps the UI code clean.
Making it feel like a web app
A Flutter web app that behaves like a phone app stretched to 1920 pixels is a bad web app. A few things go a long way:
- Real hover states and mouse cursors on clickable elements.
- Keyboard navigation and visible focus. Tab order matters on desktop.
- Wider layouts with sidebars and multi-column views instead of a centered phone column.
- Right-click and middle-click expectations where it makes sense, like opening a link in a new tab.
- Text selection via
SelectionAreawhere users will want to copy things.
Takeaways
- Flutter web is great for logged-in, app-like products: dashboards, internal tools, companion apps and PWAs.
- It’s a poor fit for SEO-driven or content-heavy sites. Use a web framework for those and link into the Flutter app.
- Try
flutter build web --wasmfirst. It falls back to CanvasKit where needed. - Manage load size with a loading screen, deferred imports, lean dependencies and good caching.
- Use
usePathUrlStrategy()and configure your host to rewrite paths toindex.html. - Check web support for every plugin, and design for mouse and keyboard, not just touch.
If you’re deciding whether your product’s web side should be Flutter or something else, get in touch and I’ll give you an honest answer for your case.
Comments
Questions, corrections or your own experience — leave a comment below (GitHub sign-in).