Warning
This project is under active development and many features are not yet implemented.
A modern, serverless reimplementation of Calibre-Web built with Next.js and TypeScript.
![]() Library |
![]() Edit metadata |
![]() Fetch metadata from the internet |
![]() Built-in reader |
Calibre-Web does the job well. This project exists because of what running it costs and demands, not because of what it does:
- The stack has aged — Python/Flask with server-rendered templates and a jQuery-era frontend, and no static typing to lean on. Reading the code and changing it safely costs more than it should.
- You pay for an instance around the clock — tending a library takes a few minutes at a time: upload a book, correct its metadata, send a file to a reader. Because it is not serverless, the instance has to stay up and billing through all the hours in between.
- TLS on a single instance is awkward — for one long-lived instance, the major cloud providers offer few managed certificate options. You end up either paying for a load balancer to sit in front of it or renewing certificates yourself.
- The library sits on ephemeral storage — a Calibre library on an instance disk has no redundancy and needs a backup story of its own. Pointing it at NFS makes it durable but slow, and a grid of cover images makes that latency impossible to miss.
Reading happens on a dedicated e-reader; the web app is only needed for those brief moments in between. A serverless architecture fits that shape:
- Reduce costs — Pay only for actual usage, not idle time
- Ensure reliability — Leverage managed services (Firebase) for affordable, redundant storage
- Improve maintainability — Use static typing with TypeScript for safer code, and leverage a modern tech stack to minimize codebase size
- Provide a refined UI — Deliver a modern, polished user experience
Achieve feature parity with Calibre-Web while embracing serverless principles and modern web technologies.
- Framework: Next.js (App Router), React
- UI: Chakra UI, Emotion
- Forms: react-hook-form
- Backend: Firebase (Auth, Firestore, Storage)
- Language: TypeScript
- Testing: Vitest, Playwright, Storybook
- Linting/Formatting: Biome
- Nix with flakes and direnv:
direnv allowenters the devshell — bun, a headless JDK for the Firebase emulators, and Chromium for the E2E tests — and loads the development environment variables. - Without Nix, install those yourself: bun, a JDK (the Firestore and Storage emulators run on Java), and Chromium for
bun run test:e2e.
direnv allow # or: nix develop
bun install
bun run devbun run dev bundles the Cloud Functions, starts the Firebase emulators, seeds them, and serves the app on http://localhost:3000. Nothing talks to a real Firebase project, so no account or credentials are needed. Sign in as test@example.com / password123; docs/seeding.md covers the rest of the seeded library.
The "fetch metadata from the internet" feature calls the Google Books API. Without an API key it falls back to the public, IP-rate-limited quota and quickly returns 429, which makes local verification flaky. For stable local testing, set a Google Books API key in .envrc.local (gitignored):
# .envrc.local
export GOOGLE_BOOKS_API_KEY=your-google-books-api-key.envrc sources .envrc.local automatically — run direnv allow after creating it. The key is only needed locally; deployed environments read it from Secret Manager.
Everything the deployed environments need is Terraform (terraform/): the Firebase projects, Firestore, Cloud Storage, Identity Platform, App Check, the functions' IAM and secrets, and the Workload Identity Federation that the GitHub Actions deploy authenticates with. It describes one deployment target, a single GCP project; a personal deployment needs just that.
Before the first apply you need, outside Terraform:
- A GCP project linked to a billing account (2nd-gen Cloud Functions need the Blaze plan), with Owner for your account. Terraform configures the project but does not create it.
- A GCS bucket for the Terraform state.
- Terraform 1.5 or later and the gcloud CLI, signed in with
gcloud auth application-default login. The Nix devshell provides neither.
Then:
-
Copy
terraform/terraform.tfvars.exampletoterraform.tfvarsand fill it in. -
Run
terraform init -backend-config="bucket=<your tfstate bucket>", thenterraform apply. It writesweb/.env.production(gitignored: it holds your project's client config) and outputs the Workload Identity provider and the deploy service account. -
Set these variables on the
productionGitHub environment, then run the Deploy workflow:Variable Value WIF_PROVIDERthe wif_provideroutputFIREBASE_SERVICE_ACCOUNTthe service_account_emailoutputFIREBASE_PROJECT_IDthe project ID WEB_ENVthe contents of the generated web/.env.production -
Create your account in the Firebase console under Authentication → Users → Add user. Sign-up is disabled on purpose: the web API key ships in the client, so open sign-up would let anyone mint an account.
To try changes before they reach production, apply the same configuration to a second project as a staging target: put its values (including environment = "staging" and, if you like, a test user) in staging.tfvars, and apply it in a workspace of the same name so the two states stay apart:
terraform workspace new staging # once; `terraform workspace select staging` afterwards
terraform apply -var-file=staging.tfvarsIt writes web/.env.staging, which goes into a staging GitHub environment the same way. Setting the repository variable DEPLOY_STAGING_ON_PUSH to true then deploys it on every push to master.
App Check enforcement takes effect the moment terraform apply creates it; from then on a deployed web build without the site key is locked out. A new deployment is safe in the order above, since nothing is deployed yet and the first deploy carries the key. To add App Check to a target that already serves a client:
terraform apply -target=local_file.dotenvcreates the key and writes the env file without enforcing anything.- Update
WEB_ENVand deploy. - Run the full
terraform apply.
docs/security.md covers what App Check adds.
Shipped:
- Authentication, with password reset
- Library dashboard
- Book upload — several files at once, in a background queue that survives in-app navigation and resumes uploads interrupted mid-transfer
- Multiple file formats per book (EPUB, PDF, MOBI, AZW, AZW3, FB2, TXT)
- Auto-extract metadata from uploaded books (EPUB, PDF)
- Book metadata editor
- Book cover management, including custom cover upload
- Book deletion
- Filter and sort the library by author, series, publisher, tag, language and rating
- Bookshelves (user-created collections)
- Fetch metadata from external sources by title or identifier
- OPDS catalog, authenticated, with per-bookshelf feeds
- Built-in reader for PDF and EPUB, including vertical writing
Planned:
- Book detail page
- Book search
- Reading progress tracking
- Send-to-Kindle
- NotebookLM integration
- ML-based metadata extraction from cover images (fallback)
- Social login
- Two-factor authentication
- Passkey support



