Routing and configuration
Serve static assets
Attach the Link Vault browser interface as Worker static assets while keeping API routes and asset fallback behavior explicit.
Link Vault needs a web page: a form to save a link and a list of saved links. A Worker can ship those files together with the API, so one deployment contains both. No separate hosting, no CORS between two origins.
Create a public folder with an index.html, an app.js, and a style.css. Then tell Wrangler about it:
{
"assets": {
"directory": "./public",
"binding": "ASSETS",
"run_worker_first": ["/api/*"]
}
}
directory is what gets uploaded. binding gives your code an env.ASSETS.fetch() you can call by hand if needed. run_worker_first lists the paths that must reach your Worker before the asset layer looks at them.
Decide who answers first
By default, Cloudflare checks the assets folder first. If a file matches the path, it’s served and your Worker never runs. If nothing matches, the request falls through to your fetch handler.
That default is fast, but it has a trap. Without run_worker_first, a request to /api/health first looks for a file called api/health. There isn’t one, so it falls through and works. Until someone drops a file with that name in public. Listing /api/* makes the contract explicit.
Write the routing contract down
Three cases need a decision, and I want you to test all three, not just the homepage:
- A missing JavaScript file, say
/app-old.js, must be a404. Not the HTML shell with a200. - An unknown API route,
/api/nothing, must return JSON with a404from Hono’snotFound. - An unknown browser route can fall back to
index.htmlif you use client-side routing. Link Vault doesn’t, so the default404is fine.
The not_found_handling option controls that last case. Leave it out unless you have a real single-page app.
Cache HTML and hashed files differently
HTML should update fast, because it points to the current script and style filenames. Fingerprinted files like app.3f9a1c.js can be cached for a very long time. When the content changes, the filename changes, so there is nothing stale to worry about.
This is also why shipping HTML and assets in one deployment matters. A rollback restores a matching pair. Serve them from two places and a rollback can leave HTML pointing at scripts that no longer exist.
Verify with curl
Run npm run dev and compare the two entry points:
curl -i http://localhost:8787/
curl -i http://localhost:8787/api/health
The first shows content-type: text/html and your page. The second shows content-type: application/json and {"ok":true}. If both return HTML, run_worker_first isn’t in place yet.
Now build the real interface in public: a form with labeled URL and title fields, plus a list that fetches /api/links and renders it. Use real <label> elements and a <button type="submit">. Bind the folder, run the two curl commands, and request a file that doesn’t exist to confirm the 404.
Lesson completed