A minimal, fast, and beautiful web app for reading over 77,000 public-domain books. No sign-up, no paywall — just great typography and a distraction-free reader built for long-form reading.
READR connects you to Project Gutenberg’s full catalog through the Gutendex API. You can browse by genre, search by title or author, open any book, and read it in a dedicated reader with themes, font size controls, and automatic progress saving — all in the browser, with no account required.
The UI is inspired by modern reading and streaming apps: clean layout, clear hierarchy, and smooth but subtle animations so the content stays the focus.
Here’s a quick walkthrough of the app: from the home screen, through browse and search, to a book detail page and the reader.
- 77,000+ books — Full Project Gutenberg catalog via Gutendex; plain-text editions for reliable in-app reading.
- Server-rendered first load — Home, browse, and search fetch data on the server so the first paint is fast and SEO-friendly.
- Smart caching — Client-side cache with URL normalization and request deduplication; server-side LRU cache for book text so repeat visits and back navigation feel instant.
- Reader built for long reads — Literata for body text, adjustable font size (14–28px), dark and sepia themes, and a progress bar. Chapter detection handles common patterns (Chapter I, Book 1, Part One, etc.); fallback sectioning for untagged texts.
- Progress and “Continue reading” — Current book and chapter are saved in
localStorage; the sidebar and home section show recently read books and let you jump back in. - Responsive layout — Sidebar + main content on desktop; top bar and bottom nav on small screens. Touch-friendly controls and readable line lengths.
- Accessibility — Skip link, semantic HTML, ARIA where needed, keyboard-friendly reader (arrows for prev/next chapter, Escape to close panels).
- Performance and resilience — Timeouts and retries for external APIs; graceful fallbacks when a category or search fails; no hanging requests.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19 |
| Styling | Vanilla CSS with custom properties (design tokens in globals.css), CSS Modules per route/component |
| Fonts | Inter (UI) and Literata (reader) via next/font |
| Motion | Framer Motion for page and panel transitions |
| Data | Gutendex API for metadata and listing; Project Gutenberg for plain-text content (proxied via app API to avoid CORS) |
| State / persistence | React state + localStorage for theme, font size, and reading progress |
No database, no auth, no Tailwind — the goal was a small, readable stack that’s easy to run and extend.
read-a-book/
├── app/
│ ├── layout.js # Root layout, fonts, theme script, shell (Sidebar, TopBar, MobileNav)
│ ├── page.js # Home (SSR) → HomePageClient
│ ├── page.server.js # getHomePageData() — parallel fetch for all featured rows
│ ├── globals.css # Design tokens, themes, base styles
│ ├── error.js # Global error boundary
│ ├── not-found.js # 404 page
│ │
│ ├── api/
│ │ └── books/[id]/text/
│ │ └── route.js # GET book plain text (proxy + server-side LRU cache, timeouts)
│ │
│ ├── browse/
│ │ ├── page.js # Browse (SSR) → BrowseContent
│ │ ├── page.server.js # getBrowsePageData(topic)
│ │ └── BrowseContent.jsx # Client: filters, infinite scroll, cache hydration
│ │
│ ├── search/
│ │ ├── page.js # Search (SSR when ?q=) → SearchContent
│ │ ├── page.server.js # getSearchPageData(query)
│ │ └── SearchContent.jsx # Client: debounced search, results grid
│ │
│ ├── book/[id]/
│ │ ├── page.js # Book detail (client, fetchWithCache)
│ │ └── read/
│ │ └── page.js # Reader: text fetch, chapters, TOC, theme, font size, progress
│ │
│ ├── components/ # Sidebar, TopBar, MobileNav, BookCard, ScrollRow, etc.
│ ├── hooks/ # useTheme, useReadingProgress, useRecentlyRead, useRecentBooks
│ └── lib/
│ ├── api.js # Gutendex helpers, parseBookText, getBookTextUrl, etc.
│ ├── apiCache.js # Client cache (getCachedData, fetchWithCache, prefetch, setCachedData)
│ └── serverApi.js # Server fetch with timeout + retry, getBooksParallel
│
├── public/ # Static assets and UI screenshots (1.png, image2–4.png)
├── next.config.mjs # Security headers, image domains (gutenberg, wikimedia)
├── CONTRIBUTING.md
├── LICENSE
└── README.md
Prerequisites: Node.js 18+ and npm (or equivalent).
git clone https://github.com/YOUR_USERNAME/read-a-book.git
cd read-a-book
npm install
npm run devOpen http://localhost:3000. No env vars are required; the app talks to public APIs only.
Production build:
npm run build
npm startLint:
npm run lint- Themes — Dark (default) and sepia; stored in
localStorageand applied before paint to avoid flash. - Reader typography — Single book-style font (Literata) for body and drop cap; chapter titles stay in the UI font for hierarchy.
- Loading states — Skeletons on list and detail; reader shows a skeleton while book text is loading. Cached data is used when available to avoid unnecessary spinners on back navigation.
- Error handling — Failed API calls show clear messages and retry options; the global error boundary catches runtime errors and offers “Try again.”
Contributions are welcome. Please read CONTRIBUTING.md for how to propose changes and submit patches.
This project is licensed under the MIT License — see LICENSE for the full text.
- Project Gutenberg for preserving and providing public-domain texts.
- Gutendex for the free JSON API over Gutenberg metadata.



