As a developer, I have a habit of looking at everyday annoyances and thinking, "I could code my way out of this."
My annoyance? Manga aggregator sites. They are often bloated, riddled with invasive ads, and frustratingly slow on mobile data. I wanted a reading experience that was clean, fast, and simple.
Instead of complaining, I opened my IDE. I decided to build FanaCumik, an open-source, ad-free manga reader. My goal was twofold: fix my reading experience and, more importantly, learn how to push SvelteKit and Cloudflare Workers to their limits.
Here is how I built it, the challenges I faced, and how you can contribute.
The Tech Stack
I chose this stack to ensure maximum performance and zero cost for a hobby project:
- SvelteKit: For its incredible developer experience and native support for Server-Side Rendering (SSR).
- Cloudflare Workers: To run the scraping logic on the Edge, ensuring low latency for users globally.
- Cheerio: For lightweight, fast HTML parsing.
- TailwindCSS: To build a mobile-first, app-like UI without writing custom CSS.
The Architecture: How it Works
The app doesn't host any content (which keeps it lightweight and legal). Instead, it acts as a smart browser.
- The Request: When you click a manga, SvelteKit sends a request to the Cloudflare Worker.
- The Scrape: The worker fetches the HTML from the source (e.g., Asura Scans) and uses
Cheerioto extract only the data we need (title, image links, chapters). - The Clean Up: All ads, trackers, and unnecessary scripts are discarded.
- The Delivery: The clean JSON data is sent back to the client, where Svelte renders it instantly.
Challenge: The Image Proxy
The biggest technical hurdle was Hotlink Protection. Many manga sites check the Referer header to ensure images are only loaded on their own domains. If you try to load their image URL directly in your app, it breaks.
The Solution:
I built a simple proxy endpoint (/api/proxy).
When the frontend requests an image, it asks my API. My API then fetches the real image while injecting the correct Referer header to "trick" the source server.
// Pseudo-code of the logic
export async function GET({ url }) {
const imageUrl = url.searchParams.get('src');
const sourceReferer = 'https://original-manga-site.com';
const image = await fetch(imageUrl, {
headers: { 'Referer': sourceReferer }
});
return image;
}
Making it Extensible: The Adapter Pattern
I didn't want this to be hard-coded for just one site. I wanted FanaCumik to be a universal reader. I implemented an Adapter pattern where every source is just a class extending a BaseSource.
To add a new site, you only need to write one file. Here is what the implementation for a new source looks like:
export class NewSource extends BaseSource {
id = 'new-source';
baseUrl = 'https://newsource.com';
async getLatestManga(page: number) {
const html = await this.fetchHtml(`${this.baseUrl}/page/${page}`);
const $ = cheerio.load(html);
// ... scraping logic ...
return mangaList;
}
}
This design allows anyone to contribute a new source without touching the core application logic.
Final Thoughts & Future Plans
Building FanaCumik taught me that Edge rendering is the future of performant web apps. The speed difference compared to client-side fetching is night and day.
The project is Open Source and available on GitHub.
- Repo: github.com/MasFana/fana-cumik
- Live Demo: fmo.qzz.io
I am currently looking for contributors! If you know a manga site you’d like to see added, feel free to fork the repo and submit a PR.
Note: This project is for educational purposes only. Please support original creators and official releases where available.
Happy coding! 🦑
Top comments (1)
UI and speeds are good .
but feature request option save the manga for future reads ..