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 a 404. Not the HTML shell with a 200.
  • An unknown API route, /api/nothing, must return JSON with a 404 from Hono’s notFound.
  • An unknown browser route can fall back to index.html if you use client-side routing. Link Vault doesn’t, so the default 404 is 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