A client-side RSS reader built as a Next.js PWA. All data persists in IndexedDB (no backend database).
| Layer | Technology |
|---|---|
| Framework | Next.js (App Router), React 19 |
| UI Components | shadcn/ui (copy-pasted into components/ui/) |
| Styling | Tailwind CSS v4 |
| State / Cache | SWR + React hooks |
| Storage | IndexedDB via idb library (lib/db.ts) |
| RSS Parsing | rss-parser — server-side only via /api/proxy (CORS bypass) |
| Icons | Lucide React |
| Toasts | Sonner |
| PWA | Service Worker at public/sw.js |
pnpm dev # Dev server on port 3000
pnpm build # Production build (TypeScript errors are ignored — see note below)
pnpm start # Serve production build
pnpm lint # ESLintNo test framework is configured.
next.config.mjssetsignoreBuildErrors: true— TypeScript compile errors won't block builds.
app/page.tsx ← 3-column layout: Feeds | Articles | Reader
app/api/proxy/ ← Server route: RSS fetch & parse (avoids CORS)
components/ ← Feature components + shadcn/ui in components/ui/
hooks/ ← All IndexedDB access goes through custom hooks
lib/db.ts ← Raw IndexedDB CRUD (lazy singleton, DB_VERSION = 1)
lib/types.ts ← Core data model (Feed, Article, Highlight, ReadingSettings)
lib/rss-parser.ts ← parseFeed() → calls /api/proxy, sanitizes HTML
public/sw.js ← PWA service worker (network-first for navigation; cache-first for assets)
Data flow: Component → Hook (SWR cache) → lib/db.ts (IndexedDB). Mutations call mutate() to refresh SWR cache.
- All components are client components (
'use client'). No server components outsideapp/. - UI components live in
components/ui/(shadcn copy-paste pattern). Do not install shadcn as a package — add components by copying from the shadcn registry. cn()utility fromlib/utils.ts(clsx + tailwind-merge) for conditional class names.- File naming: kebab-case for all component and lib files.
- Path alias:
@/maps to the project root. - Mobile breakpoint: 1024px, detected via
useIsMobile()inhooks/use-mobile.ts. - New types: Add to
lib/types.ts, then updatelib/db.tsschema if storage is needed. - New settings: Add field to
ReadingSettingsinlib/types.ts, then persist viahooks/use-reading-settings.ts.
- No IndexedDB migration strategy —
DB_VERSIONis hard-coded to1. Adding new object stores requires bumping the version and adding anupgradehandler inlib/db.ts, or existing client databases will not be upgraded. - No article deduplication on refresh —
refresh()inuse-feeds.tscallsaddArticles()without a link-based uniqueness check; repeated refreshes can create duplicate article records. - Feed groups are denormalized — stored as a string field on each
Feed. Rename/delete a group by iterating all feeds — there's no dedicated group store. - Highlights are not cascade-deleted — deleting an article orphans its highlights in IndexedDB.
- API route is network-only in SW —
/api/proxyis always fetched from the network; RSS articles are only available offline if already stored in IndexedDB.