I've been reading several hours a month for years. Novels, non-fiction, a bit of everything. I've tried quite a few devices over time — mainly Kobos — and I always stumbled on the same problem: syncing my books and especially my reading positions between my e-reader, my Linux laptop, and my phone. The Kobo app isn't bad on the e-reader itself, but on a phone it's buggy and makes it really hard to load EPUBs that don't come from their store. In short, it just didn't work at all.
I ended up taking matters into my own hands and building my own solution. As we say: you can tell the blacksmith by looking at the wall — or something like that. Here's VarBook, a self-hosted web application for managing your EPUB library with reading progress synchronization across all your devices.
When you read on multiple devices, you quickly face a headache. I read a chapter in the evening on my Kobo in bed, then want to continue the next day on the train on my phone. Result: I have to manually find where I left off. Pressing "next page" 70 times to catch up with yesterday's reading session is the kind of thing that makes you close the book faster than planned.
The Kobo ecosystem is rather closed. The mobile app works, but it's clearly geared towards their store. Loading your own EPUBs into it is an obstacle course. And syncing positions between the mobile app and the physical e-reader? Let's just say it's... theoretical.
I needed something simple: upload my EPUBs to one place, read them on any device, and find my position everywhere. Nothing more, nothing less.
So I built VarBook over the past few months. It's a full Laravel 12 application with a React 18 TypeScript frontend. The built-in EPUB reader is based on epub.js, a JavaScript library that handles EPUB rendering directly in the browser.
The interface is clean and simple. To add books, a simple drag and drop does the trick. The application automatically extracts metadata (title, author, cover) from the EPUB. You can customize the reader: light/dark/sepia theme, font size, font family, line height — each device keeps its own reading preferences.
For those managing large libraries with Calibre, I also developed a plugin that lets you send your EPUBs in batch directly to VarBook from Calibre's interface. No more exporting then uploading one by one.
Uploaded books are then available in three ways:
The app also tracks reading statistics: time spent per book, session history, overall progress. It's the kind of data you didn't know you wanted until you had it.
One aspect I really cared about is offline mode. VarBook is a PWA (Progressive Web App) installable on phones — tested on Android so far. Concretely, you can download a book to the browser's local cache via IndexedDB (using Dexie as a wrapper) and read it without an internet connection.
Reading positions are cached locally during offline reading. As soon as you're back online, everything is automatically synced to the server thanks to Service Workers and the Background Sync API.
I was able to test this feature during my last plane trip and it worked perfectly. Though I must admit my recent changes might have broken it since — sindjeu (Belgian expletive), that's the joy of solo development. It's still a work in progress.
For the Kobo e-reader side, I rediscovered KOReader. I had installed it a while back but quickly dismissed it because of its interface that seemed rudimentary to me. What a blunder!
KOReader is an open-source document viewer designed for E Ink screens. It runs on Kobo, Kindle, PocketBook, Android, and even Linux. With a few minutes of configuration, the reader becomes very pleasant. The status bar is amazing: remaining pages in the chapter, estimated reading time, percentage progress, battery — all sorts of useful info you don't always find in commercial readers.
Speaking of battery, this is where KOReader has a real killer feature: WiFi is disabled by default and only activated when needed. On the stock Kobo firmware, WiFi stays on in the background, constantly draining the battery. KOReader cuts WiFi as soon as it's no longer needed. Result: you go from a few days of battery life to several weeks without breaking a sweat. When you read several hours a day, that's a game changer.
But above all, KOReader has a very comprehensive plugin system. And that's where it gets interesting for VarBook.
I developed a KOReader plugin (still in testing but already very functional) that syncs reading positions and progress with the VarBook server. A "VarBook" tool appears in the tools menu with a "Sync to VarBook" button. I've bound this tool to a touch gesture: a tap in the top-right corner of the screen.
My daily reading workflow looks like this:
It's smooth, fast, and most importantly it works without having to think about it. WiFi only turns on for the few seconds needed for synchronization, which preserves the e-reader's battery life. It's a detail, but it's exactly the kind of detail that makes you stop using the stock firmware for good.
It might not seem like much, but syncing reading positions between the web reader (epub.js) and KOReader on the e-reader gave me quite a bit of trouble. The fundamental problem is that these two applications handle positioning in completely different ways.
epub.js uses the CFI (Canonical Fragment Identifier) system defined by the EPUB specification. A CFI looks something like /6/4[chap01]!/4/2/1:0 — it's an XPath-like path into the chapter's HTML document, pointing to a specific DOM node and character offset. This system is very precise in a web browser context, but it's tightly coupled to HTML rendering.
KOReader, on the other hand, works with a page-based system. It uses its own rendering engine (CREngine) that paginates the document differently depending on screen size, font, text size, etc. The position is stored as a progress percentage and an internal "xpointer" — a format completely incompatible with epub.js CFIs.
In practice: the same position in a book generates two completely different representations depending on whether you read it in a browser or on the e-reader. And there's no universal conversion table between the two.
After quite a bit of struggle and experimentation, I ended up opting for synchronization based on two pieces of information common to both systems: the current chapter (identified by its path in the EPUB file) and the percentage of progress within that chapter. The server stores these two data points in a device-agnostic way, and each client translates them into its own positioning system.
It's not perfect — sometimes the position isn't accurate to the exact page. But it's perfectly acceptable and infinitely better than having to manually find your page every time you switch devices.
The project is available on GitHub: github.com/ndieschburg/varbook. It's translated into French, English, and Spanish. Installation on your own server is documented in the README.
I also host my own instance that's open for registration without restriction. If you want to try it without installing anything, feel free to create an account on varbook.hophop.be.
The tech stack for the curious: Laravel 12, React 18, TypeScript, Tailwind CSS, Vite, MySQL, all automatically deployed via CI/CD.
The whole thing was built over a good ten evenings with the help of my friend Claude, but I've been using it daily for three months now and I'm quite happy with it. I regularly make improvements as I read and run into small frustrations along the way. If you have suggestions, ideas, or bugs to report, I'm all ears — GitHub issues are open.
This article was originally written in French. This translation was generated automatically with AI assistance.