-
Notifications
You must be signed in to change notification settings - Fork 87
feat(serve): Add --spa-fallback flag for SPA routing #2656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
When developing Single Page Applications (SPAs) that use client-side routing with the HTML5 History API (e.g., Angular's PathLocationStrategy), refreshing the page on a deep link results in a 404 error because the server can't find a corresponding file for the route. This change introduces a --spa-fallback flag to the webdev serve command. When enabled, the development server will serve the root index.html file for any GET request that would otherwise result in a 404, as long as the path does not appear to be a direct file asset (i.e., does not contain a file extension). This allows the client-side router to take over and handle the request, enabling a seamless development workflow for modern web applications.
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
… hit * Strip 'If-None-Match' / 'If-Modified-Since' before proxying the request to index.html, forcing the asset-server to answer with 200 + body. * Treat 304 responses as success so the fallback still works when the file is cached. * Ensure Content-Type: text/html; charset=utf-8 is set to prevent MIME errors in the browser. * Leave static asset requests (.js, .css, images, etc.) untouched. This guarantees deep-link routes keep working after multiple page reloads and removes the 'Not Found / text/plain MIME type' error seen on the second refresh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a fair feature request; the Flutter dev server does something similar to this (here)
This feature does not have tests, it'd be nice to have a test with a couple of test cases to see what's expected of this (see my question below about the URL ending with a /
or not)
(I'm not an owner of this package, so take this review with a pinch of salt, pls!)
@@ -195,6 +195,43 @@ class WebDevServer { | |||
cascade = cascade.add(assetHandler); | |||
} | |||
|
|||
if (options.configuration.spaFallback) { | |||
FutureOr<Response> spaFallbackHandler(Request request) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of defining this inline, maybe this could be defined in a separate file, similar to handlers/favicon_handler.dart
?
That could make it a little bit easier to add a test for!
@@ -75,6 +75,10 @@ refresh: Performs a full page refresh. | |||
..addFlag(logRequestsFlag, | |||
negatable: false, | |||
help: 'Enables logging for each request to the server.') | |||
..addFlag('spa-fallback', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any interest on more thoroughly documenting this param here? Is there a better place to describe this? (it seems that lots of parameters are missing)
} | ||
|
||
final indexUri = | ||
request.requestedUri.replace(path: 'index.html', query: ''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised that index.html
is not configurable in the serve
command or elsewhere, wow. I'd move index.html
to a const
at the top of the file though.
final hasExt = request.url.pathSegments.isNotEmpty && | ||
request.url.pathSegments.last.contains('.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do we see here if the request.url
ends in /
? is the last pathSegment an empty string, or is it the name of the last segment before the /
?
When developing Single Page Applications (SPAs) that use client-side routing with the HTML5 History API (e.g., Angular's PathLocationStrategy), refreshing the page on a deep link results in a 404 error because the server can't find a corresponding file for the route. This change introduces a --spa-fallback flag to the webdev serve command. When enabled, the development server will serve the root index.html file for any GET request that would otherwise result in a 404, as long as the path does not appear to be a direct file asset (i.e., does not contain a file extension). This allows the client-side router to take over and handle the request, enabling a seamless development workflow for modern web applications.
Contribution guidelines:
dart format
.Note that many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.