A GitHub Action that fetches RSS/Atom feeds from an OPML file and builds a static site aggregating all the feed content. This project creates a modern, responsive feed reader as a static site.
This action runs on Node.js 24 (runs.using: node24).
To use it, create a new repository and add an OPML file named feeds.opml with your list of website RSS/Atom feeds. Create a GitHub workflow like the one below to fetch data and store it in a contents branch:
name: Schedule
on:
schedule:
- cron: '0 * * * *'
jobs:
playground:
runs-on: ubuntu-latest
name: Test
steps:
- name: Run Action
uses: llun/feeds@4.0.0
After this, enable GitHub Pages on the contents branch and the content will be available on that page.
The action always reads the OPML file from the branch that triggered the workflow (for example main), then publishes generated output to the configured branch.
Images referenced by feed entries are downloaded while the feeds are loaded and published alongside the site under /media, so the reader never hotlinks a publisher's server. This avoids hotlink protection, mixed content and cross-origin resource policy blocking.
Images that cannot be downloaded keep their original URL and are rendered with referrerpolicy="no-referrer". Because feeds are re-read on every run, a publisher that was temporarily unavailable is picked up again on a later run. SVG images are always kept remote, since a published SVG would be a script running on the site's own origin.
Each run restores the media files of the published branch before downloading, so only images that have not been seen before are fetched, and files no longer referenced by any entry are removed. Schedule only one run at a time (a workflow concurrency group) because the publish step force pushes the whole site.
Hacker News feeds (the official RSS, hnrss, or any mirror) publish entries whose content is only a "Comments" link. For those entries the action fetches the discussion from the HN Algolia API and appends it to the entry, so the reader shows the story text and the top 20 comments, nested 3 levels deep, right below the link. Threads cut short by those caps end with a link to the full discussion on Hacker News, and an entry whose discussion cannot be fetched keeps its original content.
This action can be configured to use a custom domain and different types of storage. Here are the available configuration options:
customDomain: Specifies the custom domain for the feeds site. Required when generating a static site as it's needed to generate theCNAMEfile.branch: Branch where the static site will be generated. The default value iscontents. This is the branch you'll need to point the repository's GitHub Pages to.storageType: (Default isfiles) Content storage type, currently supportsfilesandsqlite.database: Legacy alias that behaves likefilesfiles: Stores all feed contents in a JSON tree structuresqlite: Stores content in a SQLite database that the client will download using HTTP chunks
opmlFile: Name of the OPML file containing the list of sites you want to include in your feed site.
name: Schedule
on:
schedule:
- cron: '0 * * * *'
jobs:
playground:
runs-on: ubuntu-latest
name: Generate Feeds
steps:
- name: Run Action
uses: llun/feeds@4.0.0
with:
storageType: files
opmlFile: site.opml
branch: public