Replies: 16 comments 47 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.
-
As someone who has spent the last couple weeks trying desperately to wrap my head around Cloudflare in general and the Vite Plugin specifically, in order to get a starter kit deployed to Workers that combines Cloudflare's React template w/ authentication through Better Auth, I just want to add that I am incredibly excited for this proposal, and welcome any and all increased clarity in the docs about how CF functions behind the scenes. I personally prefer lower-magic solutions, and an option to be more explicit and intentional about what will happen when navigating about my app is appreciated. I was eventually able to get authentication working using Better Auth's Email OTP plugin, but social OAuth has been a pain, no matter what config tricks I tried. I'm at least happy to KNOW what the problem is now (I learned a lot about how CF has been handling routing so far reading through this proposal), and am eager to adopt the |
Beta Was this translation helpful? Give feedback.
-
If I want to use the cloudflare vite plugin project already without having to wait, but also staying as compatible as possible with the future proposals implementation, I use
"assets": {
"binding": "ASSETS",
"run_worker_first": true
}, And in the worker: import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { prettyJSON } from 'hono/pretty-json'
import { authMiddleware } from './middleware/auth'
import authRoutes from './routes/auth'
import { createDb } from './db'
import type { Kysely } from 'kysely'
import type { DB } from './db/types'
interface Env {
ASSETS: Fetcher
DATABASE_URL: string
}
interface Variables {
db: Kysely<DB>
}
const app = new Hono<{ Bindings: Env, Variables: Variables }>()
// Initialize database
app.use('*', async (c, next) => {
c.set('db', createDb(c.env))
await next()
})
// Middleware
app.use('*', logger())
app.use('*', prettyJSON())
app.use('*', cors())
app.use('*', authMiddleware)
// API Routes
app.route('/api/auth', authRoutes)
// Health check
app.get('/api/health', (c) => {
return c.json({ status: 'ok' })
})
// Serve static assets and handle client-side routing
app.get('*', async (c) => {
try {
// Try to serve the static asset
const response = await c.env.ASSETS.fetch(c.req.url)
if (response.status === 200) {
return response
}
// If asset not found, serve index.html for client-side routing
return c.env.ASSETS.fetch(new URL('/index.html', c.req.url))
} catch (e) {
// If anything fails, serve index.html
return c.env.ASSETS.fetch(new URL('/index.html', c.req.url))
}
})
export default app So by the time the proposal is implemented I can just change the I know that this interim solution invokes the worker on every request, but it enables my OAuth solution to work as expected without major code changes and will only go into production once the proposal is implemented. Is this OK or am I missing something important? |
Beta Was this translation helpful? Give feedback.
-
Hi all, thanks again for all the valuable feedback on this proposal -- it means a ton. The backend work for supporting more granular routing control is nearly complete, and we now need to finalize how users will configure this. From the discussion here, a few distinct approaches for user configuration have emerged as the main contenders. We'd like to lay these out and gather your thoughts, before making the final decision: 1. User-Facing
2. Routing is configured
Example (
3. Configuration within Wrangler config by evolving
Example (
We're now at a crucial point where we need to decide which of these options (or a close variation) offers the best balance of usability, functionality, and clarity for you. With that, we'd love your thoughts on:
Thanks again -- we are supporting the ability to more finely specify which routes invoke your Worker, so any feedback on the DX is much appreciated. |
Beta Was this translation helpful? Give feedback.
-
Hi all, an exciting update! For example:
Advanced routing control is supported in:
Docs
Thank you all SO much for working with us on this, and helping us design the right solution. Definitely try it out and let us know of any issues you run into. |
Beta Was this translation helpful? Give feedback.
-
Hi we are in the process of porting our Astro.js website which is currently on Cloudflare Pages to Cloudflare workers by using the @astrojs/cloudflare plugin and are facing a issue w.r.t how cloudflare static asset routing which is shooting up our overall workers bill. Being a static site generator, Astro.js outputs Our use case requires the use of Astro's Middleware feature to run on specific routes. Since the middleware's default behaviour is to run on all routes, a worker invocation is triggered on every request that does not match a static asset, i.e. a A) Routes that we want to hit the middleware e.g. As of now, there is no provision in the Astro middleware to run on specific routes, like on other frameworks such as Next.js. Case B) is causing thousands of worker invocations on our current staging Astro website. The majority of these requests are coming from malicious bots and crawlers looking for sensitive files such as Hence we are trying to find a solution that allows the Astro middleware worker script to run only on Case A), i.e. only on explicitly whitelisted paths. In light of this issue, we have been closely following this proposal. Since it was merged, we have been trying make it work towards the above mentioned use case but haven't got any progress yet. Here is one of the only combinations of the "assets": {
"binding": "ASSETS",
"directory": "./dist",
"html_handling": "force-trailing-slash",
"run_worker_first": ["!/*", "/oauth/callback"]
} Before: worker would run on every route for which asset not found, including 404 Now: Worker is never invoked and an empty no-body 404 response is returned if no asset is found. This happens even on sending a request to Reasoning: The negative Intended: Worker script should (only) get invoked if the request path matches I think we want positive rules to have higher precedence over negative rules but the current implementation is the other way around. We would be glad if you could point us towards an asset config that might help us achieve our use case. We also experimented with the This is the config we tried but it doesn't seem to be doing anything: { "version": 1, "include": ["/oauth/callback"], "exclude": ["/*"] } |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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