3.9 KiB
Web Navigation
Use this reference when fixing Flutter web URLs, browser history, refresh/direct load behavior, server rewrites, or non-root hosting.
URL Strategies
Flutter web supports two URL strategies:
Hash Strategy
https://example.com/#/path/to/screen
Use it when you cannot configure the web server. Hash URLs avoid server rewrites
because the route after # is not sent to the server.
Path Strategy
https://example.com/path/to/screen
Use it when you control server rewrites and want clean, shareable paths.
import 'package:flutter_web_plugins/url_strategy.dart';
void main() {
usePathUrlStrategy();
runApp(const App());
}
flutter_web_plugins is an SDK dependency:
dependencies:
flutter:
sdk: flutter
flutter_web_plugins:
sdk: flutter
Call usePathUrlStrategy() before runApp().
Server Rewrites
Path strategy uses the browser History API. Configure the server to serve
index.html for app routes that are not real files.
Nginx
location / {
try_files $uri $uri/ /index.html;
}
Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
Firebase Hosting
{
"hosting": {
"public": "build/web",
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
}
}
Vercel
{
"rewrites": [
{ "source": "/(.*)", "destination": "/index.html" }
]
}
Netlify
/* /index.html 200
Browser History
Router-based navigation, including go_router, integrates with the browser URL
and History API for direct loads, refresh, back, and forward behavior.
Prefer canonical route state with context.go(location) for browser-visible
navigation. context.push(location) is useful for page-stack style flows, but
imperative navigation can be harder to reason about in browser history.
Build locations with query parameters using Uri:
context.go(
Uri(
path: '/search',
queryParameters: {'q': query},
).toString(),
);
Hosting At A Non-Root Path
If the app is hosted at https://example.com/myapp/, update the base href in
web/index.html:
<base href="/myapp/">
Keep app route paths app-relative unless the target project already includes the deployment prefix in routes:
GoRouter(
routes: [
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute(path: '/details/:id', builder: (context, state) => const DetailsScreen()),
],
);
Configure the hosting server so /myapp/details/42 rewrites to the built
index.html for that deployed app.
Not Found And Error Routes
For public web URLs, define a deliberate not-found or error surface:
GoRouter(
errorBuilder: (context, state) => NotFoundScreen(error: state.error),
routes: [
// ...
],
);
Validate bad routes and malformed parameters. Do not let a bad URL crash during
int.parse, forced casts, or missing path parameter access.
Web Validation
When feasible, run the app in Chrome and test:
- direct load of
/; - direct load of every changed route;
- refresh on every changed route;
- browser back and forward;
- query parameter preservation;
- unknown URL and malformed parameter behavior;
- deployed non-root path if applicable.
For production hosts, test after deployment as well as on the Flutter dev server. The dev server handles fallback routing automatically, while production hosting only works if rewrites are configured.
Accessibility And Titles
Navigation changes can affect focus, page announcements, and keyboard flow. After route changes, check that keyboard users can reach primary actions and that screen names, app bars, or semantic labels make the destination clear.