skip to content

Why do Flutter web URLs contain #/ by default, and what does switching to usePathUrlStrategy require in the app and on the server?

level: middleimportance: should knowfreq 42%

answer

  1. hash fragment never reaches the server
  2. flutter_web_plugins as an SDK dependency
  3. call it before runApp
  4. rewrite unknown paths to index.html
  5. base href for a sub-path deploy

basics

~20 s

Flutter web defaults to the hash URL strategy, keeping routes after # so any static server works. usePathUrlStrategy from flutter_web_plugins, called before runApp, gives clean paths, but the server must rewrite unknown paths to index.html.

solid answer

~40 s

Flutter web's default **hash** strategy stores the route in the fragment, as in `example.dev/#/orders/42`. The browser never sends the fragment to the server, so any static host serves `index.html` and the app reads the route. For clean URLs, add `flutter_web_plugins` as an SDK dependency (`sdk: flutter`; it cannot be added with `pub add`) and call `usePathUrlStrategy()` from `package:flutter_web_plugins/url_strategy.dart` before `runApp`. Now the route is a real path, so a refresh or shared link requests `/orders/42` from the server, which must rewrite any unknown path to `index.html`, the usual single-page-app setup. Keep real files such as `assets/`, `canvaskit/` and `main.dart.*` excluded from that rewrite. If the app lives under a sub-path, set `<base href>` in `web/index.html`, or pass `--base-href`, which must start and end with `/`.

code

yaml · 5 lines
yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_web_plugins:
    sdk: flutter

go deeper

for a junior

Recall that hash URLs are the default and usePathUrlStrategy, from flutter_web_plugins, switches to clean paths.

for a middle

Explain why the hash never reaches the server, why path URLs need an index.html rewrite, and how base href handles sub-paths.

for a senior

Show you configure hosting correctly: rewrite exclusions for real files, cache headers on index.html and sub-path deployments.

for a principal

Weigh clean URLs against hosting constraints and decide URL conventions that stay stable as routes and deployments change.

## Two URL strategies A Flutter web app decides how its route appears in the address bar through a **URL strategy**: | Strategy | Example URL | Server requirement | |---|---|---| | **Hash** (default) | `example.dev/#/orders/42` | none: every request is for `/` | | **Path** | `example.dev/orders/42` | rewrite unknown paths to `index.html` | With the **hash** strategy the route lives in the URL fragment. Browsers never send the fragment to the server, so the server always sees a request for the root, returns `index.html`, and the app reads the fragment to build the right screen. That is why it is the default: it works on any static host with zero configuration. The **path** strategy uses the browser's History API to write real paths. The URLs look like any website's, which matters for links people share and for analytics, but now the server is involved. ## Switching in the app 1. Add the SDK library to `pubspec.yaml`. `flutter_web_plugins` ships with the Flutter SDK, so it is declared with `sdk: flutter`; `pub add` cannot add it. 2. Import `package:flutter_web_plugins/url_strategy.dart`. 3. Call `usePathUrlStrategy()` **before** `runApp`, so the first route is read with the right strategy. The same library exposes `setUrlStrategy` for a custom `UrlStrategy`, and it has a non-web implementation that does nothing, so the call is safe in code that also builds for mobile. ## What the server must do Once routes are paths, a refresh or a pasted link on `/orders/42` sends that path to the server. A plain static host has no such file and returns 404. The server must **rewrite** requests for unknown paths to `index.html`, the standard single-page-app configuration, while still serving real files: - exclude `assets/`, `canvaskit/`, `icons/` and the compiled `main.dart.*` files from the rewrite, so a missing asset returns a real 404 instead of HTML with status 200; - keep caching short on `index.html` and `flutter_bootstrap.js`, so a deploy is picked up promptly. `flutter run -d chrome` already falls back to `index.html` for any path, which is why the problem often appears first in staging. ## Hosting under a sub-path If the app is served from `example.dev/admin/` rather than the root, set the `<base href="/admin/">` tag in `web/index.html`, or pass `--base-href /admin/` to `flutter build web`; the tool rejects a value that does not start and end with `/`. Relative base hrefs are possible in release builds but must match the depth of each served URL, which makes them fragile. ## Choosing between the strategies - Choose **hash** when you cannot configure the host, such as some static file buckets, or when the app is an internal tool where URL looks do not matter. - Choose **path** when links are shared with people, when analytics group pages by path, or when the web app's URLs should match those a marketing site already uses. - Switching later is possible, but old hash links then need handling, so decide before URLs are published. ## How this relates to routing packages The URL strategy is independent of the router. go_router or the Router API decide which screen a location maps to; the URL strategy decides whether that location is written after `#` or as a path. Deep-link wiring for mobile App Links and Universal Links is a separate mechanism. ## Common failure pattern A delivery service's Flutter admin dashboard switches to path URLs, works in `flutter run`, then returns 404 on every refresh in staging. The code is fine; the host has no rewrite rule. Adding the rewrite, and the exclusions for real files, fixes it without touching Dart.

  • After switching a Flutter web app to path URLs, refreshing /orders/42 returns 404 in staging but works with flutter run. Why?
    The dev server behind `flutter run -d chrome` falls back to `index.html` for any path; the staging host does not. With the path strategy the browser requests `/orders/42` itself, so the host needs a rewrite of unknown paths to `index.html`, excluding real files such as `assets/` and `main.dart.*`.
  • The same Flutter web build must be served from /admin/. What changes?
    Set `<base href="/admin/">` in `web/index.html`, or build with `--base-href /admin/`, which must start and end with `/`. Assets and the engine files then resolve under `/admin/`, and the server's rewrite rule must target that sub-path's `index.html`.

saying these in an interview costs you the question

  • Path URLs work on any static host with no configuration.
  • The hash fragment is sent to the server with every request.
  • flutter_web_plugins is added with flutter pub add like any package.
  • usePathUrlStrategy can be called anywhere, even after runApp.
  • go_router alone decides whether the URL contains #.