---
title: "Static assets"
---

> Documentation Index
> Fetch the complete documentation index at: https://ermahgerd-docs.t-bd7662a5eeb4.workers.ermahgerd.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Static assets

Deploy a static site or attach files to a Worker with ordinary Wrangler
configuration. Asset bytes use the appliance's existing storage blob engine;
the assets service adds immutable manifests, upload validation, routing,
retention, and serving. This is not an S3 bucket, a pull-through cache, or a CDN
product, and it does not create a process per site.

```toml
[assets]
directory = "./public"
binding = "ASSETS"
run_worker_first = ["/api/*"]
```

Then run `wrangler deploy` against the local API. A site with no Worker script
can omit `main`; a full-stack Worker can use `env.ASSETS.fetch(request)`.
See [Databases](/databases/) for the combined native example and runnable check.

## Routing

Matching files are served first by default. Requests without an asset match
fall through to the Worker. Set `run_worker_first = true` to invoke the Worker
first, or use a list of path patterns and `!` exclusions to protect selected
paths. These patterns support one wildcard per pattern.

Supported asset behavior includes GET and HEAD, content types, ETags and
If-None-Match, byte ranges, ETag-based If-Range, HTML handling modes, SPA index
fallback, and nearest-parent `404.html` fallback. `_headers` path rules and
`_redirects` path/placeholder rules are validated during publication. Redirect
statuses 301/302/303/307/308 and local 200 rewrites are supported. External proxy
rewrites and hostname-based `_headers` rules are rejected. Date-based conditional
requests are not implemented; use ETags.
Named placeholders occupy a complete path segment (for example, `/blog/:slug`);
ambiguous adjacent captures are rejected before publication.

An asset hit currently passes through a platform-injected wrapper in the same
dynamic isolate as the Worker. It does not invoke the application's fetch
handler on an assets-first hit, but it is **not** a separate isolate-free CDN
fast path. Application module initialization can therefore affect first use.

## Publication and rollback

Wrangler submits a manifest, receives an expiring organization/Worker-scoped
upload capability, and uploads only missing hashes. The pinned CLI's single
asset upload protocol is supported. The service checks both byte length and
Wrangler's BLAKE3 content hash, and refuses to publish an incomplete manifest.

Each Worker version records one immutable asset manifest. Code, bindings, and
manifest change together at the existing routing cutover; rollback selects the
old version's manifest too. Requests already executing retain their selected
version. The console's deployment details show file count and manifest bytes.

Retained Worker versions pin their assets. After pruning, manifests and blobs
remain available for the HAProxy drain budget (one hour by default), so an
in-flight old isolate can still fetch its files. This grace retention remains
charged to the organization. Abandoned uploads expire after one
hour. The assets service periodically collects expired, unreferenced sessions
and blobs. A durable control-plane cleanup outbox makes publication/pruning
cleanup retryable after restart or service failure; cleanup failure does not
turn a successful deployment into a reported failure.

Retention is not stale-URL routing: after a new deployment, new requests resolve
against the newly active manifest. A browser holding old HTML may request an
old fingerprinted URL that is absent from the new manifest and receive a 404.
Include old fingerprinted files in subsequent builds when that overlap is
needed. The service does not search another version's manifest behind your back.

## Isolation and quotas

Assets are scoped by organization and Worker version. Stored blobs use a private
namespace outside tenant-visible R2 buckets. Tenants cannot list them using S3
or R2 bindings. Identical hashes deduplicate within an organization, not across
organizations. Service credentials remain in trusted runtime bindings.

Asset allowances follow the organization tier: Free permits **64 MiB** and two
pending uploads, Standard permits **512 MiB** and five, and Enterprise permits
**2 GiB** and ten. Each manifest supports up to 20,000 files and each file up
to 25 MiB. Assets do not also consume the organization's R2 bucket allowance.
Expired upload slots are reclaimed by the periodic collector; referenced
versions remain charged until their blobs are no longer retained.

## Observability and continuity

Ansible provisions the assets service, scrape targets, Loki journal collection,
Tempo request spans, and the **Databases & Static Assets** Grafana dashboard.
Delivery metrics distinguish failures, cancellations, missing files, conditional
hits, redirects, and successful body completion. First-byte timing starts at
the assets service; it is not browser end-to-end latency. No traffic is not a
passing SLO.

```sh
export ERMAHGERD_OWNER_PASSWORD='<local-owner-password>'
make load-data-update
```

This generates k6 summaries and deployment timestamps under `.cache/`, with a
constant total of 10 public requests/second across direct asset delivery and a
Worker that reads both D1 and its bound assets. It checks code/manifest coherence
through deployment and rollback. The default p99 < 250 ms threshold is a test
guardrail, not a promised asset SLO. Benchmark small/large responses, warm/cold
storage, uploads, and sustained delivery before selecting service objectives.

Source: https://ermahgerd-docs.t-bd7662a5eeb4.workers.ermahgerd.io/static-assets/index.mdx
