feat: add WebSub support for real-time feed updates

Advertise WebSub hub (websubhub.com) in three discovery layers:
- HTML <link rel="hub"> in page head
- <atom:link rel="hub"> in RSS feed
- "hubs" array in JSON Feed 1.1

Notify hub after each Eleventy build so subscribers receive
push updates when new content is published.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ricardo
2026-02-05 16:34:37 +01:00
parent 71c314152c
commit c17ca030c8
4 changed files with 30 additions and 2 deletions

View File

@@ -65,6 +65,7 @@
<link rel="token_endpoint" href="{{ site.url }}/auth/token"> <link rel="token_endpoint" href="{{ site.url }}/auth/token">
<link rel="micropub" href="{{ site.url }}/micropub"> <link rel="micropub" href="{{ site.url }}/micropub">
<link rel="microsub" href="{{ site.url }}/microsub"> <link rel="microsub" href="{{ site.url }}/microsub">
<link rel="hub" href="https://websubhub.com/hub">
<link rel="webmention" href="https://webmention.io/{{ site.webmentions.domain }}/webmention"> <link rel="webmention" href="https://webmention.io/{{ site.webmentions.domain }}/webmention">
<link rel="pingback" href="https://webmention.io/{{ site.webmentions.domain }}/xmlrpc"> <link rel="pingback" href="https://webmention.io/{{ site.webmentions.domain }}/xmlrpc">

View File

@@ -404,8 +404,9 @@ export default function (eleventyConfig) {
.slice(0, 5); .slice(0, 5);
}); });
// Pagefind indexing after each build // Pagefind indexing + WebSub hub notification after each build
eleventyConfig.on("eleventy.after", ({ dir, runMode }) => { eleventyConfig.on("eleventy.after", async ({ dir, runMode }) => {
// Pagefind indexing
try { try {
console.log(`[pagefind] Indexing ${dir.output} (${runMode})...`); console.log(`[pagefind] Indexing ${dir.output} (${runMode})...`);
execFileSync("npx", ["pagefind", "--site", dir.output, "--glob", "**/*.html"], { execFileSync("npx", ["pagefind", "--site", dir.output, "--glob", "**/*.html"], {
@@ -416,6 +417,25 @@ export default function (eleventyConfig) {
} catch (err) { } catch (err) {
console.error("[pagefind] Indexing failed:", err.message); console.error("[pagefind] Indexing failed:", err.message);
} }
// WebSub hub notification — notify subscribers of feed updates
const hubUrl = "https://websubhub.com/hub";
const feedUrls = [
`${siteUrl}/feed.xml`,
`${siteUrl}/feed.json`,
];
for (const feedUrl of feedUrls) {
try {
const res = await fetch(hubUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `hub.mode=publish&hub.url=${encodeURIComponent(feedUrl)}`,
});
console.log(`[websub] Notified hub for ${feedUrl}: ${res.status}`);
} catch (err) {
console.error(`[websub] Hub notification failed for ${feedUrl}:`, err.message);
}
}
}); });
return { return {

View File

@@ -7,6 +7,12 @@ eleventyExcludeFromCollections: true
"title": "{{ site.name }}", "title": "{{ site.name }}",
"home_page_url": "{{ site.url }}/", "home_page_url": "{{ site.url }}/",
"feed_url": "{{ site.url }}/feed.json", "feed_url": "{{ site.url }}/feed.json",
"hubs": [
{
"type": "WebSub",
"url": "https://websubhub.com/hub"
}
],
"description": "{{ site.description }}", "description": "{{ site.description }}",
"language": "{{ site.locale | default('en') }}", "language": "{{ site.locale | default('en') }}",
"authors": [ "authors": [

View File

@@ -10,6 +10,7 @@ eleventyExcludeFromCollections: true
<description>{{ site.description }}</description> <description>{{ site.description }}</description>
<language>{{ site.locale | default('en') }}</language> <language>{{ site.locale | default('en') }}</language>
<atom:link href="{{ site.url }}/feed.xml" rel="self" type="application/rss+xml"/> <atom:link href="{{ site.url }}/feed.xml" rel="self" type="application/rss+xml"/>
<atom:link href="https://websubhub.com/hub" rel="hub"/>
<lastBuildDate>{{ collections.feed | getNewestCollectionItemDate | dateToRfc822 }}</lastBuildDate> <lastBuildDate>{{ collections.feed | getNewestCollectionItemDate | dateToRfc822 }}</lastBuildDate>
{%- for post in collections.feed %} {%- for post in collections.feed %}
{%- set absolutePostUrl = site.url + post.url %} {%- set absolutePostUrl = site.url + post.url %}