Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions public/.well-known/api-catalog
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
"title": "Changelog feed"
}
],
"hub": [
{
"href": "https://specification.website/websub",
"title": "WebSub hub for this site's feeds"
}
],
"sitemap": [
{
"href": "https://specification.website/sitemap-index.xml",
Expand Down
18 changes: 7 additions & 11 deletions public/_headers
Original file line number Diff line number Diff line change
Expand Up @@ -64,23 +64,19 @@
Content-Type: text/javascript; charset=utf-8
Cache-Control: public, max-age=600, must-revalidate

# Feeds — WebSub discovery. HELD BACK UNTIL THE HUB IS LIVE: advertising
# rel="hub" while /websub cannot accept subscriptions is the exact failure
# /spec/foundations/websub/ warns about (an advertisement is not a delivery
# mechanism), so these blocks are uncommented in the same commit that
# provisions the D1 database — see the checklist in wrangler.toml.
# Feeds — WebSub discovery.
#
# rel="hub" names the hub that pushes updates for this topic; rel="self" is the
# topic's canonical URL, which is the identity subscribers key on. Subscribers
# must check Link headers before embedded elements (WebSub §4), so this is the
# authoritative copy and the atom:link elements inside each feed are the
# fallback. Keep all three in sync with ALLOWED_TOPICS in functions/websub.ts.
#
# /rss.xml
# Link: </websub>; rel="hub", <https://specification.website/rss.xml>; rel="self"
#
# /changelog/rss.xml
# Link: </websub>; rel="hub", <https://specification.website/changelog/rss.xml>; rel="self"

/rss.xml
Link: </websub>; rel="hub", <https://specification.website/rss.xml>; rel="self"

/changelog/rss.xml
Link: </websub>; rel="hub", <https://specification.website/changelog/rss.xml>; rel="self"

# Long cache for fingerprinted assets
/_astro/*
Expand Down
4 changes: 3 additions & 1 deletion src/content/spec/foundations/websub.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ status: optional
order: 130
appliesTo: [all]
relatedSlugs: [feed-discovery, feed-hygiene, canonical-url, link-headers]
updated: "2026-07-30T00:00:00.000Z"
updated: "2026-07-31T00:00:00.000Z"
sources:
- title: "WebSub — W3C Recommendation, 2 June 2026"
url: "https://www.w3.org/TR/websub/"
Expand Down Expand Up @@ -62,6 +62,8 @@ Or, inside an Atom feed:

**4. Keep the feed correct anyway.** WebSub is additive. Subscribers that do not implement it keep polling, so [feed hygiene](/spec/foundations/feed-hygiene/) — stable GUIDs, valid markup, correct `Last-Modified` and `ETag` — still governs how well those clients behave. The push path carries the same feed body, so a malformed feed is malformed for everyone.

**This site ships it.** Both [`/rss.xml`](/rss.xml) and [`/changelog/rss.xml`](/changelog/rss.xml) advertise `rel="hub"` and `rel="self"` on the response and inside the feed, pointing at a hub we run ourselves at `/websub`. It is deliberately a *self-hub*: `hub.topic` is allowlisted to those two feeds, so the endpoint cannot be pointed at arbitrary URLs or made to relay anyone else's content. An open hub is a public service with a different risk profile, and running one is not a prerequisite for the advice above — pointing at a hosted hub is the ordinary choice.

## Common mistakes

- Advertising `rel="hub"` without pinging the hub on publish. Nothing is ever distributed, and nothing errors.
Expand Down
6 changes: 3 additions & 3 deletions src/pages/changelog/rss.xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export async function GET(context: APIContext) {
customData: [
"<language>en-GB</language>",
`<atom:link href="${feedUrl}" rel="self" type="application/rss+xml" />`,
// WebSub hub for this topic — held back until the hub is live, along with
// the Link header in public/_headers. See the checklist in wrangler.toml.
// `<atom:link href="${base}/websub" rel="hub" />`,
// WebSub hub for this topic. Fallback copy of the Link header in
// public/_headers — subscribers check headers first (WebSub §4).
`<atom:link href="${base}/websub" rel="hub" />`,
`<lastBuildDate>${lastBuild.toUTCString()}</lastBuildDate>`,
"<sy:updatePeriod>weekly</sy:updatePeriod>",
"<sy:updateFrequency>1</sy:updateFrequency>",
Expand Down
6 changes: 3 additions & 3 deletions src/pages/rss.xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export async function GET(context: APIContext) {
customData: [
"<language>en-GB</language>",
`<atom:link href="${feedUrl}" rel="self" type="application/rss+xml" />`,
// WebSub hub for this topic — held back until the hub is live, along with
// the Link header in public/_headers. See the checklist in wrangler.toml.
// `<atom:link href="${new URL("/websub", siteUrl).toString()}" rel="hub" />`,
// WebSub hub for this topic. Fallback copy of the Link header in
// public/_headers — subscribers check headers first (WebSub §4).
`<atom:link href="${new URL("/websub", siteUrl).toString()}" rel="hub" />`,
`<lastBuildDate>${lastBuild.toUTCString()}</lastBuildDate>`,
"<sy:updatePeriod>weekly</sy:updatePeriod>",
"<sy:updateFrequency>1</sy:updateFrequency>",
Expand Down
Loading