A different kind of RSS reader. Articles arrive, linger, and fade. You are not behind.
Most RSS readers treat your feeds like an inbox: unread counts, mark-as-read, the quiet implication that falling behind is a failure. Stream does something different. It shows your feeds as a river of things that arrived, each one fading at a rate that suits its source. Breaking news fades in hours. A long essay lingers for days. A prolific source cannot drown out a thoughtful one.
There are no unread counts. You are not behind.
Stream is directly inspired by Current by Terry Godier, an RSS reader for iPhone and Mac built around the same idea.
Stream is a frontend only. It does not fetch or store feeds itself. You need one of the following accounts first:
Feedbin — a hosted RSS service. $5/month, no setup required. Sign up, add your feeds, then connect Stream.
FreshRSS — open-source RSS with a free hosted option at freshrss.net, or self-host on your own server. FreshRSS needs a separate API password: set one under Settings → Profile → API management. Your regular login password will not work.
The quickest way to run Stream is to deploy it to Netlify for free:
- Fork this repo to your GitHub account.
- In Netlify, click Add new site → Import an existing project and connect your repo.
- Build settings are read from
netlify.tomlautomatically. Click Deploy. - Open your Netlify URL. Stream asks how it should reach your RSS service (direct, your own proxy, or the bundled Netlify function). Pick one, enter your credentials, and you are done. See Your credentials below for what the three modes mean.
Each feed is assigned a velocity tier. This controls how quickly articles fade:
| Tier | Label | Half-life | Good for |
|---|---|---|---|
| 1 | Breaking | 3h | BBC News, Reuters |
| 2 | News | 12h | Ars Technica, The Verge |
| 3 | Article | 24h | Most blogs (default) |
| 4 | Essay | 72h | Aeon, Craig Mod |
| 5 | Evergreen | 7 days | Tutorials, references |
Set velocity per feed or per category in Settings. Fresh articles are fully visible; older ones dim gradually until they disappear.
Stream can use Google's Gemini AI to help organise your feeds. It is entirely optional — everything works without it.
- Suggest categories — sends your uncategorised feed titles and URLs to Gemini and suggests which category each belongs in, grouping similar feeds together. Apply suggestions one at a time, by group, or all at once.
- Suggest velocity — recommends velocity tiers for each category based on the category name and its feeds. Apply and undo with one click.
- Suggest feeds — recommends new RSS feeds you might enjoy based on what you already subscribe to.
To enable: open Settings → AI assistant, get a free API key from Google AI Studio, and paste it in. Your key is stored in your browser only and never sent to Stream's servers. Only feed titles and URLs are shared with Google — not your articles, reading habits, or any other data.
| Key | Action |
|---|---|
j / k |
Navigate down / up |
Enter / o |
Open article |
b |
Open original in new tab |
c |
Copy link / share |
d |
Dismiss article |
s |
Save / star |
z |
Undo dismiss |
Esc |
Close reading view |
? |
Show / hide shortcuts |
Your Feedbin or FreshRSS username and password are stored only in your own browser. Stream does not have a central server, and nothing is sent to anyone but your RSS service.
Because browsers do not let JavaScript make authenticated cross-origin requests to APIs that do not send CORS headers, Stream needs a way to bridge that gap. On first run it asks you to pick one of three modes, each with an honest trade-off:
Direct. The browser talks to your FreshRSS server directly. No proxy anywhere in the path, nothing for anyone to log. This is the strongest trust story — Stream is a static page and your credentials only ever touch your own server. Only works with FreshRSS, and only if your server sends CORS headers (see the nginx snippet below). Does not work with Feedbin, because Feedbin's API does not send CORS headers.
Your own proxy. You deploy a tiny proxy (the same 90-odd lines of TypeScript as the bundled Netlify function) under your own Cloudflare, Deno Deploy, or Vercel account. Stream talks to your proxy; your proxy talks to Feedbin or FreshRSS. Your credentials pass through a server you control, whose logs you control, and which nobody else has access to. Ready-to-deploy templates for all three platforms live under proxies/.
This site's shared proxy. The quickest setup: route through the Netlify function that ships with the hosted deployment. But your credentials will pass through the operator's Netlify account on every request, and Netlify's platform logs every function invocation. An operator with bad intent or a compromised account could read your credentials from those logs. For a personal deployment where you are the operator this is fine. For a shared deployment like stream.dynamicskillset.com it is a real trust trade-off, and Stream will show you the warning before letting you pick it.
If you already had a Stream connection from before this change, it still works — old connections keep using the shared proxy and a one-time banner on the river nudges you toward switching to a private mode.
Running Stream locally (for developers)
npm install
npm run dev # → http://localhost:5173The dev server includes a reverse proxy that forwards API requests server-side, so CORS is handled automatically.
Deploying to other static hosts
npm run build:web
# deploy packages/web/dist/ to your hostFor self-hosted FreshRSS, your server must send CORS headers. Add to your nginx config:
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
add_header Access-Control-Allow-Methods "GET, POST";Feedbin users need no extra configuration beyond what is described above.
Docker images are automatically built and published to GitHub Container Registry when a GitHub Release is published.
Pull and run the latest image:
# Pull from GitHub Container Registry
docker pull ghcr.io/dynamicskillset/stream:latest
# Run the container
docker run -p 8080:8080 ghcr.io/dynamicskillset/stream:latestOr build locally:
# Build the Docker image
docker build -f docker/Dockerfile -t stream .
# Run the container
docker run -p 8080:8080 streamThe container serves Stream on port 8080. Access it at http://localhost:8080.
Architecture
Two packages in an npm workspace monorepo:
packages/
core/ Preact components, river engine, backend adapters
web/ Vite SPA
Tech: Preact · Vite · CSS Modules
Docker support contributed by @benrhughes.