A full-stack EPUB reader: look up words while reading, review with Ebbinghaus intervals (1→30 days), plus study stats, leaderboards, and an English / Chinese UI.
Try it online: https://english-read.bitbw.top/
Overview
English Read is a Next.js 15 full-stack web application combining an online EPUB reader with an SRS (Spaced Repetition System) vocabulary learning workflow. Upload EPUBs or browse a shared public library, look up words while reading, and review them with an Ebbinghaus-curve-based flashcard system.
Features
- EPUB Reader — paginated reading, font size control, reading progress auto-saved
- Personal Library & Public Library — upload to your shelf; browse and add books from a shared public catalog
- Vocabulary — collect unknown words from the reader; optional review plan view
- Spaced Repetition Review — Ebbinghaus intervals: 1d → 2d → 4d → 7d → 15d → 30d → mastered; phrase (multi-word) review distractors can use Vercel AI Gateway when configured; single-word paths may not need it
- Study Statistics — reading time, average speed (wpm), words reviewed, errors, and review time; bar charts plus daily breakdown; filter by last 7 / 14 / 30 days or a custom range (learning timezone)
- Leaderboard — community popular public books; reading, review, and overall user rankings (time, speed, streak, study score, etc.); opt out of user boards in Settings
- Dictionary & Translation — English definitions (Free Dictionary API) + Chinese translation (MyMemory); optional Google Cloud Translation fallback; cached 24 h
- Authentication — GitHub and Google OAuth; email/password; phone OTP (Aliyun SMS) via NextAuth v5 with JWT sessions
- Internationalization — English / Chinese UI (next-intl, cookie-based, URL unchanged)
- Dark Mode — system-aware theme toggle
- Observability — Sentry error monitoring; optional PostHog and Vercel Analytics
Tech Stack
| Layer | Technology |
|---|
| Framework | Next.js 15 + React 19 (App Router) |
| Language | TypeScript (strict) |
| UI | shadcn/ui v4 (@base-ui/react), Tailwind CSS v4 |
| Auth | NextAuth v5 (OAuth + Credentials + phone OTP), JWT sessions |
| Database | Drizzle ORM + Neon serverless PostgreSQL |
| File Storage | Vercel Blob |
| i18n | next-intl ^4.9.1 |
| EPUB Engine | epubjs |
| Errors | @sentry/nextjs |
| Analytics | Optional: PostHog (posthog-js), @vercel/analytics |
Getting Started
Prerequisites
- Node.js 18+ (Node 20 LTS recommended for local dev and production)
- npm — this repo ships
package-lock.json; npm is the documented package manager (yarn/pnpm work only if you align lockfiles yourself)
Installation
git clone <repo-url>
cd english-read
npm install
Environment Variables
Create .env.local in the project root. Minimum for local auth + DB + uploads:
AUTH_SECRET=
AUTH_URL=http://localhost:5000
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
POSTGRES_URL= # Neon pooled connection
POSTGRES_URL_NON_POOLING= # Neon direct connection (used by drizzle-kit)
BLOB_READ_WRITE_TOKEN= # Vercel Blob
Generate AUTH_SECRET with npx auth secret.
Optional integrations (SMS login, AI Gateway for phrase review distractors, translation fallback, analytics) are documented in [.env.example](#).
Database Setup
npx drizzle-kit generate # Generate migration files from schema
npm run db:migrate # Apply migrations (alias for drizzle-kit migrate)
# or: npx drizzle-kit migrate
npx drizzle-kit studio # Open Drizzle Studio (DB browser)
Development
npm run dev # Dev server at http://localhost:5000
npm run build # Production build (includes ESLint)
npm run start # Start production server after build
npm run lint # ESLint only
npx tsc --noEmit # TypeScript check only
Project Structure
src/
├── app/
│ ├── (app)/ # Authenticated shell (Sidebar + Topbar)
│ │ ├── dashboard/
│ │ ├── dashboard/stats/ # Study statistics
│ │ ├── leaderboard/ # Community & user rankings
│ │ ├── library/ # Personal books + upload
│ │ ├── library/store/ # Public library browse & contribute
│ │ ├── vocabulary/ # Word list
│ │ ├── vocabulary/review/ # SRS flashcards
│ │ ├── vocabulary/plan/ # Review planning
│ │ ├── read/[bookId]/ # Full-screen EPUB reader
│ │ └── settings/
│ ├── (auth)/ # login, signup, error (no app shell)
│ ├── api/
│ └── dev/ # Internal/dev-only pages (optional)
├── components/
│ ├── reader/
│ └── ui/
├── lib/
│ ├── db/ # Drizzle schema, migrations, db client
│ ├── srs.ts # Spaced repetition intervals
│ ├── blob.ts # Vercel Blob uploads
│ ├── auth.ts # NextAuth config
│ ├── review-quiz.ts # Review / distractor logic (example)
│ └── … # reading-time, phone-auth, dictionary helpers, etc.
├── i18n/
└── middleware.ts
messages/
├── en.json
└── zh.json
The src/lib/ listing above is not exhaustive — browse the folder for the full set of helpers (e.g. reading-time.ts, phone-auth.ts, aliyun-dypns.ts).
Architecture Notes
Authentication
middleware.ts protects /dashboard, /leaderboard, /library, /read, /vocabulary, and /settings. The app session is JWT (session.strategy: "jwt"); the sessions table remains for the Drizzle adapter / OAuth linking, not as the primary per-request session store. Providers include GitHub, Google, email/password (hashed with bcrypt), and phone OTP (requires Aliyun env vars). Logged-in users hitting /login or /signup are redirected to /dashboard.
EPUB Reader
EpubReader is dynamically imported with { ssr: false }. Paginated flow with pixel dimensions from getBoundingClientRect(); touch swipe is registered on each iframe view.window for multi-iframe layouts.
Spaced Repetition
Stages: 0→1d, 1→2d, 2→4d, 3→7d, 4→15d, 5→30d, 6+→mastered. “Forgot” resets to stage 0. The review queue uses vocabulary.nextReviewAt ≤ now.
UI Components
shadcn/ui v4 on @base-ui/react (not Radix):
- No
asChild — use render={<Link href="..." />}
- In Server Components, import
buttonVariants from @/components/ui/button-variants
Deployment guide
Hosting target is Vercel. Recommended flow: create Storage → register OAuth apps → copy env into .env.local → run npm run db:migrate once against Neon → connect the GitHub repo and deploy. Migration SQL lives in src/lib/db/migrations/ ([drizzle.config.ts](#) uses POSTGRES_URL_NON_POOLING). Optional env keys are listed in [.env.example](#).
1. Create Vercel Storage
Open the Vercel Dashboard → select or create your project → Storage, then provision:
Postgres (Neon)
| Field | Value |
|---|
| Type | Postgres |
| Purpose | App data: users, books, vocabulary, reviews, etc. |
| Auto-injected env | POSTGRES_URL, POSTGRES_URL_NON_POOLING |
Link the database to your Vercel project so these variables appear under Settings → Environment Variables (or copy them from the Storage UI for local .env.local).
Blob
| Field | Value |
|---|
| Type | Blob |
| Suggested name | english-read-epub |
| Purpose | Uploaded EPUB files |
| Auto-injected env | BLOB_READ_WRITE_TOKEN |
2. Create OAuth applications