[Dev Log][Node.js] Feedly Classic is gone, so I built my own FeedFlow (Part 1) A developer built FeedFlow, an RSS reader with automatic translation of non-Chinese articles using Gemini 2.5 Flash, after being dissatisfied with Feedly's redesign. The project uses an Express backend, Vanilla JS frontend, Google Firestore, and LINE Login for authentication, and is deployed on Cloud Run. Since Feedly's redesign, I haven't been able to get used to the interface. I especially miss the high-density, single-column, no-nonsense reading rhythm of the old Feedly Classic. My subscription sources include many English, Japanese, and Korean technical blogs. Every time I encountered them, I either had to open a new tab for translation or simply skip them. Over time, these sources became "read but ignored." Instead of continuing to settle, I spent a weekend building my own RSS reader: FeedFlow https://github.com/kkdai/rss-feed-class-webapp . It features a mobile-first design, dark theme, and multi-column view modes—a tribute to Feedly Classic. Data is stored in Google Firestore, and accounts are tied to LINE Login. For non-Chinese articles, the background automatically uses Gemini 2.5 Flash to translate them into Traditional Chinese. It is currently deployed on Cloud Run https://feedflow-660825558664.asia-east1.run.app , and I use it every day. This repo will continue to be developed. This post is the first in a series, documenting the skeleton of the entire project and the context behind several key decisions. This article will introduce: There is no shortage of RSS readers on the market, but what I wanted was very specific: it needs to be fast on mobile, have high information density no big images taking up the whole screen for every article , and allow for in-place translation of foreign sources without switching tabs. These three conditions combined are not fully met by any existing service, especially "automatic translation of non-Chinese articles," which is almost never treated as a first-class citizen in other readers. The project is named FeedFlow, and its core consists of three parts: an Express backend server.js responsible for fetching RSS, parsing content, calling Gemini, and reading/writing to Firestore; a frontend using pure Vanilla JS ES Modules app.js , store.js , api.js , i18n.js without any frameworks; and Firestore for the database with LINE Login for accounts. The first MVP version produced subscription management, folder categorization, four view modes, and a dark theme. Every subsequent version has been built upon this skeleton. I previously wrote about How to develop OAuth2 PKCE via Golang https://www.evanlin.com/go-oauth-pkce/ , which discussed the implementation details of introducing PKCE to LINE Login. At that time, LINE Login was the subject of research for deconstructing the protocol. In FeedFlow, LINE Login takes on a different role—it is the key to making the multi-user architecture viable. FeedFlow initially didn't have an account system; data was stored in the browser's localStorage. If you switched devices, your subscriptions would disappear. To achieve cloud sync, the first step was to have a stable user identity that could serve as a key for document paths in Firestore users/{userId}/... . Instead of building a custom username/password system, using the LINE User ID sub claim obtained via LINE Login as this key saved me from building the entire password, verification email, and "forgot password" flow. For a personal project, this was much more cost-effective. This decision wasn't made all at once. The earliest version was a "lazy" version: letting users paste a LINE UID string, which the backend just used as a Firestore key without any authentication. This kind of "login" meant anyone could impersonate any UID. The next version switched to formal LINE OpenID Connect: a standard OAuth 2.1 authorization code flow. After obtaining the id token , the signature is verified using LINE's /oauth2/v2.1/verify , and the session is stored in an HTTP-only cookie rather than being passed around in URL parameters. Later, state and nonce checks were added to block CSRF. This "get it working first, then get it right" sequence reflects the typical rhythm of personal projects: get the features working to confirm the direction, and fix security holes once the need is visible. Since the primary use case is sharing links within LINE and opening them in the LINE in-app browser, I also integrated the LIFF SDK. This allows users opening the app within LINE to log in directly using LIFF's SSO without being redirected to an external browser. The features during the MVP stage weren't much different from a standard RSS reader: paste a URL, the backend parses the feed using rss-parser , and if no feed is found, it uses cheerio to scan the webpage's