Warning: This project was developed for personal use 100% via Claude Code. Didn't write one line. Make of that what you will.
Self-hosted speed reading web app for reading EPUB files using the RSVP (Rapid Serial Visual Presentation) technique.
Built for fun because I couldn't find an existing OSS RSVP reader that did quite what I wanted.
2026-01-14.02-33-53.mp4
- RSVP display — Words shown one at a time with ORP (Optimal Recognition Point) highlighting
- Adjustable speed — 100-1000 WPM with real-time adjustment
- Smart timing — Punctuation delays that scale with your WPM (pauses longer at periods, shorter at commas)
- Length delays — Optional extra time for longer words
- Frequency delays — Optional extra time for uncommon words (uses a 10k word frequency list)
- Paragraph view — Toggle to see full paragraphs with clickable words
- Full keyboard control (see shortcuts below)
- Chapter dropdown for quick navigation
- Mobile touch controls
- Upload and manage EPUB files
- Search books by title or author
- Automatic progress saving — resume exactly where you left off
- Duplicate detection
- Adjustable font size
- Timing delay toggles and intensity sliders
- All preferences saved to localStorage
git clone https://github.com/vinnymeller/rsvpub
cd rsvpub
npm install
npm run devTo just try it out:
nix run github:vinnymeller/rsvpubIf you've cloned the repo:
# Development shell
nix develop
# Build the package
nix build
# Build and run
nix runThe server looks for configuration in this order:
--config <path>CLI argument~/.config/rsvpub/config.json./config.json(current directory)
{
"server": {
"port": 7787,
"host": "0.0.0.0"
},
"storage": {
"dataDir": "~/.local/share/rsvpub"
}
}All fields are optional — defaults are shown above.
npm run dev -- --port 3000 --host 127.0.0.1Books and the database are stored in dataDir:
~/.local/share/rsvpub/
├── books/ # Uploaded EPUBs (stored by content hash)
└── rsvpub.db # SQLite database
Add the flake to your inputs:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
rsvpub = {
url = "github:vinnymeller/rsvpub";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}Import and configure the module:
{ inputs, pkgs, ... }:
{
imports = [ inputs.rsvpub.nixosModules.default ];
services.rsvpub = {
enable = true;
package = inputs.rsvpub.packages.${pkgs.system}.default;
port = 7787;
host = "127.0.0.1"; # Use "0.0.0.0" to expose to network
openFirewall = false;
};
}| Option | Type | Default | Description |
|---|---|---|---|
enable |
bool | false |
Enable the service |
package |
package | — | Package from flake (required) |
port |
port | 7787 |
Server port |
host |
string | "127.0.0.1" |
Bind address |
dataDir |
string | "/var/lib/rsvpub" |
Data directory |
openFirewall |
bool | false |
Open firewall for the port |
The service runs as a systemd unit with security hardening (DynamicUser, ProtectSystem, etc.). Data in dataDir persists across reboots.
| Key | Action |
|---|---|
Space |
Play / Pause |
← → |
Previous / Next word |
R |
Restart current paragraph |
[ ] |
Decrease / Increase speed (±25 WPM) |
PageUp PageDown |
Previous / Next chapter |
V |
Toggle RSVP / Paragraph view |
Escape |
Return to library |
In paragraph view, click any word to jump to it.
- Frontend: TypeScript, Vite
- Backend: Express 5, sql.js (SQLite in-process)
- EPUB parsing: epubjs
- Nix packaging: importNpmLock from nixpkgs
MIT