Build Feedly
YESreplaces $7/mosaves $84/yrback to the verdict
A personal RSS reader that is polite to the sites it reads: your Feedly OPML imported with folders, conditional fetching so unchanged feeds cost nothing, dedupe that survives rotating GUIDs, sanitized content, a keyboard-driven reader, starred items and full-text search, and optional LLM summaries that only appear when a key exists.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Everything in this build runs on it: the server, the scripts, the tests.
Get it Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. open ↗
Verify
node --version prints v22 or higher - installfree
Why Every step below is a command you type or a file you edit.
Get it VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. open ↗
Verify
You can open a folder and run a command in its terminal - installfree
Why History for your code, and the way most hosts deploy.
Get it Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. open ↗
Verify
git --version prints a version - have readyfree
Why Phase 1 imports it so nothing is retyped.
Get it Feedly > Settings (gear) > OPML > Export. Save it as feeds.opml in the project.
- decidefree
Why Polite fetching identifies itself. Site owners can reach you if your reader misbehaves.
Get it Something like MyReader/1.0 (+https://yourdomain.com/reader)
- API keypay per use
Why Phase 6 adds a per-article summary button; without a key the button does not exist.
Get it Anthropic or OpenAI console, into .env. open ↗
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- `feeds`: id, url, title, folder, site_url, etag, last_modified, last_fetched_at, last_status, consecutive_failures, disabled - `items`: id, feed_id, guid, link, title, author, published_at, content, read (bool), starred (bool), fetched_at Unique index on `(feed_id, guid)`. `etag` and `last_modified` are not optimizations · see Phase 2. Store `published_at` normalized to UTC, and fall back to `fetched_at` when a feed omits or mangles the date, which many do.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
PORT | required | 4830 | Bound to 127.0.0.1 or your LAN. |
DATABASE_PATH | required | ./data/reader.db | SQLite file. |
OPML_PATH | required | ./feeds.opml | Your subscription list; the reader exports back to it. |
USER_AGENT | required | MyReader/1.0 (+https://yourdomain.com/reader) | Identifies your fetcher to the sites you read. |
FETCH_INTERVAL_MINUTES | optional | 30 | How often the loop runs. |
ANTHROPIC_API_KEYsecret | optional | sk-ant-... | Enables summaries. Empty hides the feature entirely. |
The build, in order
OPML and feed list
Import with folders, idempotently, and export back.
feeds (id, url, title, folder, site_url, etag, last_modified, last_fetched_at, last_status, consecutive_failures, disabled), items (id, feed_id, guid, link, title, author, published_at, content, read, starred, fetched_at), unique (feed_id, guid).
terminalmkdir reader && cd reader && git init && npm init -y && npm pkg set type=module npm install express@4 better-sqlite3@13 rss-parser@3 node-cron@3 mkdir data && cp .env.example .env
done when · tick each as it passesPolite fetching
Conditional GETs, a real User-Agent, timeouts, per-host courtesy, backoff.
Skipping this re-downloads every feed forever, which is how a reader becomes a nuisance.
done when · tick each as it passesItem ingestion
Dedupe that survives bad GUIDs, and HTML that cannot hurt you.
- terminal
npm install sanitize-html@2
done when · tick each as it passesReader UI
Folders with counts, a list, a reading pane, keyboard everything.
Mark-as-read on scroll-past is a preference, not the default.
done when · tick each as it passesStarred and search
Optional summaries
A button that exists only when a key does, cached per item.
A dead button is worse than no button.
done when · tick each as it passesRetention and deploy
Prune read items, keep starred, back up, run as a service.
README: how to add a feed (edit the OPML) and export it back, the polite-fetching behaviour as a promise, where the database lives.
Files
README.md
done when · tick each as it passesOperate it like a productproduct builder
Only for the product-builder path: know when the reader is down, never lose the database, and keep the server patched.
Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices.
One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight.
SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it.
terminalsqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/
Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure.
done when · tick each as it passes
That is the whole plan for Feedly. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Mobile apps; the web UI over your network is the answer.
- Feed discovery and their AI filtering layer, which need a crawl of the web.
- mobile apps
- feed discovery
- integrations
- Leo/AI filtering
- uptime
- polished reading UX
- A read-later inbox that saves arbitrary URLs into the same reader
- Per-folder digests by email
Need the files? The project pack on the verdict page hands your agent the whole brief · more rss & research.