A zero-cost RSS aggregation pipeline that runs entirely on GitHub Actions + GitHub Pages.
This repository fetches multiple RSS feeds, deduplicates and sorts articles, then generates both a static HTML website and a unified RSS feed.
No servers, databases, cron jobs, or paid services required.
The current demo tracks gamification-related news, but the repository is intentionally designed to be forked for any niche or topic.
Many RSS aggregation and monitoring solutions rely on hosted infrastructure or paid plans for advanced features such as feed merging, filtering, and monitoring.
The goal of this project is to provide an alternative that:
- runs entirely on the GitHub free tier
- requires zero infrastructure maintenance
- is easy to fork and customize
- outputs static files instead of relying on a backend
RSS feeds
↓
GitHub Action (scheduled)
↓
GitHub Actions Cache (restores/saves feed_cache.json)
↓
config.py (typed configuration bridge)
↓
domain models (Article, FeedSource, OutputConfig)
↓
Python aggregation pipeline (app.py)
↓
stream processing (filtering, deduplication, sorting)
↓
Jinja template rendering
↓
Static HTML + RSS output
↓
GitHub Pages
No VPS, Docker, Redis, Postgres, or external schedulers.
Everything runs on GitHub's free tier:
- GitHub Actions → scheduled updates
- GitHub Pages → static hosting
- Python → aggregation pipeline
- Jinja → static rendering
No backend services required.
Unlike naive scrapers that load all data into memory, this pipeline processes articles as a continuous stream using Python generators (yield). This reduces intermediate memory usage by processing articles through a generator-based pipeline before final collection and rendering.
The pipeline is structured as a domain-driven streaming system using typed models (Article, FeedSource) to ensure consistency across ingestion, processing, and rendering stages.
The pipeline utilizes Python's ThreadPoolExecutor to fetch and process multiple RSS feeds concurrently. Since feed retrieval is primarily I/O-bound, concurrent requests can significantly improve throughput despite the presence of Python's GIL.
Instead of waiting for each feed to download sequentially, multiple remote sources are fetched concurrently to reduce overall execution time. Additionally, the worker pool size dynamically auto-tunes based on your source count to prevent resource wasting or accidental rate-limiting.
The deduplication layer normalizes URLs to avoid duplicate articles across feeds.
It handles:
- trailing slashes
- casing normalization
- tracking query parameters
- duplicate Google News links
All timestamps are normalized into timezone-aware datetime objects using feedparser's native RFC-compliant time parsing and standard Python libraries. This ensures consistent chronological ordering across heterogeneous RSS and Atom feeds while keeping external dependencies to a strict minimum.
To reduce unnecessary network traffic and avoid repeatedly downloading unchanged feeds, the pipeline implements an automated HTTP caching layer using ETag and Last-Modified headers.
Instead of downloading full XML payloads on every single run, the workflow leverages GitHub Actions' native encrypted cache storage to persist a lightweight feed_cache.json state. On subsequent runs, conditional HTTP requests are made: if a source hasn't published new content, the remote server returns a lightweight 304 Not Modified empty packet. This can substantially reduce bandwidth usage when feeds support conditional HTTP requests and reduces the number of requests sent to upstream feed providers.
The pipeline generates:
- a static HTML frontend
- a unified RSS XML feed
This makes the project usable both as a standalone website and as a source for external RSS readers.
- Static website: https://jackydangelo.github.io/staticfeed/
- RSS feed: https://jackydangelo.github.io/staticfeed/rss.xml
You can fork this repository to monitor any niche:
- AI
- cybersecurity
- startups
- indie hacking
- Rust
- gaming
- finance
- scientific research
- custom Google News searches
Click the Fork button on GitHub.
Open config.json and update the general settings or add your own RSS sources to the SOURCE_RSS array:
{
"DAYS_LIMIT": 7,
"REQUEST_TIMEOUT": 20,
"TIMEZONE_STR": "Europe/Rome",
"URL": "https://your-username.github.io/staticfeed/",
"PAGE_TITLE": "My Custom News",
"FOOTER_TEXT": " have forked this repository!",
"SOURCE_RSS": [
{
"url": "https://news.google.com/rss/search?q=your_keyword",
"label": "Google News",
"keyword": "your_keyword"
}
]
}Go to:
Settings → Pages
Then:
- Source → Deploy from a branch
- Branch →
main - Folder →
/docs
Save the configuration.
Open the Actions tab and enable workflows.
The pipeline will now automatically refresh based on the cron schedule defined in:
.github/workflows/update.yml
Typical GitHub Action execution:
- dozens of RSS feeds
- hundreds of aggregated articles
- a few seconds runtime
- static output only
.github/workflows/update.yml → Scheduled GitHub Action
config.json → RSS source configuration
templates/homepage.html → HTML homepage template
templates/sources.html → HTML sources template
templates/rss.xml → RSS template
docs/index.html → Generated static frontend
docs/sources.html → Generated static page about sources
docs/rss.xml → Generated RSS feed
docs/static/style.css → Frontend styling
app.py → Aggregation pipeline
config.py → Configuration loader (Python bridge)
domain.py → Domain models (Article, FeedSource, OutputConfig)
requirements.txt → Python dependencies
Note on Caching: You won't see a
feed_cache.jsonfile in this repository. To keep the git history clean and avoid noise, the HTTP cache state is stored and managed invisibly using GitHub Actions native cache infrastructure. It is restored dynamically at the beginning of each run and saved at the end.
The project intentionally prioritizes:
- simplicity
- portability
- inspectability
- forkability
- zero maintenance
- minimal external dependencies (relying on standard Python libraries wherever possible)
This project is not intended to replace full-featured RSS platforms like Feedly, Inoreader, or Miniflux.
Instead, it is designed to complement them. The unified rss.xml feed generated by this pipeline is fully standard-compliant and ready to be added directly to Feedly, Inoreader, or any other RSS reader.
This allows you to use StaticFeed as a lightweight, zero-cost "pre-processing layer" to aggregate, filter, and deduplicate noisy web sources before reading them in your favorite RSS client.
| Keywords | URL feed | Source |
|---|---|---|
| gamification | https://news.google.com/rss/search?q=gamification | Google news |
| gamification | https://news.google.com/rss/search?q=gamification&hl=it&gl=IT&ceid=IT:it | Google news Italia |
| gamified | https://news.google.com/rss/search?q=gamified | Google news |
| gamificata OR gamificato | https://news.google.com/rss/search?q=%22gamificata%22+OR+%22gamificato%22&hl=it&gl=IT&ceid=IT:it | Google news Italia |
| gamification | https://www.reddit.com/search.rss?q=gamification | |
| gamification | https://hnrss.org/newest?q=gamification | Hacker News |
Pull Requests are welcome, especially for:
- performance improvements
- additional feed normalization strategies
- UI enhancements
- better deduplication logic
- caching support
Please open an issue first.
Open source and free to fork, modify, and reuse.