2026 · Solo · Production
Vertex Reader
Manga and manhwa reader aggregating 50+ sources into one library, with an LLM that writes and versions its own scrapers.
Mission
My manhwa and manhua lived in too many places. Each source had its own reader, its own bookmarks, and its own idea of which chapter I was on. I wanted one library that pulls chapters from 50+ sources, keeps my progress in sync across phone and laptop, and tells me when something new lands.
For whom: readers who follow dozens of series across aggregator sites and are tired of tabs. Constraints: one engineer, self-hosted infrastructure, sources that change their markup without notice, and Cloudflare in front of most of them.
Manifest
| Layer | Choice | Version | Why |
|---|---|---|---|
| app | Next.js | 16 | Web reader and admin from one app router; ISR keeps series pages cheap. |
| app | Expo (React Native) | — | One codebase for iOS and Android with an offline SQLite cache for chapters. |
| services | NestJS | 11 | Module boundaries and DI I already trust; the API stays thin because work goes to queues. |
| services | Fastify + Playwright renderer | — | A browser pool for sources behind Cloudflare, isolated from the API process. |
| data | PostgreSQL + Prisma + Kysely | 16 / 7 | Prisma for migrations, Kysely for typed hot-path queries. |
| infra | Docker Swarm + nginx | — | Self-hosted, cheap, and the same deploy shape as my other products. |
| agents | Anthropic and OpenAI SDKs | — | Generate and regenerate per-site scraping strategies; every output is versioned and reviewed. |
Decisions
Let the model write the scrapers. An LLM-driven engine generates a per-site scraping strategy, versions it, and regenerates when a source changes. I review diffs instead of hand-editing selectors.
One monorepo, five surfaces. NestJS API, Next.js web and admin, an Expo app with offline SQLite, and a BullMQ worker fleet share types and Prisma models through Nx.
Prisma for the schema, Kysely for the queries. Prisma owns migrations; anything hot or complex is a typed Kysely query.
Workers for everything slow. Chapter checks, image optimisation and slicing, dedup, notifications, backups, and email campaigns all run as BullMQ jobs so the API stays thin.
Self-hosted on Docker Swarm. Cheaper than managed, and I already run the rest of my products this way. PostHog for product analytics, Stripe for subscriptions, Better Auth for accounts.
Impact
- Beta users
- ~230
- Sources
- 55
- Commits
- 1,205
About 220 beta users on the open staging build, 50+ sources, and 1,189 commits, all of it built and maintained solo. Gamification and subscriptions are live; the mobile app reads offline.
What broke
Cloudflare. The first scraper fleet used plain HTTP fetches, and within weeks the biggest sources started serving challenge pages instead of chapters. Chapter checks silently returned empty lists, so users saw series go quiet rather than an error.
The fix was a separate Fastify renderer service with a pool of warm Playwright browsers, plus FlareSolverr for the sources that need a solved challenge before the pool can take over. Strategies now declare which fetch tier they need, and a check that returns nothing twice in a row is flagged instead of trusted.
Ship log
- First commit
- Open beta on staging; wrote it up on Medium
- In-app support: conversations with an admin queue, chat on web and mobile, and an email fallback for replies left unread
- Source Health: a triage board with one-click fixes matched to each failure kind
- Now: ~230 beta users, 55 sources, 360+ titles, 1,205 commits