Error: No such module "fs" appeared in Cloudflare Workers Logs for the first real request to /legal/terms, which returned HTTP 500. Local Node.js runs never produced the same clue:
GET /legal/terms -> 500
Uncaught Error: No such module "fs"
at node_modules/gray-matter/lib/utils.js
at renderTermsPage
The misleading part was that nothing failed early. npm test ran on Node.js 20. The CI smoke test touched /health and /api/session, neither of which rendered Markdown. wrangler deploy accepted the bundle because the bad API was behind a helper call, not a top-level import that the bundler had to execute.
The production-only request did this:
const page = matter.read("./content/terms.md");
That line is convenient in a server process with a disk. In a Worker isolate, it asks a package to cross the edge boundary and open a local file at request time. There is no local project tree there, and in this deployment there was not even a Node fs module because compatibility_flags = ["nodejs_compat"] was absent.
Adding nodejs_compat would have changed the error shape, but it would not have made request-time disk reads a good deployment contract. The durable fix was to move the file boundary to build time:
import rawTermsMarkdown from "../content/terms.md?raw";
const page = matter(rawTermsMarkdown);
After that change, the Worker receives ordinary JavaScript text in its bundle. gray-matter still parses frontmatter, but the edge runtime no longer has to pretend to be a Node server with a filesystem.