Skip to content

v0.2.3

Choose a tag to compare

@goddtriffin goddtriffin released this 26 Sep 22:34
· 2 commits to main since this release
b8af856

Adds a feed kit: a site may declare one content stream, served as RSS 2.0,
Atom 1.0 and JSON Feed 1.1 at /rss.xml, /atom.xml and /feed.json.

A feed is not a search ranking signal, and this does not pretend otherwise. What
it buys is real but narrower: Google accepts an RSS or Atom file as a sitemap
and treats it as the recency signal beside the full inventory, and syndication to
readers and aggregators produces inbound links, which are.

Feeds

  • WebServer::feed is omittable — a site with no stream serves no feed
    documents and emits no autodiscovery links, so existing sites are untouched.
    Calling it without frontend fails the boot rather than silently serving
    nothing.
  • Entries are supplied, not derived: a blog is a dynamic_page_group, whose
    template data is built per request, so there is nothing to read at boot.
    Supply them in any order and any number — sorted newest-first, truncated to 20.
  • Channel metadata (author, language, icons, copyright, self and alternate
    links) is derived from what the site already declares.
  • FeedEntry is a named-field struct, so shipping a teaser feed is something you
    type (content_html: None) rather than something you forget.

Correctness

  • Content URLs are rewritten, not copied: cache-busted, then absolutised, with a
    bare #fragment resolved against the post. A reader renders your entry on
    feedly.com, where every relative URL is broken. Rewriting is confined to real
    elements, so a post that shows markup in a code fence is left alone;
    srcset fails the boot rather than being half-rewritten.
  • Characters outside XML 1.0's Char production are stripped, not escaped — no
    escape exists for them, and one 0x0B rejects an entire feed on a validating
    parser.
  • Documents are byte-stable: dates come from the newest entry, never the
    clock, and ordering ties break on path. A redeploy that changed no post
    re-sends nothing to any subscriber.

Serving and discovery

  • Feeds bypass the never-cache layer, which strips ETag/If-None-Match and
    stamps no-store — wrong for the most-polled document a site serves. They
    carry public, max-age=1800, a boot-computed strong ETag and
    Last-Modified, and answer a conditional request with a bodiless 304.
  • Three rel="alternate" links on every page, ordered RSS, Atom, JSON.
  • robots.txt names only /atom.xml, between the sitemap index and the chunk
    lines — Atom has a real per-entry <updated>, RSS carries publication dates
    only, and JSON Feed is not a supported sitemap format.

Also

  • Every resource and its cache tier is named once at boot, logical path to
    hashed path, so a tier that failed to wire up is visible in the first screen
    of a deploy's logs.
  • Pages::paths, so a feed advertising an undeclared page fails the boot rather
    than linking a 404.
  • bin/, cache-buster.json, static/script/generated/ and .DS_Store are
    excluded from the Docker build context, so a host-generated manifest cannot
    reach the image and feed the JS build a stale one.

Upgrading

No consuming project needs to change anything. Frontend::build gained a
parameter and FrontendRuntime, WellKnown and TemplateData each gained a
field, so the public surface did move — but all of them are constructed by the
library and read by the layout, and no project builds one. Kept a patch bump
deliberately so existing 0.2.x pins pick the feed kit up without an edit.

The TypeScript library is untouched and stays at 0.1.1.

Full Changelog: v0.2.2...v0.2.3