---
title: Routing · Cloudflare Workers docs
description: How Workers assets' routing and its configuration works.
lastUpdated: 2025-04-07T23:45:28.000Z
source_url:
  html: https://77937b5f.preview.developers.cloudflare.com/workers/static-assets/routing/index.md
  md: https://77937b5f.preview.developers.cloudflare.com/workers/static-assets/routing/
---

## Default behavior

By default, assets are served by attempting to match up the incoming request's pathname to a static asset. The structure and organization of files in your static asset directory, along with any routing configuration, determine the routing paths for your application. When a request invokes a Worker with assets:

1. If a request is found with a matching path to the current route requested then that asset will always be served.

In this example, request to `example.com/blog` serves the `blog.html` file.

![A request to example.com/blog serves the /blog.html file. ](https://developers.cloudflare.com/_astro/workers-assets-serve-asset.3IvXeGfG_kdAt9.webp)

1. If there is no Worker script, [`not_found_handling`](https://developers.cloudflare.com/workers/static-assets/routing/#2-not_found_handling) will be evaluated. By default, a null-body 404-status response will be served.

If a Worker is configured, and there are no assets that match the current route requested, the Worker will be invoked. The Worker can then "fall back" to `not_found_handling` asset behavior, by passing the incoming request through to the [asset binding](https://developers.cloudflare.com/workers/static-assets/binding/#runtime-api-reference).

In this example, a request to `example.com/api` doesn't match a static asset so the Worker is invoked.

![A request to example.com/blog runs the Worker.](https://developers.cloudflare.com/_astro/workers-assets-invoke-worker.CqKLM-ef_wOra8.webp)

## Invoking Worker Script Ahead of Assets

You may wish to run code before assets are served. This is often the case when implementing authentication, logging, personalization, internationalization, or other similar functions. [`run_worker_first`](https://developers.cloudflare.com/workers/static-assets/binding/#run_worker_first) is a configuration option available in [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) which controls this behavior. When enabled, `run_worker_first = true` will invoke your Worker's code, regardless of any assets that would have otherwise matched.

Take the following directory structure, [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/), and user Worker code:

* wrangler.jsonc

  ```jsonc
  {
    "name": "my-worker",
    "compatibility_date": "2024-09-19",
    "main": "src/index.ts",
    "assets": {
      "directory": "./public/",
      "binding": "ASSETS",
      "run_worker_first": true
    }
  }
  ```

* wrangler.toml

  ```toml
  name = "my-worker"
  compatibility_date = "2024-09-19"
  main = "src/index.ts"
  assets = { directory = "./public/", binding = "ASSETS", run_worker_first = true }
  ```

```js
export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext
  ): Promise<Response> {
    const url = new URL(request.url);
    if (url.pathname === "/supersecret.txt") {
      const auth = request.headers.get("Authorization");
      if (!auth) {
        return new Response("Forbidden", {
          status: 403,
          statusText: "Forbidden",
          headers: {
            "Content-Type": "text/plain",
          },
        });
      }
    }


    return await env.ASSETS.fetch(request);
  },
};
```

In this example, any request will be routed to our user Worker, due to `run_worker_first` being enabled. As a result, any request being made `/supersecret.txt` without an `Authorization` header will result in a 403.

## Routing configuration

There are two options for asset serving that can be configured in the [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/#assets):

#### 1. `html_handling`

Forcing or dropping trailing slashes on request paths (for example, `example.com/page/` vs. `example.com/page`) is often something that developers wish to control for cosmetic reasons. Additionally, it can impact SEO because search engines often treat URLs with and without trailing slashes as different, separate pages. This distinction can lead to duplicate content issues, indexing problems, and overall confusion about the correct canonical version of a page.

`html_handling` configuration determines the redirects and rewrites of requests for HTML content. It is used to specify the pattern for canonical URLs, thus where Cloudflare serves HTML content from, and additionally, where Cloudflare redirects non-canonical URLs to.

#### 2. `not_found_handling`

In the event a request does not match a static asset, and there is no Worker script (or that Worker script calls `fetch()` on [the assets binding](https://developers.cloudflare.com/workers/static-assets/binding/)), `not_found_handling` determines how Cloudflare will construct a response.

It can be used to serve single-page applications (SPAs), or to serve custom 404 HTML pages.

If creating a SPA, place an `/index.html` in your asset directory. When `not_found_handling` is configured to `"single-page-application"`, this page will be served with a 200 response.

If you have custom 404 HTML pages, and configure `not_found_handling` to `"404-page"`, Cloudflare will recursively navigate up by pathname segment to serve the nearest `404.html` file. For example, you can have a `/404.html` and `/blog/404.html` file, and Cloudflare will serve the `/blog/404.html` file for requests to `/blog/not-found` and the `/404.html` file for requests to `/foo/bar`.

### Default configuration

#### 1. `html_handling`

`"auto-trailing-slash"` is the default mode if `html_handling` is not explicitly specified.

Take the following directory structure:

Based on the incoming requests, the following assets would be served:

| Incoming Request | Response | Asset Served |
| - | - | - |
| /file | 200 | /file.html served |
| /file.html | 307 to /file | - |
| /file/ | 307 to /file | - |
| /file/index | 307 to /file | - |
| /file/index.html | 307 to /file | - |
| /folder | 307 to /folder/ | - |
| /folder.html | 307 to /folder/ | - |
| /folder/ | 200 | /folder/index.html |
| /folder/index | 307 to /folder/ | - |
| /folder/index.html | 307 to /folder/ | - |

#### 2. `not_found_handling`

`"none"` is the default mode if `not_found_handling` is not explicitly specified.

For all non-matching requests, Cloudflare will return a null-body 404-status response.

```plaintext
/not-found -> 404
/foo/path/doesnt/exist -> 404
```

### Alternate configuration options

Alternate configuration options are outlined on this page and can be specified in your project's [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/#assets). If you're deploying using a [framework](https://developers.cloudflare.com/workers/frameworks/), these options will be defined by the framework provider.

Example Wrangler configuration file:

* wrangler.jsonc

  ```jsonc
  {
    "assets": {
      "directory": "./public",
      "binding": "ASSETS",
      "html_handling": "force-trailing-slash",
      "not_found_handling": "404-page"
    }
  }
  ```

* wrangler.toml

  ```toml
  assets = { directory = "./public", binding = "ASSETS", html_handling = "force-trailing-slash", not_found_handling = "404-page" }
  ```

#### `html_handling = "auto-trailing-slash" | "force-trailing-slash" | "drop-trailing-slash" | "none"`

Take the following directory structure:

**`html_handling: "auto-trailing-slash"`**

Based on the incoming requests, the following assets would be served:

| Incoming Request | Response | Asset Served |
| - | - | - |
| /file | 200 | /file.html |
| /file.html | 307 to /file | - |
| /file/ | 307 to /file | - |
| /file/index | 307 to /file | - |
| /file/index.html | 307 to /file | - |
| /folder | 307 to /folder/ | - |
| /folder.html | 307 to /folder | - |
| /folder/ | 200 | /folder/index.html |
| /folder/index | 307 to /folder | - |
| /folder/index.html | 307 to /folder | - |

**`html_handling: "force-trailing-slash"`**

Based on the incoming requests, the following assets would be served:

| Incoming Request | Response | Asset Served |
| - | - | - |
| /file | 307 to /file/ | - |
| /file.html | 307 to /file/ | - |
| /file/ | 200 | /file.html |
| /file/index | 307 to /file/ | - |
| /file/index.html | 307 to /file/ | - |
| /folder | 307 to /folder/ | - |
| /folder.html | 307 to /folder/ | - |
| /folder/ | 200 | /folder/index.html |
| /folder/index | 307 to /folder/ | - |
| /folder/index.html | 307 to /folder/ | - |

**`html_handling: "drop-trailing-slash"`**

Based on the incoming requests, the following assets would be served:

| Incoming Request | Response | Asset Served |
| - | - | - |
| /file | 200 | /file.html |
| /file.html | 307 to /file | - |
| /file/ | 307 to /file | - |
| /file/index | 307 to /file | - |
| /file/index.html | 307 to /file | - |
| /folder | 200 | /folder/index.html |
| /folder.html | 307 to /folder | - |
| /folder/ | 307 to /folder | - |
| /folder/index | 307 to /folder | - |
| /folder/index.html | 307 to /folder | - |

**`html_handling: "none"`**

Based on the incoming requests, the following assets would be served:

| Incoming Request | Response | Asset Served |
| - | - | - |
| /file | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /file.html | 200 | /file.html |
| /file/ | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /file/index | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /file/index.html | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /folder | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /folder.html | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /folder/ | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /folder/index | Depends on `not_found_handling` | Depends on `not_found_handling` |
| /folder/index.html | 200 | /folder/index.html |

#### `not_found_handling = "404-page" | "single-page-application" | "none"`

Take the following directory structure:

**`not_found_handling: "none"`**

```plaintext
/not-found -> 404
/folder/doesnt/exist -> 404
```

**`not_found_handling: "404-page"`**

```plaintext
/not-found -> 404 /404.html
/folder/doesnt/exist -> 404 /folder/404.html
```

**`not_found_handling: "single-page-application"`**

```plaintext
/not-found -> 200 /index.html
/folder/doesnt/exist -> 200 /index.html
```

## Serving assets from a custom path

Note

This feature requires Wrangler v3.98.0 or later.

Like with any other Worker, [you can configure a Worker with assets to run on a path of your domain](https://developers.cloudflare.com/workers/configuration/routing/routes/). Assets defined for a Worker must be nested in a directory structure that mirrors the desired path.

For example, to serve assets from `example.com/blog/*`, create a `blog` directory in your asset directory.

With a [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) like so:

* wrangler.jsonc

  ```jsonc
  {
    "name": "assets-on-a-path-example",
    "main": "src/index.js",
    "route": "example.com/blog/*",
    "assets": {
      "directory": "dist"
    }
  }
  ```

* wrangler.toml

  ```toml
  name = "assets-on-a-path-example"
  main = "src/index.js"
  route = "example.com/blog/*"


  [assets]
  directory = "dist"
  ```

In this example, requests to `example.com/blog/` will serve the `index.html` file, and requests to `example.com/blog/posts/post1` will serve the `post1.html` file.

If you have a file outside the configured path, it will not be served. For example, if you have a `home.html` file in the root of your asset directory, it will not be served when requesting `example.com/blog/home`. However, if needed, these files can still be manually fetched over [the binding](https://developers.cloudflare.com/workers/static-assets/binding/#binding).

```plaintext
```
