Skip to content
PAVEL GLUKHIKH
Menu

Engineering Note

Static sites on Cloudflare Workers static assets

Serving a static Astro build from Cloudflare Workers: trailing slashes, custom 404s, caching, and the one-command deploy loop — this site's exact setup.

2 min read

TL;DR

Cloudflare Workers can serve a static site straight from the edge with no Worker script at all — the static assets feature uploads your build directory and handles routing, caching, and 404s from a few lines of wrangler.jsonc. This note documents the exact configuration this site runs on: a static Astro build, directory-style URLs with auto-trailing-slash, a real 404 page, and a one-command deploy loop.

The setup

This site (iampavel.com) is a fully static Astro build served by Cloudflare Workers static assets. There is no server code and no framework adapter — astro build emits plain HTML into dist/, and wrangler uploads it. The entire hosting configuration:

// wrangler.jsonc
{
  "name": "iampavel",
  "compatibility_date": "2025-06-01",
  "assets": {
    "directory": "./dist",
    "html_handling": "auto-trailing-slash",
    "not_found_handling": "404-page"
  }
}

Deploy loop:

npm run build          # astro build → dist/
npx wrangler deploy    # uploads changed assets only

First-time setup is npx wrangler login, deploy once, then attach the custom domain in the dashboard (Worker → Settings → Domains & Routes). DNS for the apex just points at the Worker — no origin server exists, which also means there is no origin to misconfigure or DDoS (the DNS architecture conversation gets much shorter when the answer is “it’s all edge”).

The three settings that matter

html_handling: "auto-trailing-slash" — serves about/index.html at /about/ and 308-redirects /about to it. Astro is configured with trailingSlash: "always" and build.format: "directory", so generated canonical tags, sitemap entries, and served URLs all agree. Pick one convention and enforce it everywhere; a canonical that redirects is a self-inflicted SEO wound.

not_found_handling: "404-page" — serves dist/404.html with a real HTTP 404 status. The alternative single-page-application mode rewrites misses to index.html with a 200, which is correct for SPAs and wrong for content sites: soft-404s pollute the search index.

No main entry at all — the absence is the feature. With no Worker script, requests never leave Cloudflare’s asset-serving fast path: no cold start, no CPU billing, nothing to patch. The best code is still the code you didn’t deploy.

Caching behavior

Static assets are cached at the edge automatically; you don’t manage a cache config. Two things worth knowing:

  • Deploys are atomic per-file — wrangler uploads a manifest, and new versions serve immediately after deploy. I’ve never caught a stale HTML page post-deploy.
  • Hashed assets (Astro’s /_astro/*.css etc.) are immutable by filename, so repeat visits are effectively free; HTML revalidates.

When you outgrow pure static

The upgrade path is incremental, which is the point of starting here: add a main Worker script and the assets config keeps working, with the script receiving only requests that don’t match a static asset (configurable via run_worker_first if you need middleware). Contact forms, redirects with logic, or an API route slot in later without re-platforming — the same repo-as-source-of-truth discipline applies, since the whole deployment is two files of config in git.

Frequently asked questions

Do I need to write a Worker script to host a static site on Cloudflare Workers?
No. With the assets block in wrangler.jsonc, Cloudflare serves the build directory directly from the edge — no JavaScript entrypoint required. You add a Worker script later only if you need dynamic routes (forms, APIs, redirects beyond what assets config handles).
How do trailing slashes work on Workers static assets?
Set html_handling to auto-trailing-slash: directory-style pages (about/index.html) get canonical /about/ URLs with redirects from /about, and file-style pages (about.html) get /about. Match it to your generator's trailingSlash setting so canonical URLs and served URLs agree — mismatches create redirect chains that waste crawl budget.

References

Related reading