Repository: expressjs/expressjs.com
Pull request #1867: feat: add feeds for the blog and vulnerabilities
Author: login: rowanmanning
Created: 2025-04-11T09:57:39Z | Merged: 2025-04-15T03:10:04Z

This adds two new feeds to the site: `feed.xml` includes all blog posts, and `vulnerabilities.xml` includes all posts tagged with `vulnerabilities`.

You can validate the feeds via the [W3C Feed Validation Service](https://validator.w3.org/feed/). I also tried subscribing via Feedbin and all the posts appear as expected.

Resolves #1763

<!--
Thank you for your pull request. Please provide a description and 
note the Certificate of Origin below. 

-->

<!--
Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Linked issue #1763: We should generate a feed.xml
As the title says, we should generate a feed.xml file for the blog. We will probably need to split it into two files, one for vulnerabilities (@expressjs/security-wg) and another for the blog in general
Linked issue comments: user: login: rowanmanning
created_at: 2025-04-01T10:56:10Z
body: I'm interested in picking this up. I tried to subscribe via RSS today and would like to be able to! I have some questions:

  * Do you have a preference between RSS and ATOM as a feed format? Both are widely (universally?) supported by readers, I slightly prefer ATOM but happy to take direction

  * Should the feed include the full post content, just a snippet, or just a link to the post on the website?

  * Should the feed have a limited number of items? E.g. the most recent 10 posts? I'd be tempted to add this later _if_ the number of posts becomes an issue (and it also maybe depends on whether we have full post content in the feed).

  * When you say a separate feed for vulnerabilities I assume you mean a feed _only_ for posts with the `vulnerabilities` tag? If so, should vulnerability-related posts still appear in the main feed as well or should they be fully split? I'd be tempted to still include them in the main feed myself but your opinions might differ!

  * In terms of paths, `feed.xml` for the main feed and `vulnerabilities.xml` for the vulnerabilities feed OK? I'd link to both via `link rel="alternate"` on the home page.

  * Discoverability: where do we want to link to the feeds from, aside from `link rel="alternate"` in the `<head>`?

---

I'm also happy to just make a call on all of the above ☝ sometimes easier to make decisions like this in review.
author_association: CONTRIBUTOR
user: login: bjohansebas
created_at: 2025-04-11T01:12:24Z
body: Oh, this got lost in my notifications, sorry for the delay!

> Do you have a preference between RSS and ATOM as a feed format? Both are widely (universally?) supported by readers, I slightly prefer ATOM but happy to take direction

i don’t have any preferences, not sure about the other members, but Atom is fine by me

> Should the feed include the full post content, just a snippet, or just a link to the post on the website?

I’d say just the link, the important thing is to make the blog content get shared more quickly.

> Should the feed have a limited number of items? E.g. the most recent 10 posts? I'd be tempted to add this later if the number of posts becomes an issue (and it also maybe depends on whether we have full post content in the feed).

I don’t think we have too many blogs, so I don’t see much sense in having a limit. The blog is currently for Express updates. There are ideas for more content on the blog, but they haven’t materialized yet.

> When you say a separate feed for vulnerabilities I assume you mean a feed only for posts with the vulnerabilities tag? If so, should vulnerability-related posts still appear in the main feed as well or should they be fully split? I'd be tempted to still include them in the main feed myself but your opinions might differ!

A different feed for vulnerabilities, but those posts should still be in the main feed

> In terms of paths, feed.xml for the main feed and vulnerabilities.xml for the vulnerabilities feed OK? I'd link to both via link rel="alternate" on the home page.

Sounds good to me, we can discuss it better in the PR.

> Discoverability: where do we want to link to the feeds from, aside from link rel="alternate" in the <head>?

Other blogs have buttons to share content on social media, we probably need one too, but that would be another issue.

![Image](https://github.com/user-attachments/assets/da5a9cb7-f6a7-47f1-8167-af3eea47e605)

author_association: MEMBER
user: login: rowanmanning
created_at: 2025-04-11T10:11:27Z
body: [First pass in this PR](https://github.com/expressjs/expressjs.com/pull/1867)
author_association: CONTRIBUTOR

Diff:
diff --git a/_includes/feed-entry.xml b/_includes/feed-entry.xml
new file mode 100644
index 0000000000..d95472797d
--- /dev/null
+++ b/_includes/feed-entry.xml
@@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
+  <link href="{{ site.url }}{{ entry.url }}" rel="alternate" type="text/html" />
+  <title type="text">{{ entry.title }}</title>
+  {%- if entry.author %}
+  <author>
+    <name>{{ entry.author }}</name>
+  </author>
+  {%- endif %}
+  <published>{{ entry.date | date_to_xmlschema }}</published>
+  <updated>{{ entry.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
+  {%- for tag in post.tags %}
+  <category term="{{ tag | xml_escape }}" />
+  {%- endfor %}
+  <content type="html"><![CDATA[ <a href="{{ site.url }}{{ entry.url }}">Read {{ entry.title }}</a> ]]></content>
+</entry>
diff --git a/_includes/head.html b/_includes/head.html
index 44c2310b5d..4fd7214096 100644
--- a/_includes/head.html
+++ b/_includes/head.html
@@ -47,4 +47,6 @@
     <script data-cfasync="false" defer src="/js/menu.js"></script>
     <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css" />
 
+    <link rel="alternate" type="application/atom+xml" href="/feed.xml" title="Express Blog" />
+    <link rel="alternate" type="application/atom+xml" href="/vulnerabilities.xml" title="Express Vulnerabilities" />
 </head>
diff --git a/_layouts/feed.xml b/_layouts/feed.xml
new file mode 100644
index 0000000000..09a7aab788
--- /dev/null
+++ b/_layouts/feed.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+  <title type="text">{{ page.title }}</title>
+  <link href="{{ site.url }}{{ page.html_url }}" rel="alternate" type="text/html" />
+  <link href="{{ site.url }}{{ page.url }}" rel="self" type="application/atom+xml" />
+  <id>{{ site.url }}{{ page.url }}</id>
+  <rights>Copyright The Express Contributors</rights>
+  <updated>{{ site.time | date_to_xmlschema }}</updated>
+  {{ content }}
+</feed>
diff --git a/_posts/2024-09-29-security-releases.md b/_posts/2024-09-29-security-releases.md
index e8f2378db5..bff156d6b7 100644
--- a/_posts/2024-09-29-security-releases.md
+++ b/_posts/2024-09-29-security-releases.md
@@ -1,7 +1,7 @@
 ---
 title: September 2024 Security Releases
 description: Security releases for Express, body-parser, send, serve-static, and path-to-regexp have been published. We recommend that all users upgrade as soon as possible.
-tags: security, vulnerabilities
+tags: security vulnerabilities
 author: Ulises Gascón
 ---
 
diff --git a/_posts/2024-10-01-HeroDevs-partnership-announcement.md b/_posts/2024-10-01-HeroDevs-partnership-announcement.md
index 7b69a4bbf0..3f053e6ee0 100644
--- a/_posts/2024-10-01-HeroDevs-partnership-announcement.md
+++ b/_posts/2024-10-01-HeroDevs-partnership-announcement.md
@@ -1,7 +1,7 @@
 ---
 title: Express Never Ending Support Launched by HeroDevs and Express.js
 description: The Express.js team is pleased to announce a partnership with HeroDevs to launch Express Never-Ending Support (NES), providing long-term support for applications built with legacy Express. This collaboration ensures that developers relying on older versions of the framework will continue to receive critical security and compatibility updates, allowing them to maintain and scale their applications securely, even after the framework's official end-of-life.
-tags: partnerships, announcements
+tags: partnerships announcements
 author: Express Technical Committee
 ---
 
diff --git a/_posts/2024-10-22-security-audit-milestone-achievement.md b/_posts/2024-10-22-security-audit-milestone-achievement.md
index 115f4e7d6a..66677917f6 100644
--- a/_posts/2024-10-22-security-audit-milestone-achievement.md
+++ b/_posts/2024-10-22-security-audit-milestone-achievement.md
@@ -1,6 +1,6 @@
 ---
 title: "Express.js Security Audit: A Milestone Achievement"
-tags: security, audit, releases
+tags: security audit releases
 author: Express Technical Committee
 description: Celebrating the successful completion of the Express.js security audit conducted by Ada Logics and facilitated by OSTIF.
 ---
diff --git a/_posts/2025-01-09-rewind-2024-triumphs-and-2025-vision.md b/_posts/2025-01-09-rewind-2024-triumphs-and-2025-vision.md
index 93d0e4b454..72024ef918 100644
--- a/_posts/2025-01-09-rewind-2024-triumphs-and-2025-vision.md
+++ b/_posts/2025-01-09-rewind-2024-triumphs-and-2025-vision.md
@@ -1,6 +1,6 @@
 ---
 title: "A New Chapter for Express.js: Triumphs of 2024 and an ambitious 2025"
-tags: news, rewind, 2024
+tags: news rewind 2024
 author: Express Technical Committee
 description: Explore the transformative journey of Express.js in 2024, marked by governance improvements, the long-awaited release of Express 5.0, and heightened security measures. Look into the ambitious plans for 2025, including performance optimizations, scoped packages, and a bold roadmap for sustained growth in the Node.js ecosystem.
 ---
diff --git a/_posts/2025-03-31-v5-1-latest-release.md b/_posts/2025-03-31-v5-1-latest-release.md
index 809dd6143b..554f961e2a 100644
--- a/_posts/2025-03-31-v5-1-latest-release.md
+++ b/_posts/2025-03-31-v5-1-latest-release.md
@@ -1,6 +1,6 @@
 ---
 title: "Express@5.1.0: Now the Default on npm with LTS Timeline"
-tags: news, release 
+tags: news release
 author: Express Technical Committee
 description: Express 5.1.0 is now the default on npm, and we're introducing an official LTS schedule for the v4 and v5 release lines.
 ---
diff --git a/feed.xml b/feed.xml
new file mode 100644
index 0000000000..d5d8b9fcea
--- /dev/null
+++ b/feed.xml
@@ -0,0 +1,10 @@
+---
+layout: feed
+sitemap: false
+title: Express Blog
+html_url: /en/blog/posts.html
+---
+{% assign posts = site.posts | sort: "date" | reverse %}
+{% for post in posts %}
+{% include feed-entry.xml entry=post %}
+{% endfor %}
diff --git a/vulnerabilities.xml b/vulnerabilities.xml
new file mode 100644
index 0000000000..d76816832b
--- /dev/null
+++ b/vulnerabilities.xml
@@ -0,0 +1,10 @@
+---
+layout: feed
+sitemap: false
+title: Express Vulnerabilities
+html_url: /en/blog/posts.html
+---
+{% assign posts = site.posts | sort: "date" | reverse %}
+{% for post in posts %}
+{% if post.tags contains "vulnerabilities" %}{% include feed-entry.xml entry=post %}{% endif %}
+{% endfor %}


Reviews:
user: login: rowanmanning
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T10:06:10Z
user: login: bjohansebas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T16:47:23Z
user: login: bjohansebas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T16:52:07Z
user: login: bjohansebas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T16:58:13Z
user: login: bjohansebas
body: Overall, everything looks good, @rowanmanning  thanks for working on this.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T16:59:27Z
user: login: rowanmanning
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T16:44:53Z
user: login: rowanmanning
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T16:52:42Z
user: login: rowanmanning
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-13T16:56:09Z
user: login: wesleytodd
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T15:25:10Z
user: login: wesleytodd
body: I think this is great! Thanks!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T15:26:10Z
user: login: rowanmanning
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T15:32:09Z
user: login: bjohansebas
body: LGTM!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-15T02:58:28Z

Review comments:
diff_hunk: @@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
path: _includes/feed-entry.xml
user: login: rowanmanning
body: In Atom, the `id` is [expected to be an IRI](https://www.ietf.org/rfc/rfc4287.html#section-4.2.6) however using the page URL seems to be relatively common practice. An alternative would be to mimic [GitHub's releases feed](https://github.com/expressjs/express/releases.atom) and use something like

```
tag:expressjs.com,<YEAR>:https://expressjs.com/<POST_URL>
```

however this doesn't get around the fact that the ID is based on the page URL.
created_at: 2025-04-11T10:02:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
+  <link href="{{ site.url }}{{ entry.url }}" rel="alternate" type="text/html" />
+  <title type="text">{{ entry.title }}</title>
+  {%- if entry.author %}
+  <author>
+    <name>{{ entry.author }}</name>
+  </author>
+  {%- endif %}
+  <published>{{ entry.date | date_to_xmlschema }}</published>
+  <updated>{{ entry.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
+  {%- for tag in post.tags %}
+  <category term="{{ tag | replace: ',', '' | xml_escape }}" />
path: _includes/feed-entry.xml
user: login: rowanmanning
body: This is overcomplicated to fix an issue with the current posts. [Jekyll post tags](https://jekyllrb.com/docs/posts/#tags-and-categories) are supposed to be space-separated. The tags in the current posts are comma-separated which results in trailing commas appearing in the tags.

I wasn't sure whether to add this little hack or to edit all of the posts to correct the tags. If the latter than I'll be setting the last modified date of each post; happy to do this if you want me to.
created_at: 2025-04-11T10:04:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+  <title type="text">{{ page.title }}</title>
+  <link href="{{ site.url }}{{ page.html_url }}" rel="alternate" type="text/html" />
+  <link href="{{ site.url }}{{ page.url }}" rel="self" type="application/atom+xml" />
+  <id>{{ site.url }}{{ page.url }}</id>
+  <rights>Copyright © {{ site.time | date: '%Y' }}, The Express Contributors</rights>
path: _layouts/feed.xml
user: login: rowanmanning
body: Is this copyright OK? I made assumptions based on the license. It's not a required element so I can ditch it or make changes.
created_at: 2025-04-11T10:05:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+  <title type="text">{{ page.title }}</title>
+  <link href="{{ site.url }}{{ page.html_url }}" rel="alternate" type="text/html" />
+  <link href="{{ site.url }}{{ page.url }}" rel="self" type="application/atom+xml" />
+  <id>{{ site.url }}{{ page.url }}</id>
+  <rights>Copyright © {{ site.time | date: '%Y' }}, The Express Contributors</rights>
path: _layouts/feed.xml
user: login: bjohansebas
body: I don't think we need to include the date — we don't have it in the license of the repo.

Ping @expressjs/express-tc in case they have an opinion on this.
created_at: 2025-04-12T16:47:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
+  <link href="{{ site.url }}{{ entry.url }}" rel="alternate" type="text/html" />
+  <title type="text">{{ entry.title }}</title>
+  {%- if entry.author %}
+  <author>
+    <name>{{ entry.author }}</name>
+  </author>
+  {%- endif %}
+  <published>{{ entry.date | date_to_xmlschema }}</published>
+  <updated>{{ entry.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
+  {%- for tag in post.tags %}
+  <category term="{{ tag | replace: ',', '' | xml_escape }}" />
path: _includes/feed-entry.xml
user: login: bjohansebas
body: It's really better to fix it at the source than to make small hacks
created_at: 2025-04-12T16:52:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
path: _includes/feed-entry.xml
user: login: bjohansebas
body: I like either of those two options, and i think both are fine.
created_at: 2025-04-12T16:58:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
path: _includes/feed-entry.xml
user: login: rowanmanning
body: OK let's just go with the URL 👍 
created_at: 2025-04-13T16:44:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,17 @@
+{%- assign entry = include.entry -%}
+<entry>
+  <id>{{ site.url }}{{ entry.url }}</id>
+  <link href="{{ site.url }}{{ entry.url }}" rel="alternate" type="text/html" />
+  <title type="text">{{ entry.title }}</title>
+  {%- if entry.author %}
+  <author>
+    <name>{{ entry.author }}</name>
+  </author>
+  {%- endif %}
+  <published>{{ entry.date | date_to_xmlschema }}</published>
+  <updated>{{ entry.last_modified_at | default: post.date | date_to_xmlschema }}</updated>
+  {%- for tag in post.tags %}
+  <category term="{{ tag | replace: ',', '' | xml_escape }}" />
path: _includes/feed-entry.xml
user: login: rowanmanning
body: Great 👍 fixed in a separate commit
created_at: 2025-04-13T16:52:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+  <title type="text">{{ page.title }}</title>
+  <link href="{{ site.url }}{{ page.html_url }}" rel="alternate" type="text/html" />
+  <link href="{{ site.url }}{{ page.url }}" rel="self" type="application/atom+xml" />
+  <id>{{ site.url }}{{ page.url }}</id>
+  <rights>Copyright © {{ site.time | date: '%Y' }}, The Express Contributors</rights>
path: _layouts/feed.xml
user: login: rowanmanning
body: I removed the date. I'm unable to @ the team though, are you able to give them a heads up?
created_at: 2025-04-13T16:56:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+  <title type="text">{{ page.title }}</title>
+  <link href="{{ site.url }}{{ page.html_url }}" rel="alternate" type="text/html" />
+  <link href="{{ site.url }}{{ page.url }}" rel="self" type="application/atom+xml" />
+  <id>{{ site.url }}{{ page.url }}</id>
+  <rights>Copyright © {{ site.time | date: '%Y' }}, The Express Contributors</rights>
path: _layouts/feed.xml
user: login: wesleytodd
body: Hm, I don't know much about how to represent this in feeds, but this is the correct guidance to follow if we need to have this at all: https://github.com/openjs-foundation/cross-project-council/blob/main/governance/IP_POLICY_GUIDANCE.md#2-copyright-notices
created_at: 2025-04-14T15:25:10Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+  <title type="text">{{ page.title }}</title>
+  <link href="{{ site.url }}{{ page.html_url }}" rel="alternate" type="text/html" />
+  <link href="{{ site.url }}{{ page.url }}" rel="self" type="application/atom+xml" />
+  <id>{{ site.url }}{{ page.url }}</id>
+  <rights>Copyright © {{ site.time | date: '%Y' }}, The Express Contributors</rights>
path: _layouts/feed.xml
user: login: rowanmanning
body: Ah great, that's all the guidance I need, thanks. I've copied the format exactly from the linked doc 👍
created_at: 2025-04-14T15:32:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-04-11T09:58:01Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *expressjscom-preview* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 45fab4092f55a1701e44607a82f60fd0e2aa2d0f |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/expressjscom-preview/deploys/67fdcca5efc4170008ab4d4c |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-1867--expressjscom-preview.netlify.app](https://deploy-preview-1867--expressjscom-preview.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTE4NjctLWV4cHJlc3Nqc2NvbS1wcmV2aWV3Lm5ldGxpZnkuYXBwIn0.ZUkmnwe3T7llWNjgC3o2ErkzvjDYpI7ruLtPSrAd_Z4)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [expressjscom-preview Preview](https://deploy-preview-1867--expressjscom-preview.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/expressjscom-preview/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: bjohansebas
created_at: 2025-04-15T03:10:19Z
body: @rowanmanning Thanks!
author_association: MEMBER