A local-first RSS reader inspired by Google Reader. Built with Next.js, SQLite, and Tailwind CSS. Runs entirely on your machine — no accounts, no cloud, no tracking.
I built this because I missed Google Reader. Clean feeds, no algorithm, no ads — just the internet the way I used to learn from it.
I used Claude Code to build it, which means the tool I used to make this is the same thing I was trying to understand. That felt like the right way to learn.
If it brings back the feeling too, star the repo. It helps more people find it.
And if you really love it: buy me a coffee. Hon, you didn't have to, but I appreciate it.
- Three-panel layout — folders/feeds sidebar, article list, reading pane
- Two-panel layout — focus mode that swaps between list and reader; toggle in Settings
- Unread & Starred views — fixed items at the top of the sidebar; Unread is the default view
- Article content — full article body rendered in a clean typography layout
- Open original — open the source article in a new tab
- Mark as read/unread — automatic on open, or toggle manually
- Star articles — save favorites for later
- Add feeds — paste any RSS/Atom URL
- Feed auto-discovery — paste a site URL and Good Reader finds the RSS feed automatically
- Feed favicons — icons fetched by parsing each site's
<link rel="icon">tag, not just/favicon.ico - Folders — organize feeds into collapsible folders (collapsed by default)
- Refresh All — fetch latest articles from all feeds at once; updates title, URL, and favicon on each refresh
- OPML import/export — migrate from Google Reader, Feedly, or any other RSS reader
- Full-text search — powered by SQLite FTS5; searches titles and article bodies
- Load more — pagination for feeds with large backlogs (50 articles per page)
- Auto-scroll — article list keeps the selected item in view during keyboard navigation
- Tags — create and manage tags across all articles
- Tag filtering — click any tag in the sidebar to filter articles by that tag
- Inline tag manager — add or remove tags directly from the reading pane header
- Tag pills — tags shown on article cards in the list view (up to 3, with overflow count)
- Auto-color — tags are automatically assigned colors from a preset palette
- Summarize — one-click bullet-point summaries using a local LLM
- Auto-tag — automatically generates and applies relevant tags to an article using a local LLM; reuses existing tags and creates new ones freely (3–6 tags per article)
- Both features are configurable: set the server URL and model name in Settings
| Key | Action |
|---|---|
j |
Next article |
k |
Previous article |
o / Enter |
Open original in browser |
s |
Star / unstar |
m |
Toggle read/unread |
r |
Refresh all feeds |
? |
Show keyboard shortcut help |
j, k, o, s, and m are customizable in Settings. r, Enter, and ? are always active.
- Switch between 3-panel and 2-panel layout modes
- Remap any keyboard shortcut from a curated list of alternatives
- Configure LM Studio server URL and model name
- Settings persist in
localStorage
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Styling | Tailwind CSS v4 + shadcn/ui |
| Database | SQLite via better-sqlite3 |
| Full-text search | SQLite FTS5 virtual table |
| RSS parsing | rss-parser |
| HTML parsing | jsdom (OPML import, feed discovery) |
| AI | LM Studio (OpenAI-compatible local API) |
| Desktop | Electron (wraps Next.js server natively) |
- Node.js 18+ (download)
- npm (comes with Node.js)
- Git
# Clone the repo
git clone https://github.com/scottpurcell/good-reader.git
cd good-reader
# Install dependencies
npm install
# Start the dev server
npm run devOpen http://localhost:3000 in your browser.
The SQLite database is created automatically at data/good-reader.db on first run — nothing extra to set up.
node -v # should print v18.x.x or higherIf you need to upgrade Node, use nvm (nvm install 20 && nvm use 20) or download from nodejs.org.
- Click + Add Feed in the top bar
- Paste a feed URL (e.g.
https://hnrss.org/frontpage) or a site URL — Good Reader will auto-discover the RSS link - Optionally assign to a folder and click Add Feed
- Export an OPML file from your current reader
- Click ··· → Import OPML in Good Reader
- All feeds and folders are imported automatically
Good Reader can run as a native desktop app on macOS and Windows via Electron.
Download a pre-built release:
The easiest way to get the desktop app is to download a release from the GitHub Releases page.
| Platform | File |
|---|---|
| macOS Apple Silicon (M1/M2/M3) | Good Reader-x.x.x-arm64.dmg |
| macOS Intel | Good Reader-x.x.x.dmg |
| Windows | Good Reader Setup x.x.x.exe |
macOS note: Builds are currently unsigned. On first launch, right-click the app → Open to bypass Gatekeeper.
Development (live reload):
npm run electron:devThis starts Next.js and Electron together. The app opens in a native window.
Build a distributable:
npm run electron:build:mac # macOS → dist-electron/*.dmg
npm run electron:build:win # Windows → dist-electron/*.exe
npm run electron:build # current platformThe build scripts automatically rebuild native modules (
better-sqlite3) for the correct Electron Node version before compiling. On macOS, both Intel (x64) and Apple Silicon (arm64) DMGs are built from a single command. Cross-compiling a Windows.exefrom macOS uses Wine (downloaded automatically by electron-builder).
The database is stored in the OS user-data directory (~/Library/Application Support/Good Reader/ on macOS, %APPDATA%\Good Reader\ on Windows) so it persists across app updates.
- Download and open LM Studio
- Load a model (e.g.
meta-llama-3.1-8b-instruct) - Start the local server (default port:
1234) - In Good Reader: Settings (sidebar) → AI Summarization
- Set the model name to match what's shown in LM Studio → Save
- Open any article and click ✨ Summarize or 🏷 Auto-tag
app/
layout.tsx — root layout, dark mode, Toaster
page.tsx — main three-panel UI (all state)
globals.css — Tailwind v4 + typography plugin
api/
folders/ — GET/POST/PATCH/DELETE folders
feeds/ — GET/POST/PATCH/DELETE feeds
discover/ — GET: auto-discover RSS from a site URL
refresh-all/ — POST: refresh all feeds + update metadata/favicons
[id]/refresh/ — POST: refresh single feed + update metadata/favicons
articles/ — GET (with FTS search, tag filter) / PATCH
[id]/summarize/ — POST: summarize via LM Studio
[id]/auto-tag/ — POST: generate & apply tags via LM Studio
[id]/tags/ — POST: add tag to article
[id]/tags/[tagId]/ — DELETE: remove tag from article
mark-all-read/ — POST: bulk mark read
tags/ — GET: list tags, POST: create tag
[id]/ — PATCH: rename, DELETE: remove tag
opml/ — GET: export, POST: import
components/
sidebar.tsx — feed/folder tree + tags section with context menus
article-list.tsx — scrollable article cards with tag pills
reading-pane.tsx — article content, inline tag manager, AI summary/auto-tag
top-bar.tsx — Refresh All, Add Feed, ··· menu
add-feed-dialog.tsx — add feed with auto-discovery
settings-dialog.tsx — keyboard shortcuts + LM Studio config
keyboard-shortcuts.tsx — shortcut reference dialog
lib/
db.ts — SQLite singleton + schema migrations + FTS5 + tags tables
feed-fetcher.ts — rss-parser wrapper + async favicon resolution
keybindings.ts — keybinding types, defaults, localStorage
layout.ts — layout mode types, defaults, localStorage
lmstudio.ts — LM Studio config types, localStorage
utils.ts — shadcn cn() helper
electron/
main.js — Electron main process; spawns Next.js server + BrowserWindow
preload.js — renderer preload (context isolation)
scripts/
backfill-favicons.mjs — one-time script to populate favicon_url for existing feeds
- Three-panel Google Reader–style layout
- SQLite database with auto-migration on startup
- Add / remove feeds and folders
- Refresh individual feeds and refresh all
- OPML import and export
- Full-text search (SQLite FTS5)
- Feed URL auto-discovery from site homepages
- Load more / infinite scroll pagination
- Auto-scroll article list on j/k navigation
- Open all article links in new tab
- Unread and Starred fixed sidebar items
- Reading pane scrolls to top on article change
- Customizable keyboard shortcuts (Settings)
- Folders collapsed by default
- "Inactive" folder for stale feeds
- AI summarization via LM Studio (local LLM)
- Article tagging with tag-based sidebar filtering
- Inline tag manager in the reading pane
- Tag pills on article cards
- Auto-tag articles with LLM (creates new tags freely)
- Feed favicons via
<link rel="icon">parsing (not just/favicon.ico) - Feed metadata (title, URL, favicon) updated on every refresh
- Electron wrapper — native desktop app for macOS and Windows
- GitHub Releases with pre-built DMG and Windows installer
- Two-panel layout mode (focus view)
- Settings as a sidebar nav item (mutually exclusive with feed/folder/tag views)
- Settings: 3-column layout with General, Shortcuts, AI, and Data sections
- OPML import/export moved into Settings → Data (removed ··· dropdown from top bar)
- Settings auto-save — all changes apply immediately, no Save/Cancel needed
- Auto-refresh feeds when switching to Unread view (no separate Refresh All button)
- 2-panel fix: preserve selected article during background feed refreshes
- Dark / light mode toggle
- "Read later" queue
- Mobile-responsive layout
- PWA support for offline reading
- Signed macOS DMG (requires Apple Developer account)
- Per-feed refresh interval settings
- Article sharing (copy link, share sheet)
- Swipe gestures on mobile / trackpad
# 1. Bump the version (updates package.json, commits, and creates a git tag)
npm version patch # bug fixes: 0.2.0 → 0.2.1
npm version minor # new features: 0.2.0 → 0.3.0
npm version major # breaking: 0.2.0 → 1.0.0
# 2. Push the commit and tag
git push origin main --tags
# 3. Build the distributables
npm run electron:build:mac # creates dist-electron/*.dmg
npm run electron:build:win # creates dist-electron/*.exe
# 4. Create a GitHub Release and upload the artifacts
gh release create vX.X.X \
"dist-electron/Good Reader-X.X.X-arm64.dmg#macOS (Apple Silicon)" \
"dist-electron/Good Reader-X.X.X.dmg#macOS (Intel)" \
"dist-electron/Good Reader Setup X.X.X.exe#Windows Installer" \
--title "Good Reader vX.X.X" \
--notes "## What's new
- ..."Or create the release manually via GitHub → Releases → Draft a new release, pick the tag, and drag the files from dist-electron/ into the assets area.
Note:
ghCLI must be authenticated (gh auth login). The build scripts handlenpm rebuild better-sqlite3automatically before each build — no manual step needed.
- All API routes require
export const dynamic = 'force-dynamic'to prevent build-time SQLite errors - Tailwind v4: use
w-[360px]notw-90(only multiples of 4 up tow-96are valid) - Flex scroll pattern:
flex-1 min-h-0 overflow-y-auto+overflow-hiddenon the parent container - SQLite FTS5: use actual table name in
MATCHquery, not an alias - Tags use
json_group_array/json_objectcorrelated subquery to avoid N+1 queries - The database file at
data/good-reader.dbis gitignored
MIT
