Build Feedly

YESreplaces $7/mosaves $84/yrback to the verdict

0%0 of 21 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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.

estimated effort one sittingthe files for this build are in the project pack

RuntimeNode 22 with Express and better-sqlite3Parsingrss-parser, node-cronSearchSQLite FTS5Hostinglocalhost, or your own network

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. 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

  2. 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

  3. 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

  4. 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.

  5. 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)

  6. 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.

VariableNeededExampleWhere the value comes from
PORTrequired4830Bound to 127.0.0.1 or your LAN.
DATABASE_PATHrequired./data/reader.dbSQLite file.
OPML_PATHrequired./feeds.opmlYour subscription list; the reader exports back to it.
USER_AGENTrequiredMyReader/1.0 (+https://yourdomain.com/reader)Identifies your fetcher to the sites you read.
FETCH_INTERVAL_MINUTESoptional30How often the loop runs.
ANTHROPIC_API_KEYsecretoptionalsk-ant-...Enables summaries. Empty hides the feature entirely.

The build, in order

  1. OPML and feed list

    Import with folders, idempotently, and export back.

    1. 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).

      terminal
      mkdir 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 passes
  2. Polite fetching

    Conditional GETs, a real User-Agent, timeouts, per-host courtesy, backoff.

    1. Skipping this re-downloads every feed forever, which is how a reader becomes a nuisance.

    done when · tick each as it passes
  3. Item ingestion

    Dedupe that survives bad GUIDs, and HTML that cannot hurt you.

    1. terminal
      npm install sanitize-html@2
    done when · tick each as it passes
  4. Reader UI

    Folders with counts, a list, a reading pane, keyboard everything.

    1. Mark-as-read on scroll-past is a preference, not the default.

    done when · tick each as it passes
  5. Starred and search

    Starred forever, FTS5 via triggers.

    done when · tick each as it passes
  6. Optional summaries

    A button that exists only when a key does, cached per item.

    1. A dead button is worse than no button.

    done when · tick each as it passes
  7. Retention and deploy

    Prune read items, keep starred, back up, run as a service.

    1. 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 passes
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more rss & research.