A static site with one API route on Cloudflare Workers
How to serve a prerendered site from Workers static assets and run code for one API route only, with run_worker_first. The setup behind this store's downloads.
This store has no backend in the usual sense. Every page is prerendered HTML. The one exception is a download route: a buyer enters a licence key, the key is checked with the payment provider, and the file comes back. That route runs on Cloudflare Workers beside the static site, and no other request touches code.
The config
// wrangler.jsonc
{
"name": "my-site",
"main": "worker/index.ts",
"assets": {
"directory": "./dist",
"binding": "ASSETS",
"run_worker_first": ["/api/*"]
}
}assets.directory is the built site. run_worker_first limits the script to /api/*. Everything else, every page and image, is served as a static asset without running a line of your code.
The Worker
export default {
async fetch(req: Request, env: Env) {
const url = new URL(req.url);
if (url.pathname === "/api/download") return download(req, env);
return env.ASSETS.fetch(req);
},
};The fallback to env.ASSETS.fetch matters for any /api/ path you did not handle: it returns the site's normal 404 rather than a blank error.
What the download route does
- Reads the licence key from a POST body.
- Calls the payment provider's public licence activation endpoint. No secret is needed for that call.
- Maps the product the key belongs to onto a file.
- Streams the file, which is bundled into the Worker rather than served as an asset, so no URL reaches it without a key.
Each download uses one activation, so a key posted publicly stops working after a few uses, and a refunded key fails. Licence key gated downloads covers the key side in more detail.
Keep it narrow
The value of this shape is that it stays small: no database, no secrets in the build, nothing to patch at 2am. Every new route is a reason to reconsider, because each one turns a static site back into a server to maintain.
The pages around that route, including the order page and the download form, are built in Warm Storefront UI, the design system of this store.
Questions
Can a Cloudflare Worker serve static files and an API?
Yes. Workers static assets serves your built files, and the Worker script handles requests you route to it. With run_worker_first set to a path pattern, only matching requests run your code.
Does every page request run the Worker?
Not with this setup. Requests that match a static asset are served directly. Setting run_worker_first to a list like /api/* makes only those paths go through your script first.
Where do files live if R2 is not enabled?
Small files can be bundled into the Worker with a Data module rule and imported as ArrayBuffers. That suits kilobyte-sized downloads, not large media.