Skip to content

Commit b7342a1

Browse files
committed
adds new static routing options under run_worker_first
1 parent 1674e46 commit b7342a1

File tree

6 files changed

+133
-226
lines changed

6 files changed

+133
-226
lines changed

src/content/docs/workers/static-assets/billing-and-limitations.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ description: Billing, troubleshooting, and limitations for Static assets on Work
1111

1212
Requests to a project with static assets can either return static assets or invoke the Worker script, depending on if the request [matches a static asset or not](/workers/static-assets/routing/).
1313

14-
Requests to static assets are free and unlimited. Requests to the Worker script (for example, in the case of SSR content) are billed according to Workers pricing. Refer to [pricing](/workers/platform/pricing/#example-2) for an example.
15-
16-
There is no additional cost for storing Assets.
14+
- Requests to static assets are free and unlimited. Requests to the Worker script (for example, in the case of SSR content) are billed according to Workers pricing. Refer to [pricing](/workers/platform/pricing/#example-2) for an example.
15+
- There is no additional cost for storing Assets.
16+
- **Important note for free tier users**: When using [`run_worker_first`](/workers/static-assets/binding/#run_worker_first), requests matching the specified patterns will always invoke your Worker script. If you exceed your free tier request limits, these requests will receive a 429 (Too Many Requests) response instead of falling back to static asset serving.
1717

1818
## Limitations
1919

src/content/docs/workers/static-assets/binding.mdx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,32 @@ Now Wrangler will not upload these files as client-side assets when deploying th
6363

6464
Controls whether to invoke the Worker script regardless of a request which would have otherwise matched an asset. `run_worker_first = false` (default) will serve any static asset matching a request, while `run_worker_first = true` will unconditionally [invoke your Worker script](/workers/static-assets/routing/worker-script/#run-your-worker-script-first).
6565

66+
You can also specify `run_worker_first` as an array of route patterns to selectively run the Worker first only for specific routes:
67+
68+
<WranglerConfig>
69+
70+
```toml title="wrangler.toml"
71+
name = "my-worker"
72+
compatibility_date = "2024-09-19"
73+
main = "src/index.ts"
74+
75+
[assets]
76+
directory = "./public/"
77+
binding = "ASSETS"
78+
run_worker_first = [
79+
"/api/**", # API calls go to Worker first
80+
"!/api/docs/**" # EXCEPTION: For /api/docs/**, try static assets first
81+
]
82+
```
83+
84+
</WranglerConfig>
85+
86+
The array supports glob patterns with `**` for deep matching and exception patterns with `!` prefix. In this configuration, requests to `/api/**` routes will invoke the Worker script first, except for `/api/docs/**` which will follow the default asset-first routing behavior.
87+
88+
<Aside type="caution">
89+
When using `run_worker_first`, requests matching the specified patterns will always invoke your Worker script. If you're on the free tier and exceed your request limits, these requests will receive a 429 (Too Many Requests) response instead of falling back to static asset serving. Consider this when designing your routing patterns.
90+
</Aside>
91+
6692
<WranglerConfig>
6793

6894
```toml title="wrangler.toml"

src/content/docs/workers/static-assets/index.mdx

Lines changed: 25 additions & 219 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ By default, if a requested URL matches a file in the static assets directory, th
8787

8888
The default behavior for requests which don't match a static asset can be changed by setting the [`not_found_handling` option under `assets`](/workers/wrangler/configuration/#assets) in your Wrangler configuration file:
8989

90-
- [`not_found_handling = "single-page-application"`](/workers/static-assets/routing/single-page-application/): Sets your application to return a `200 OK` response with `index.html` for requests which don't match a static asset. Use this if you have a Single Page Application.
90+
- [`not_found_handling = "single-page-application"`](/workers/static-assets/routing/single-page-application/): Sets your application to return a `200 OK` response with `index.html` for requests which don't match a static asset. Use this if you have a Single Page Application. We recommend pairing this with selective routing using `run_worker_first` for [advanced routing control](/workers/static-assets/routing/single-page-application/#advanced-routing-control).
9191
- [`not_found_handling = "404-page"`](/workers/static-assets/routing/static-site-generation/#custom-404-pages): Sets your application to return a `404 Not Found` response with the nearest `404.html` for requests which don't match a static asset.
9292

9393
<WranglerConfig>
@@ -101,8 +101,9 @@ The default behavior for requests which don't match a static asset can be change
101101

102102
</WranglerConfig>
103103

104-
If you want the Worker code to execute before serving an asset (for example, to protect an asset behind authentication), you can set `run_worker_first = true`.
104+
If you want the Worker code to execute before serving assets, you can use the `run_worker_first` option. This can be set to `true` to run the Worker for all requests, or configured as an array of route patterns for selective Worker-first routing:
105105

106+
**Run Worker for all requests:**
106107
<WranglerConfig>
107108

108109
```toml
@@ -114,240 +115,45 @@ If you want the Worker code to execute before serving an asset (for example, to
114115

115116
</WranglerConfig>
116117

117-
<LinkCard
118-
title="Routing options"
119-
href="/workers/static-assets/routing/"
120-
description="Learn more about how you can customize routing behavior."
121-
/>
122-
123-
### Caching behavior
124-
125-
Cloudflare provides automatic caching for static assets across its network, ensuring fast delivery to users worldwide. When a static asset is requested, it is automatically cached for future requests.
126-
127-
- **First Request:** When an asset is requested for the first time, it is fetched from storage and cached at the nearest Cloudflare location.
128-
129-
- **Subsequent Requests:** If a request for the same asset reaches a data center that does not have it cached, Cloudflare's [tiered caching system](/cache/how-to/tiered-cache/) allows it to be retrieved from a nearby cache rather than going back to storage. This improves cache hit ratio, reduces latency, and reduces unnecessary origin fetches.
130-
131-
## Try it out
132-
133-
#### 1. Create a new Worker project
134-
135-
```sh
136-
npm create cloudflare@latest -- my-dynamic-site
137-
```
138-
139-
**For setup, select the following options**:
140-
141-
- For _What would you like to start with?_, choose `Framework`.
142-
- For _Which framework would you like to use?_, choose `React`.
143-
- For _Which language do you want to use?_, choose `TypeScript`.
144-
- For _Do you want to use git for version control_?, choose `Yes`.
145-
- For _Do you want to deploy your application_?, choose `No` (we will be making some changes before deploying).
146-
147-
After setting up the project, change the directory by running the following command:
148-
149-
```sh
150-
cd my-dynamic-site
151-
```
152-
153-
#### 2. Build project
154-
155-
Run the following command to build the project:
156-
157-
```sh
158-
npm run build
159-
```
160-
161-
We should now see a new directory `/dist` in our project, which contains the compiled assets:
162-
163-
<FileTree>
164-
- package.json
165-
- index.html
166-
- ...
167-
- dist Asset directory
168-
- ... Compiled assets
169-
- src
170-
- ...
171-
- ...
172-
173-
</FileTree>
174-
175-
In the next step, we use a Wrangler configuration file to allow Cloudflare to locate our compiled assets.
176-
177-
#### 3. Add a Wrangler configuration file (`wrangler.toml` or `wrangler.json`)
178-
179-
<WranglerConfig>
180-
181-
```toml
182-
name = "my-spa"
183-
compatibility_date = "2025-01-01"
184-
[assets]
185-
directory = "./dist"
186-
```
187-
188-
</WranglerConfig>
189-
190-
**Notice the `[assets]` block**: here we have specified our directory where Cloudflare can find our compiled assets (`./dist`).
191-
192-
Our project structure should now look like this:
193-
194-
<FileTree>
195-
- package.json
196-
- index.html
197-
- **wrangler.toml** Wrangler configuration
198-
- ...
199-
- dist Asset directory
200-
- ... Compiled assets
201-
- src
202-
- ...
203-
- ...
204-
205-
</FileTree>
206-
207-
#### 4. Deploy with Wrangler
208-
209-
```sh
210-
npx wrangler deploy
211-
```
212-
213-
Our project is now deployed on Workers! But we can take this even further, by adding an **API Worker**.
214-
215-
#### 5. Adjust our Wrangler configuration
216-
217-
Replace the file contents of our Wrangler configuration with the following:
218-
118+
**Run Worker first for specific routes only:**
219119
<WranglerConfig>
220120

221121
```toml
222-
name = "my-spa"
223-
main = "src/api/index.js"
224-
compatibility_date = "2025-01-01"
225122
[assets]
226123
directory = "./dist"
227-
binding = "ASSETS"
228-
not_found_handling = "single-page-application"
124+
run_worker_first = [
125+
"/api/**", # API routes go to Worker first
126+
"/auth/**", # Auth routes go to Worker first
127+
"!/api/public/**" # Exception: public API docs served as assets
128+
]
229129

230130
```
231131

232132
</WranglerConfig>
233133

234-
We have edited the Wrangler file in the following ways:
235-
236-
- Added `main = "src/api/index.js"` to tell Cloudflare where to find our Worker code.
237-
- Added an `ASSETS` binding, which our Worker code can use to fetch and serve assets.
238-
- Enabled routing for Single Page Applications, which ensures that unmatched routes (such as `/dashboard`) serve our `index.html`.
239-
240-
:::note
241-
242-
By default, Cloudflare serves a `404 Not Found` to unmatched routes. To have the frontend handle routing instead of the server, you must enable `not_found_handling = "single-page-application"`.
134+
This selective approach allows you to protect specific routes behind authentication while serving other assets directly, and disables automatic `Sec-Fetch-Mode: navigate` detection for explicit routing control.
243135

244-
:::
245-
246-
#### 5. Create a new directory `/api`, and add an `index.js` file
247-
248-
Copy the contents below into the index.js file.
249-
250-
```js {13}
251-
// api/index.js
252-
253-
export default {
254-
async fetch(request, env) {
255-
const url = new URL(request.url);
256-
257-
if (url.pathname.startsWith("/api/")) {
258-
return new Response(JSON.stringify({ name: "Cloudflare" }), {
259-
headers: { "Content-Type": "application/json" },
260-
});
261-
}
262-
263-
return env.ASSETS.fetch(request);
264-
},
265-
};
266-
```
267-
268-
**Consider what this Worker does:**
269-
270-
- Our Worker receives a HTTP request and extracts the URL.
271-
- If the request is for an API route (`/api/...`), it returns a JSON response.
272-
- Otherwise, it serves static assets from our directory (`env.ASSETS`).
273-
274-
#### 6. Call the API from the client
275-
276-
Edit `src/App.tsx` so that it includes an additional button that calls the API, and sets some state. Replace the file contents with the following code:
277-
278-
```js {9,25, 33-47}
279-
280-
// src/App.tsx
281-
import { useState } from "react";
282-
import reactLogo from "./assets/react.svg";
283-
import viteLogo from "/vite.svg";
284-
import "./App.css";
285-
286-
function App() {
287-
const [count, setCount] = useState(0);
288-
const [name, setName] = useState("unknown");
289-
290-
return (
291-
<>
292-
<div>
293-
<a href="https://vite.dev" target="_blank">
294-
<img src={viteLogo} className="logo" alt="Vite logo" />
295-
</a>
296-
<a href="https://react.dev" target="_blank">
297-
<img src={reactLogo} className="logo react" alt="React logo" />
298-
</a>
299-
</div>
300-
<h1>Vite + React</h1>
301-
<div className="card">
302-
<button
303-
onClick={() => setCount((count) => count + 1)}
304-
aria-label="increment"
305-
>
306-
count is {count}
307-
</button>
308-
<p>
309-
Edit <code>src/App.tsx</code> and save to test HMR
310-
</p>
311-
</div>
312-
<div className="card">
313-
<button
314-
onClick={() => {
315-
fetch("/api/")
316-
.then((res) => res.json() as Promise<{ name: string }>)
317-
.then((data) => setName(data.name));
318-
}}
319-
aria-label="get name"
320-
>
321-
Name from API is: {name}
322-
</button>
323-
<p>
324-
Edit <code>api/index.ts</code> to change the name
325-
</p>
326-
</div>
327-
<p className="read-the-docs">
328-
Click on the Vite and React logos to learn more
329-
</p>
330-
</>
331-
);
332-
}
333-
334-
export default App;
335-
```
136+
<LinkCard
137+
title="Routing options"
138+
href="/workers/static-assets/routing/"
139+
description="Learn more about how you can customize routing behavior."
140+
/>
336141

337-
Before deploying again, we need to rebuild our project:
142+
### Caching behavior
338143

339-
```sh
340-
npm run build
341-
```
144+
Cloudflare provides automatic caching for static assets across its network, ensuring fast delivery to users worldwide. When a static asset is requested, it is automatically cached for future requests.
342145

343-
#### 7. Deploy with Wrangler
146+
- **First Request:** When an asset is requested for the first time, it is fetched from storage and cached at the nearest Cloudflare location.
344147

345-
```sh
346-
npx wrangler deploy
148+
- **Subsequent Requests:** If a request for the same asset reaches a data center that does not have it cached, Cloudflare's [tiered caching system](/cache/how-to/tiered-cache/) allows it to be retrieved from a nearby cache rather than going back to storage. This improves cache hit ratio, reduces latency, and reduces unnecessary origin fetches.
347149

348-
```
150+
## Try it out
349151

350-
Now we can see a new button **Name from API**, and if you click the button, we can see our API response as **Cloudflare**!
152+
<LinkCard
153+
title="Vite + React SPA tutorial"
154+
href="/workers/vite-plugin/tutorial/"
155+
description="Learn how to build and deploy a full-stack Single Page Application with static assets and API routes."
156+
/>
351157

352158
## Learn more
353159

src/content/docs/workers/static-assets/routing/single-page-application.mdx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,36 @@ Configuring `assets.not_found_handling` to `single-page-application` overrides t
3333

3434
<Render file="navigation_requests" />
3535

36+
## Advanced routing control
37+
38+
For more explicit control over SPA routing behavior, you can use `run_worker_first` with an array of route patterns. This approach disables the automatic `Sec-Fetch-Mode: navigate` detection and gives you explicit control over which requests should be handled by your Worker script vs served as static assets.
39+
40+
<WranglerConfig>
41+
42+
```json
43+
{
44+
"name": "my-spa-worker",
45+
"compatibility_date": "$today",
46+
"main": "./src/index.ts",
47+
"assets": {
48+
"directory": "./dist/",
49+
"binding": "ASSETS",
50+
"run_worker_first": [
51+
"/app/**", // App routes go to Worker for SPA handling
52+
"!/app/assets/**" // Exception: static assets served directly
53+
]
54+
}
55+
}
56+
```
57+
58+
</WranglerConfig>
59+
60+
This configuration provides explicit routing control without relying on browser navigation headers, making it ideal for complex SPAs that need fine-grained routing behavior. Your Worker script will need to handle the matched routes and use the assets binding to serve the appropriate content.
61+
62+
<Aside type="caution">
63+
When using `run_worker_first`, requests matching the specified patterns will always invoke your Worker script. If you're on the free tier and exceed your request limits, these requests will receive a 429 (Too Many Requests) response instead of falling back to static asset serving. Consider this when designing your routing patterns.
64+
</Aside>
65+
3666
## Local Development
3767

3868
If you are using a Vite-powered SPA framework, you might be interested in using our [Vite plugin](/workers/vite-plugin/) which offers a Vite-native developer experience.

0 commit comments

Comments
 (0)