Feeds start moving off the build

Every social, community and live-status refresh used to commit a file and rebuild the whole site. The first two pieces of the replacement are in: one renderer for feed cards shared by the build and the Worker, and a Worker endpoint that serves the latest feed snapshots with a safe fallback.

Three scheduled jobs keep this site’s feeds current: the social feed every hour, the Mello Zone community articles every six hours and the Twitch live status every ten minutes. Each one ran the full test suite, built the site, committed its data file to the repository and waited for Cloudflare to deploy the result. A feed update was a site release.

That coupling has a cost beyond build minutes. On 24 October an unrelated lockfile problem stopped Cloudflare from deploying anything, and the same failing checks stopped every feed job before it could commit, so the feeds froze along with the code. The live status page cannot be more current than a build either, however often it is polled.

The plan is to keep the latest snapshot of each feed in Workers KV and have the site’s Worker render it into pages as they are served. This release lands the first two pieces. Nothing a visitor sees changes yet.

One renderer for feed cards

The social cards on the home and work pages and the Mello Zone cards on the community page were rendered by two build-time modules. The Worker will need to render the same cards from newer data, and two copies of that markup would drift. The markup now lives once, in src/lib/feed-render.js, which has no file-system or network code so both the build and the Worker can use it, and the build modules call it.

The move changes no output. The site was built before and after it, and all 262 files in the build are byte-for-byte identical.

A read path for the latest snapshots

The Worker gains a FEEDS KV namespace and an endpoint at /api/feeds/social, /api/feeds/community and /api/feeds/live. It serves the newest snapshot from KV, and says so in an x-feed-source: kv header.

A snapshot from KV will be rendered into public pages without a build in between, so it is held to the same rules the build applies before it is served: a supported version, a real and non-future generation date, at least one item, HTTPS links, a known platform for social posts, Mello Zone as the origin of community articles, images either cached on this site or, for community articles, linked from Mello Zone, with dimensions, and a true-or-false live flag. A snapshot that fails any of them is logged and ignored.

When KV is empty, unreachable, not bound, or holding a snapshot that fails those rules, the endpoint serves the snapshot packaged in the build instead, labelled x-feed-source: build. It answers 503 only if neither is usable. Responses carry an ETag and answer a matching If-None-Match with 304, are cached for 60 seconds (social), 5 minutes (community) or 30 seconds (live), and are marked noindex.

Until the pollers are switched over in the next part, KV is empty and the endpoint serves the packaged snapshots.

Verification

  • A build before the renderer moved and a build after it were compared file by file: all 262 files identical.
  • tools/test-feeds-api.mjs, new in npm run test:worker, covers serving from KV, the fallback for empty, unreachable and unbound KV, eight kinds of snapshot that must be refused, a community article from another origin, a non-boolean live flag, 304 revalidation, HEAD, and the 405, 404 and 503 answers. Disabling the social item checks on purpose made it fail, as it should.
  • npm run test:delivery 187 of 187, npm run build 40 pages, npm run test:worker 84 of 84, npm run test:built 293 of 293, the release audit across 39 canonical pages, and npm run check:feeds. npx wrangler deploy --dry-run resolves the FEEDS binding.
  • Against npm run preview, with local KV: all three feeds answered from the build while KV was empty, a seeded social snapshot was served from KV, and a seeded snapshot with a plain-HTTP link was refused in favour of the build. npm run test:browser reported 0 failures across 39 routes and npm run test:a11y 0 failures across 88 checks.
  • tools/indexing-surface.json was rewritten deliberately; its only change is this entry’s route.