Replies: 11 comments 11 replies
-
Very much excited for this proposal, One thing of note:
While I get exposing I think that this should continue to happen. For example, if a framework has an OAuth plugin installed, it makes sense to have the framework add it to the include in |
Beta Was this translation helpful? Give feedback.
-
❤️ I love this! Thank you guys so much for listening and putting together this proposal. Only had two thoughts - once was similar to Isaac, and the second was wether routes definition might make sense in the Either way, very excited for this. |
Beta Was this translation helpful? Give feedback.
-
Love it! Will the Vite plugin also support |
Beta Was this translation helpful? Give feedback.
-
Small note on the naming—I realize it's meant to be the same as Cloudflare Pages, but I find it fairly unintuitive. I would find it clearer if it were something like
|
Beta Was this translation helpful? Give feedback.
-
My feedback would be to continue using the wrangler assets field for templates and tutorials, most apps will only need that. Also remove the binding field in tutorials code, only add it for workers that use it in advanced use cases, showing this field confuses the reader because it looks like you have to handle static assets yourself in the worker, using a KV binding, which is not the case. This happened to me for example. Another improvement is to add this detailed documentation in the wrangler json schema description for the assets field, then add the $schema field to all wrangler.jsonc files in documentation and guides. |
Beta Was this translation helpful? Give feedback.
-
Hi! I am building a small SPA on Cloudflare workers and have some thoughts:
I don't like the naming and that it's a separate file (coming for someone that hasn't created manually a |
Beta Was this translation helpful? Give feedback.
-
My high level perspective is that there are two use cases to cover here:
I think _routes.json makes sense for (1) – but for (2) I think _routes.json is too complicated for most users (and unnecessary to have in a separate file) I think for (2) all you need is: "assets": {
"directory": "./dist/",
"not_found_handling": "single-page-application",
"handle_by_worker": ["/api/"]
} Obviously plenty of bike shedding to be had over naming. I think it needs to be something less ambiguous that
(We already use the term "handle" in "not_found_handling", so one advantage of reusing that is that it makes it less confusing) |
Beta Was this translation helpful? Give feedback.
-
In the proposal and future documentation, can we please make absolutely clear that the local development environment with the cloudflare vite plugin will behave exactly like the deployed production environment. It is absolutely crucial that it is crystal clear which request will incur costs and which will be free thanks to cloudflares generous free asset hosting 🙏 Thanks for the great work 🥰 |
Beta Was this translation helpful? Give feedback.
-
This is really exciting, as a framework author I think this adds a ton of clarity, and we would love to add a set of default rules to our starter projects. My only request is that you also support |
Beta Was this translation helpful? Give feedback.
-
I don't have a lot to add except to +1 that all I want right now is to be able to support my oAuth routes and it sounds like this proposal covers that and a lot more. I wasn't even sure about making it a list in my worker-first-paths PR rather than a single prefix as you can always nest more under it. Nearly just went with a hard-coded Are there documented use-cases for the other scenarios? I can only see I'm guessing that I'm just ignorant of the use-cases, but it feels like anything that wouldn't be covered by a simple whitelist of routes to go to the worker could probably be cleaned up so it could and gain other benefits in the process. On the other hand, maybe you just want to make it possible it to support those complex cases. |
Beta Was this translation helpful? Give feedback.
-
Is there a rough estimate already when this will be released? |
Beta Was this translation helpful? Give feedback.
-
Hi 👋
I’m a product manager on the Workers team, and we’d like feedback on the following proposal for adding support for
_routes.json
configuration files when using Workers with static assets. In a_routes.json
file, you can more granularly specify which routes invoke your Worker by specifyinginclude
andexclude
rules. Many Cloudflare Pages projects use_routes.json
in this way today, and our goal is to make it even easier to migrate from Pages to Workers, as well as solve some of the issues we’ve been receiving regarding our current routing.Below I’ve included some background information on how routing currently works so that we can all get on the same page – you can skip to the actual proposal if you’re already familiar. It’s a bit long, but we’ve had enough questions about our routing that it felt necessary to outline.
Background - How routing currently works
Currently, you can upload and immediately route traffic to a fullstack app on Cloudflare Workers by configuring an
assets
block in your Wrangler configuration file and deploying:This
assets
block informs Cloudflare that you have client-side code/an asset that can be found in your build output directory, which the platform then uploads. Then, when requests come in, Cloudflare looks at the request path and checks if there’s a file name in your assets directory that matches – if so, that asset is served directly. Put more simply, static assets have precedence by default. If there’s no match, your Worker code (if there is any) is invoked. If there is no Worker, we return an empty 404 response by default.The original philosophy of this design was to treat your Worker as the primary entity, while still providing enough sensible defaults out-of-the-box. Assets can be served automatically by just matching the path to a file name, or you can treat static assets as a resource that the Worker can interact with (via the asset binding,
env.ASSETS
). Then, all your routing logic (especially for dynamic requests) can reside within your Worker code, providing you a lot more control over routing behavior. This allows you to decide if and when to involve the Worker in serving assets – you can use the path matching defaults, or change the behavior in your Wrangler config.You have a few levers to change these defaults, but for the purpose of this proposal, we will focus on two:
run_worker_first
: Switches the routing behavior so that your Worker executes first for every request. This blunt instrument was intended for use-cases like gating assets behind authentication.“not_found_handling” = “single-page-application”
: servesindex.html
for unmatched assets. You can also setnot_found_handling
to404-page
, in order to serve a custom 404-page for unmatched requests.Problems
single-page-application
modeWhen we first released support for static assets in Workers, enabling
single-page-application
mode meant that if you had a server-side API alongside your SPA, all non-asset requests would get forwarded to your Worker’sfetch()
handler before servingindex.html
. If a non-asset request was intended to go toindex.html
, it would need to be configured via theenv.ASSETS
binding. This meant that servingindex.html
for unmatched routes would always invoke your Worker, using up a request against your Worker’s request limit (and potentially incurring a financial cost).This behavior got a lot of complaints, such as this issue. To fix this, we recently implemented a new default (
assets_navigation_prefers_asset_serving
), which infers whether a request that doesn’t match an asset should be servedindex.html
, or should be handled by the Worker. The heuristic relies on the presence of aSec-Fetch-Mode: navigate
header, that is attached to browser navigation requests in modern browsers. If this header is present, we now invoke thenot_found_handling
behavior directly (forsingle-page-application
mode, this means servingindex.html
).This change, however, caused even more problems. Primarily:
Users want to be able to view their API routes in the browser, including when
not_found_handling
equalssingle_page_application
. With this new default behavior, however, navigating to your API in a browser means your request now has this special header attached, and you’ll seeindex.html
instead of hitting API.Users want to send some browser navigations to their Worker, such as for OAuth redirects. With this new behavior, we broke a lot of OAuth flows unexpectedly.
We honestly moved too fast and broke things – we changed the defaults within a short time frame, didn’t have the proper docs up, and didn’t communicate these changes to the community. Now, we’d like to fix these problems with your feedback, and prevent these kinds of mishaps in the future.
run_worker_first
As mentioned,
run_worker_first
is a blunt instrument: routing either prefers assets by default, or prefers the Worker whenrun_worker_first
is set to true. But there’s no declarative way currently to say “just invoke my Worker on these routes, else serve the assets.” It’s all or nothing.Routing behavior is confusing
In general, we’ve consistently heard (and experienced ourselves in writing these kinds of documents) that our routing is “too magical” and a lot to keep in mind. The complex interplay between
run_worker_first
, direct path matching, the choice to callenv.ASSETS.fetch()
inside the Worker, andnot_found_handling
(along with our other configuration options) creates a system that is difficult to reason about. It’s difficult to pinpoint why a request was served directly, why the Worker ran, or whyenv.ASSETS.fetch()
returned a specific result without deeply understanding all interacting configurations. While our intention was to give users more control over how requests are routed via the Worker, we ultimately failed in making this actually easy to use and understand.Proposed solution
Our proposed solution is to support
_routes.json
configuration (as is present in Pages today) and run the_routes.json
logic as an initial filter.Depending on the request matching
exclude
orinclude
patterns in_routes.json
, this logic will delegate handling of the request to either:not_found_handling
has been configured (eithersingle_page_application
or404-page
) and serves eitherindex.html
or a custom 404-page.The following diagram shows how requests might be handled depending on the
not_found_handling
configuration:The routing algorithm is as follows:
exclude
routing patterns. If any match, then run Asset service handling.include
routing patterns. If any match then run User Worker handling.The handling options are:
User Worker: The request will skip the Routing service and go straight to the User Worker. The User Worker could then delegate back to the Asset service if desired by calling
env.ASSETS.fetch(req)
.Asset service: The request will skip the Routing service and go straight to the Asset service. The Asset service will still apply its
html_handling
configuration as normal.Routing service: The request will go to the Routing service and follow its normal behaviour. This might involve forwarding the request to either the User Worker or Asset service depending upon the
not_found_handling
and theassets_navigation_prefers_asset_serving
compatibility flag.This approach has the following notable features:
If there is no
_routes.json
, or it is empty, then there is no change to the current behaviour of Workers with static assets.A
_routes.json
that contains just{ “include”: “*” }
will be equivalent to”run_worker_first”: true
, making it superfluous. If this proposal is approved, we’d like to then deprecaterun_worker_first
to keep configuration options more sensible.The routing logic is close enough to Pages that it is trivial to migrate a Pages project that has a
_routes.json
file to Workers with assets.Proposed solution examples
The scenarios below are based on the presence of a User Worker and the following assets:
index.html
404.html
code.js
image.jpg
assets/style.css
With the following
_routes.json
file:Navigation request (
Sec-Fetch-Mode: navigate
)/code.js
/code.js
/code.js
/code.js
/missing
404.html
index.html
/image.jpg
/worker/a
/worker/b
404.html
index.html
/assets/style.css
assets/style.css
assets/style.css
assets/style.css
/assets/missing
404.html
index.html
Non-navigation request
(blue is used to indicate where the behaviour differs from a navigation request)
/code.js
/code.js
/code.js
/code.js
/missing
/image.jpg
/worker/a
/worker/b
404.html
index.html
/assets/style.css
assets/style.css
assets/style.css
assets/style.css
/assets/missing
404.html
index.html
Other options considered
Given the proposed algorithm above, there were two other approaches that were considered. Each has slightly different corner-cases, ease of documenting and levels of surprise. The following table shows how the
include
andexclude
settings of_routes.json
would behave.include
matches go to User Worker,exclude
matches go to Asset service, default handling (Routing service) otherwise_routes.json
file can be used to fully control where requests are handled, but also allows the current default handling to kick in naturally. In this case the use of a_routes.json
file is a purely additive feature, which means that it can be added by an application developer without affecting how a full-stack framework might handle routing.include
matches go to User Workernot_found_handling
isnone
or if it is not a navigation request.include
matches go to User Worker, no default handlingSec-Fetch-Mode
behaviour and replaces the Routing service logic, when there is a_routes.json
. Adding a_routes.json
(even if it is empty) will change how routing of a request works. This arguably provides the most accurate compatibility with Pages projects but is not as intuitive whennot_found_handling
is set tonone
.Call to action
Please provide feedback on the above proposal. We’d love to hear if you think supporting
_routes.json
would improve the current routing behavior, or if the routing layers would get even more complicated. We’d love to hear any suggestions or feedback, even on the other options we’ve considered.Framework authors and maintainers – we’d love your feedback as well. You see first-hand many pain-points our users run into, and your insight is invaluable.
_routes.json
becomes a user-facing configuration file, and frameworks should avoid using it for framework-specific purposes. When combined with the built-in asset handling fallback, frameworks will no longer need to list entire asset manifests into theexclude
rules inroutes.json
.If you have other general feedback about Workers with static assets, please feel free to join our community Discord, or you can book time with me directly to talk about your feedback on my calendar.
Thank you for reading through this longer post – we really appreciate it. And a huge thank you to @jamesopstad for driving this proposed solution, and putting tons of care into the routing scenarios.
Excited to get your thoughts, and thank you for using Workers and being such an engaged community.
Beta Was this translation helpful? Give feedback.
All reactions