Skip to content
Blog

Flutter Web URL Strategies: Hash vs. Path Routing Done Right

Hash and path URL strategies in Flutter web, how to enable path URLs with flutter_web_plugins, the server rewrite you must not forget, and go_router interaction.

Published on • October 9, 2026

AI Assistant

Ship a Flutter app to the web and the address bar betrays your routing choice: example.com/#/products/42 or example.com/products/42. That single character decides your server configuration, shareability, and how deep links behave on refresh.

Flutter supports two URL strategies, and hash is still the default as of Flutter 3.47.

Hash vs. path

Hash (default)Path
URL shapesite.com/#/products/42site.com/products/42
Server configNone requiredSPA rewrite to index.html required
Who sees the routeOnly the client — the fragment never hits the serverThe server sees every path
ShareabilityUgly, fragments often stripped by chat appsClean, canonical
Refresh / deep linkAlways worksBreaks without the rewrite

Hash URLs are robust precisely because the server never participates: anything can host them — S3 buckets, shared hosting, even file://. Path URLs look like a conventional web app but shift responsibility onto your hosting configuration.

Enabling path strategy

Call usePathUrlStrategy() before runApp():

import 'package:flutter_web_plugins/url_strategy.dart';

void main() {
  usePathUrlStrategy();
  runApp(const ExampleApp());
}

flutter_web_plugins cannot be pub add’d — declare it as an SDK dependency:

dependencies:
  flutter:
    sdk: flutter
  flutter_web_plugins:
    sdk: flutter

Under the hood this selects PathUrlStrategy, which reads platformLocation.pathname and respects your <base href>. The lower-level API is setUrlStrategy(PathUrlStrategy()) or setUrlStrategy(const HashUrlStrategy()) — hash is the engine default, so doing nothing gives you #/.

The server rewrite you must not forget

PathUrlStrategy uses the browser History API. Every unmatched request must fall back to index.html, or refreshing a deep link returns a 404 from your server even though the app routes fine in-session.

nginx:

location / {
    try_files $uri $uri/ /index.html;
}

Firebase Hosting — choose “Configure as a single-page app” when initializing, which writes:

{ "rewrites": [{ "source": "**", "destination": "/index.html" }] }

flutter run -d chrome already falls back to index.html gracefully, which is exactly why the bug usually appears for the first time in production.

Sub-path hosting and base href

If the app is not deployed at the domain root, edit <base href> in web/index.html to match — for example /flutter_app/. Relative base href values (".", "..") work for release builds but differ per request path, so pin it explicitly for anything non-root.

Interaction with go_router

go_router reads whatever path Flutter reports — it does not configure the strategy for you. Configure the strategy in main.dart independently of your router. Note there is no official useHashUrlStrategy() in flutter_web_plugins; the old url_strategy package from pub.dev that provided those helpers is now marked discontinued and should not enter new projects.

go_router’s web topic links to the Flutter URL strategy page for exactly this reason: router choice and URL strategy are orthogonal decisions.

When to choose which

Choose hash when:

  • You do not control server config (S3, static buckets, GitHub Pages without redirects)
  • It is an internal tool where URL aesthetics do not matter
  • You want zero-configuration maximum compatibility

Choose path when:

  • It is a public, shareable product — canonical URLs matter
  • You control rewrite configuration and can test refresh + deep links
  • You want links that survive being pasted into chat apps without # mangling

One honest caveat: Flutter’s docs do not make strong SEO claims either way — modern Googlebot renders JavaScript. The real argument for path URLs is shareability, canonical structure, and server-side control (caching rules, redirects, analytics that see full paths).

Gotchas

  • Path strategy without the rewrite = first refresh in production 404s.
  • Strategy must be set before runApp().
  • <base href> must match the deployment subpath.
  • Hash fragments are never sent to servers — you cannot canonicalize or server-render hash routes.
  • Do not add the discontinued url_strategy package; flutter_web_plugins covers it.

Wrapping up

Pick hash for zero-config robustness, path for a production web presence — and if you pick path, write the SPA rewrite in the same pull request that enables usePathUrlStrategy(). Then verify by refreshing a deep link on a staging deploy before you ship.

References