Repository navigation
Fix hash route URL strategy returning 200 instead of 404 for unknown paths - #6954
Merged
Merged
Conversation
With hash routing the client never asks the server for route paths, but FletStaticFiles still served index.html with 200 OK for any missing path. Skip the SPA fallback in hash mode, so unknown paths get a real 404 (the assets dir's 404.html if there is one).
FletStaticFiles stored route_url_strategy and web_renderer as given, and patch_index_html reads their .value, so a string such as "hash" made every request fail with a 500. flet.fastapi.app() passes the arguments through unchanged, and ft.run(..., export_asgi_app=True) returns before it converts strings. Convert them to the enums in FletStaticFiles, which covers both entry points. The routing guide's link text named the environment variable FLET_ROUTE_URL_STRATEGY; it is FLET_WEB_ROUTE_URL_STRATEGY.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Both reported regressions need automated coverage before approval.
1 open finding
What changed in this PR
Fixes hash-routed web apps returning the SPA with HTTP 200 for unknown paths.
Changes:
- Return real 404 responses for missing hash-mode paths.
- Accept string renderer and routing strategy values.
- Correct routing documentation and changelog entries.
| File | Description |
|---|---|
flet_static_files.py |
Adds enum conversion and hash-mode 404 handling. |
navigation-and-routing.md |
Corrects the environment variable name. |
CHANGELOG.md |
Documents both bug fixes. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Deploying flet-website-v2 with
|
| Latest commit: |
00926c1
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://d2d2fea1.flet-website-v2.pages.dev |
| Branch Preview URL: | https://fix-hash-routing-404-2796.flet-website-v2.pages.dev |
page.login(redirect_to_page=True) built the return URL as a path URL from the WebSocket path: `/store` even with the hash route URL strategy, where the route belongs in the fragment and an unknown path is a 404. The WebSocket path also misses a prefix that a proxy strips (proxy_path) and picks up the folder of a nested WebSocket endpoint. FletApp now takes the route URL strategy that FletStaticFiles writes into index.html, environment override included, and the page's base path, computed like the served <base href> from proxy_path and the ASGI root path. login() returns to `<base path>#<route>` with the hash strategy and to `<base path><route>` otherwise.
Page.login() only said "Starts OAuth flow." It now describes the authorization code flow and the saved-token restore, every argument and the return value, with short comments on the steps in its body. Connection's page_base_path, route_url_strategy and local_data_transport get attribute docstrings instead of comments, and the route URL strategy docs on Connection and FletApp no longer single out OAuth.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fixes #2796
The bug
A Flet web app is a single-page app, so the static file server serves
index.htmlfor any unknown route-like path (/products/1) and lets the client route it. In #2796, @FeodorFitsner pointed out that a real 404 is only possible with thehashroute URL strategy, where routes live after the#(/#/products/1) and never reach the server.That never worked. With
route_url_strategy="hash",FletStaticFiles.lookup_path()still fell back toindex.htmlfor every missing path, because the fallback never checked the strategy./invalidurl,/products/1,/page.htmland/sub-app/nopeall returned200 OKwith the app, exactly as in path mode.The fix
FletStaticFilesskips theindex.htmlfallback when the strategy ishash, whether it's set by argument or byFLET_WEB_ROUTE_URL_STRATEGY. A path that isn't a real file now gets a real404, with the assets directory's404.htmlas the body if there is one. The mount root (/,/sub-app/) still serves the app.index.htmlas the body, so deep links like/users/john.doestill load the app.route_url_strategyandweb_renderergiven as strings, e.g.route_url_strategy="hash"as in the routing guide, made every request toflet.fastapi.app()andft.run(..., export_asgi_app=True)fail with a500('str' object has no attribute 'value').FletStaticFilesnow converts them to the enums, which covers both entry points.page.login(redirect_to_page=True)sent the browser back to a path URL (/store), because the Python side didn't know the strategy. That lost the route, and with the 404s above it would land on a 404.flet.fastapi.app()now gives each session the strategy it serves, environment variable included, and login returns to/#/store, or/sub-app/#/storefor a sub-app.<base href>, fromproxy_pathplus the ASGI root path, instead of from the WebSocket URL. In both modes it now keeps a prefix that a proxy strips (proxy_path), and it ignores the folder of a nestedFLET_WEBSOCKET_HANDLER_ENDPOINTsuch assocket/ws.FLET_ROUTE_URL_STRATEGY; it isFLET_WEB_ROUTE_URL_STRATEGY.page.login()docstring. It only said "Starts OAuth flow." It now explains both ways the method works: the authorization code flow (it returns before sign-in finishes and needs Flet's web server) and restoring a saved token. It also documents every argument and the return value, and adds short comments on the steps in the method.Connectionattribute docs.page_base_path,route_url_strategyandlocal_data_transportnow have attribute docstrings instead of comments.Behaviour changes
All of these apply only to apps using the
hashroute URL strategy and served by Flet's Python web server:ft.run(..., view=ft.AppView.WEB_BROWSER),flet run --web,ft.run(..., export_asgi_app=True)orflet.fastapi.app(). Path mode, desktop and mobile apps, andflet build web/flet publishsites on a static host are not affected.https://myapp.dev/storeused to load the app at route/, silently dropping/store. It now returns404. Proper hash URLs (https://myapp.dev/#/store) and in-app navigation (page.navigate(),push_route(), back/forward) never ask the server for the route and work as before.app.mount("/sub-app", ...)andapp.mount("/", ...), Starlette'sMount("/sub-app")doesn't match the bare/sub-app, so the root app handles it. Before, that served the root app with200. Now it's a404./sub-app/works as before, and without a root mount Starlette still redirects/sub-appto/sub-app/. A redirect route before the mounts avoids it:An earlier version of this list had a third item:
page.login(redirect_to_page=True)landing on a 404 in hash mode. That is now fixed in this PR (see "Login return URL" above).Testing
flet.fastapiapp with root and sub-app mounts, both strategies, with and without a404.htmlin the assets dir.flet run --webservers checked with curl and headless Chrome. In hash mode,/#/storerenders route/storeand/invalidurlreturns 404. Onflet-1.1.0,/invalidurlloads the app.404.html, the env var and the string forms. 9 fail onflet-1.1.0, and all pass with this change.TestClient: register a client,page.login(),/oauth_callbackanswering307with theflet_oauth_statecookie, reconnect, and the token request with the code. They cover root, sub-app and nested mounts,root_path,proxy_path, a nested WebSocket endpoint, a route with a query, and the environment variable overriding the argument both ways. All pass. On the previous head, every hash-mode case fails, as do path mode withproxy_pathand with a nested WebSocket endpoint./sub-appmounts and behind a proxy that strips/prefix(withproxy_path="/prefix"). Both strategies ran with an instant provider redirect, and hash mode at the root and/sub-appalso ran with a provider page in between. Every run returns to/#/store,/sub-app/#/storeor/prefix/#/store(path mode:/store,/sub-app/store,/prefix/store), resumes the same session, and fireson_loginon route/store. On the previous head, hash mode returns to/store, which is a 404, and behind the proxy both modes return to/store, outside the prefix.Summary by Sourcery
Fix hash-mode web routing and OAuth redirects so unknown server paths return 404 and login returns users to the correct application route.
Bug Fixes:
Enhancements:
Documentation: