diff --git a/docs-site/versioned_docs/version-v2.0.0/about/acknowledgements.md b/docs-site/versioned_docs/version-v2.0.0/about/acknowledgements.md
new file mode 100644
index 0000000..0a6c5ec
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/about/acknowledgements.md
@@ -0,0 +1,16 @@
+---
+title: Acknowledgements
+---
+
+This project is built with support from the following open-source projects and tools:
+
+- [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M)
+- [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI)
+- [Better Auth](https://www.better-auth.com/)
+- [SQLite](https://www.sqlite.org/)
+- [PostgreSQL](https://www.postgresql.org/)
+- [SeaweedFS](https://github.com/seaweedfs/seaweedfs)
+- [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
+- [ffmpeg](https://ffmpeg.org)
+- [react-pdf](https://github.com/wojtekmaj/react-pdf)
+- [react-reader](https://github.com/happyr/react-reader)
diff --git a/docs-site/versioned_docs/version-v2.0.0/about/license.md b/docs-site/versioned_docs/version-v2.0.0/about/license.md
new file mode 100644
index 0000000..24af383
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/about/license.md
@@ -0,0 +1,7 @@
+---
+title: License
+---
+
+OpenReader is licensed under the MIT License.
+
+- Repository license file: [LICENSE](https://github.com/richardr1126/openreader/blob/main/LICENSE)
diff --git a/docs-site/versioned_docs/version-v2.0.0/about/support-and-contributing.md b/docs-site/versioned_docs/version-v2.0.0/about/support-and-contributing.md
new file mode 100644
index 0000000..cef6224
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/about/support-and-contributing.md
@@ -0,0 +1,19 @@
+---
+title: Support and Contributing
+---
+
+## Feature requests
+
+Use [GitHub Discussions](https://github.com/richardr1126/openreader/discussions) for feature requests and ideas.
+
+## Issues and support
+
+If you encounter a bug, open an issue in [GitHub Issues](https://github.com/richardr1126/openreader/issues).
+
+## Contributing
+
+Contributions are welcome.
+
+- Fork the repository
+- Create your branch
+- Open a pull request with your changes
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/auth.md b/docs-site/versioned_docs/version-v2.0.0/configure/auth.md
new file mode 100644
index 0000000..c89400c
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/auth.md
@@ -0,0 +1,46 @@
+---
+title: Auth
+---
+
+This page covers application-level configuration for provider access and authentication.
+
+## Auth behavior
+
+- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
+- Remove either value to disable auth.
+- Keep `AUTH_TRUSTED_ORIGINS` empty to trust only `BASE_URL`.
+- Anonymous auth sessions are disabled by default.
+- Set `USE_ANONYMOUS_AUTH_SESSIONS=true` to enable anonymous session flows.
+
+## Route behavior
+
+- `/` is a public landing/onboarding page and remains indexable.
+- `/app` is the protected app home (document list and uploader UI).
+- If auth is enabled and a valid session exists (including anonymous), visiting `/` redirects to `/app`.
+- Protected app routes continue to require auth; when anonymous sessions are disabled and no session exists, users are redirected to `/signin`.
+
+## Related docs
+
+- For auth environment variables: [Environment Variables](../reference/environment-variables#auth-and-identity)
+- For TTS character limits and quota behavior: [TTS Rate Limiting](./tts-rate-limiting)
+- For provider-specific guidance: [TTS Providers](./tts-providers)
+- For storage/S3/SeaweedFS behavior: [Object / Blob Storage](./object-blob-storage)
+- For database mode: [Database](./database)
+- For migration behavior and commands: [Migrations](./migrations)
+
+## Sync notes
+
+### Auth enabled
+
+- Settings and reading progress are saved to the server.
+- Updates are not instant push-based sync; they use normal client polling/refresh behavior.
+- If two devices change the same item around the same time, the newest update wins.
+
+### Auth disabled
+
+- Settings and reading progress stay local in the browser (Dexie/IndexedDB).
+- This avoids no-auth cross-browser conflicts, but there is no cross-device sync.
+
+## Claim modal note
+
+- You may still see old anonymous settings/progress available to claim from older deployments.
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/database.md b/docs-site/versioned_docs/version-v2.0.0/configure/database.md
new file mode 100644
index 0000000..45586af
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/database.md
@@ -0,0 +1,39 @@
+---
+title: Database
+---
+
+This page covers database mode selection for OpenReader.
+
+## Database mode
+
+- SQLite (default): embedded DB at `docstore/sqlite3.db`; good for local/self-host single-instance setups.
+- Postgres: enabled when `POSTGRES_URL` is set; recommended for production/distributed deployments.
+
+## What the database stores
+
+- Document and audiobook metadata/state used by server routes.
+- Auth/session tables (`user`, `session`, `account`, `verification`) when auth is enabled — schema is auto-generated by Better Auth.
+- TTS character usage counters (`user_tts_chars`) for daily rate limiting (when enabled).
+- User settings preferences (`user_preferences`) when auth is enabled.
+- User reading progress (`user_document_progress`) when auth is enabled.
+- Document preview job/asset metadata (`document_previews`) for server-side PDF/EPUB thumbnails.
+
+App-specific tables are manually maintained in Drizzle schema files, while auth tables are generated by the Better Auth CLI. Both are migrated together via Drizzle. See [Migrations](./migrations) for details.
+
+## Related variables
+
+- `POSTGRES_URL`
+
+For database variable behavior, see [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
+
+## Related docs
+
+- [Migrations](./migrations)
+- [Object / Blob Storage](./object-blob-storage)
+- [Auth](./auth)
+
+## State sync summary
+
+- With auth enabled, settings and reading progress are stored in SQL and synced from the app.
+- With auth disabled, settings and reading progress remain local in the browser.
+- Sync is currently request-based (not realtime push invalidation).
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/migrations.md b/docs-site/versioned_docs/version-v2.0.0/configure/migrations.md
new file mode 100644
index 0000000..66c8f4b
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/migrations.md
@@ -0,0 +1,132 @@
+---
+title: Migrations
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+This page covers migration behavior for both database schema and storage data in OpenReader.
+
+## Startup migration behavior
+
+By default, the shared entrypoint runs migrations automatically before app startup in:
+
+- Docker container startup
+- `pnpm dev`
+- `pnpm start`
+
+Startup migration phases:
+
+- DB schema migrations (`pnpm migrate`)
+- Storage/data migration (`pnpm migrate-fs`) for legacy filesystem content into S3 + DB rows
+
+:::info
+In most setups, you do not need to run migration commands manually because startup handles this automatically.
+:::
+
+To skip automatic startup migrations:
+
+- Set `RUN_DRIZZLE_MIGRATIONS=false`
+- Set `RUN_FS_MIGRATIONS=false`
+
+:::warning
+If you disable startup migrations, ensure your deployment process runs migrations before serving traffic.
+:::
+
+## Apply migrations
+
+In most cases, you do not need manual migration commands because startup runs migrations automatically.
+
+`pnpm migrate` applies migrations for one database target:
+
+- Postgres when `POSTGRES_URL` is set
+- SQLite when `POSTGRES_URL` is unset
+
+You can always override the target explicitly with `--config`.
+
+
+
+
+```bash
+# Run pending migrations for one target:
+# - Postgres if POSTGRES_URL is set
+# - SQLite if POSTGRES_URL is unset
+pnpm migrate
+
+# Run storage migration (filesystem -> S3 + DB)
+pnpm migrate-fs
+
+# Dry-run storage migration without uploading/deleting
+pnpm migrate-fs:dry-run
+```
+
+
+
+
+```bash
+# Migrate SQLite
+pnpm exec drizzle-kit migrate --config drizzle.config.sqlite.ts
+
+# Migrate Postgres
+pnpm exec drizzle-kit migrate --config drizzle.config.pg.ts
+```
+
+
+
+
+## Generate migrations
+
+`pnpm generate` is a two-phase script for contributors and schema changes:
+
+1. **Better Auth schema generation** — runs the Better Auth CLI twice (once for SQLite, once for Postgres) to produce auto-generated Drizzle schema files for auth tables (`user`, `session`, `account`, `verification`).
+2. **Drizzle migration generation** — runs `drizzle-kit generate` for both `drizzle.config.sqlite.ts` and `drizzle.config.pg.ts`, producing SQL migration files from all schema files (app + auth).
+
+:::note
+Most users do not need to run `pnpm generate`. Use it when contributing or when you have changed Drizzle schema files and need new migration files.
+:::
+
+### Schema ownership
+
+Auth tables are owned by Better Auth. Their Drizzle schema definitions are auto-generated and should **not** be hand-edited:
+
+- `src/db/schema_auth_sqlite.ts`
+- `src/db/schema_auth_postgres.ts`
+
+App-specific tables are manually maintained in the standard Drizzle schema files:
+
+- `src/db/schema_sqlite.ts`
+- `src/db/schema_postgres.ts`
+
+Both sets of schema files are included in the Drizzle configs, so `drizzle-kit generate` and `drizzle-kit migrate` handle all tables together.
+
+
+
+
+```bash
+# Full pipeline: Better Auth CLI + Drizzle generate (both dialects)
+pnpm generate
+```
+
+
+
+
+```bash
+# Generate SQLite migrations only (skips Better Auth CLI)
+pnpm exec drizzle-kit generate --config drizzle.config.sqlite.ts
+
+# Generate Postgres migrations only (skips Better Auth CLI)
+pnpm exec drizzle-kit generate --config drizzle.config.pg.ts
+```
+
+:::warning
+Running `drizzle-kit generate` directly skips the Better Auth CLI step. If auth schema has changed upstream (e.g. after a Better Auth version bump), run `pnpm generate` instead to regenerate the auth schema files first.
+:::
+
+
+
+
+## Related docs
+
+- [Database](./database)
+- [Object / Blob Storage](./object-blob-storage)
+- [Migration Environment Variables](../reference/environment-variables#migration-controls)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/object-blob-storage.md b/docs-site/versioned_docs/version-v2.0.0/configure/object-blob-storage.md
new file mode 100644
index 0000000..ff46fd1
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/object-blob-storage.md
@@ -0,0 +1,103 @@
+---
+title: Object / Blob Storage
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+This page documents storage backends, blob upload routing, and core Docker mount behavior.
+
+## Storage backends
+
+- Embedded (default): SQLite metadata + embedded SeaweedFS (`weed mini`) blobs.
+- External: Postgres + external S3-compatible object storage.
+
+Storage variables are documented in [Environment Variables](../reference/environment-variables#database-and-object-blob-storage).
+
+## Ports
+
+- `3003`: OpenReader app and API routes
+- `8333`: Embedded SeaweedFS S3 endpoint for direct browser blob access
+
+:::info
+`8333` is only needed for direct browser presigned access to embedded SeaweedFS.
+:::
+
+## Upload behavior
+
+- Primary path: browser uploads to presigned URL from `/api/documents/blob/upload/presign`.
+- Fallback path: `/api/documents/blob/upload/fallback` when direct upload fails/unreachable.
+- Read/download path: blob/content serving route `/api/documents/blob` (not the upload fallback route).
+- Preview path: `/api/documents/blob/preview` (returns `202` while a preview is generating; serves/redirects when ready).
+
+## Document previews
+
+- PDF/EPUB previews are generated server-side and stored in object storage under `document_previews_v1`.
+- Preview generation is triggered on upload registration and also backfills on first preview request for older docs.
+- Preview artifacts are temporary-cache friendly and can be regenerated from the source document blob.
+
+## FS / Volume Mounts
+
+### App data mount
+
+- Target: `/app/docstore`
+- Recommended: yes, for persistence
+- Purpose: persists SeaweedFS blob data, SQLite metadata DB, migrations, and local runtime temp state
+- Mount string: `-v openreader_docstore:/app/docstore`
+
+### Library source mount (optional)
+
+- Target: `/app/docstore/library`
+- Recommended: optional, use read-only (`:ro`)
+- Purpose: exposes host files as a source for server library import
+- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
+- Details: [Server Library Import](./server-library-import)
+
+## Private blob endpoint mode
+
+If `8333` is not published externally:
+
+- Document uploads still work through upload fallback proxy
+- Reads/snippets continue through app API routes
+- Direct presigned browser upload/download to embedded endpoint is unavailable
+
+:::warning
+Without `8333`, expect higher app-server traffic because uploads/downloads go through API routes instead of direct object endpoint access.
+:::
+
+## Audiobook Storage Debug Commands
+
+Audiobook assets are stored in object storage under the `audiobooks_v1` keyspace. Use these commands to inspect and download objects for debugging.
+
+
+
+
+```bash
+# List all audiobook objects
+aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive
+
+# Filter to one book id (replace )
+aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive | grep "-audiobook/"
+
+# Download one object by full key
+aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1//.m4b" "./audiobook.m4b"
+```
+
+
+
+
+```bash
+# List all audiobook objects
+aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive --endpoint-url "$S3_ENDPOINT"
+
+# Filter to one book id (replace )
+aws s3 ls "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1/" --recursive --endpoint-url "$S3_ENDPOINT" | grep "-audiobook/"
+
+# Download one object by full key
+aws s3 cp "s3://$S3_BUCKET/$S3_PREFIX/audiobooks_v1//.m4b" "./audiobook.m4b" --endpoint-url "$S3_ENDPOINT"
+```
+
+Embedded default example: `S3_ENDPOINT=http://127.0.0.1:8333` (or your mapped host/port).
+
+
+
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/server-library-import.md b/docs-site/versioned_docs/version-v2.0.0/configure/server-library-import.md
new file mode 100644
index 0000000..acdecd2
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/server-library-import.md
@@ -0,0 +1,68 @@
+---
+title: Server Library Import
+---
+
+This page documents how server library import works and how to configure it.
+
+## What it does
+
+Server library import lets you browse files from one or more server directories and import selected files into OpenReader.
+
+- Import is user-driven via a selection modal
+- Only selected files are imported
+- Imported files become normal OpenReader documents
+
+## FS / Volume Mounts
+
+### App data mount
+
+- Target: `/app/docstore`
+- Recommended: yes, for persistence
+- Purpose: stores app runtime data, metadata DB, and embedded storage state
+- Mount string: `-v openreader_docstore:/app/docstore`
+
+### Library source mount
+
+- Target: `/app/docstore/library`
+- Recommended: yes for this feature, use read-only (`:ro`)
+- Purpose: exposes host files as import candidates in Server Library Import
+- Mount string: `-v /path/to/your/library:/app/docstore/library:ro`
+
+## Import flow
+
+1. Open **Settings -> Documents -> Server Library Import**.
+2. Select files in the modal.
+3. Click **Import**.
+
+Selected files are fetched from the server library endpoint and imported into OpenReader storage.
+
+:::warning Shared Library Roots
+Library roots are configured at the server level (not per-user). Any user who can access Server Library Import can browse/import from the same configured roots.
+
+Imported documents are still saved to the importing user's document scope.
+:::
+
+## Supported file types
+
+- `.pdf`
+- `.epub`
+- `.html`, `.htm`
+- `.txt`
+- `.md`, `.mdown`, `.markdown`
+
+## Optional: Configure Library Roots
+
+You only need this when the default mounted path is not what you want.
+
+By default, OpenReader uses `docstore/library` as the import root. You can override that with environment variables:
+
+- `IMPORT_LIBRARY_DIRS` (takes precedence): multiple roots separated by comma, colon, or semicolon
+- `IMPORT_LIBRARY_DIR`: single root
+
+See [Environment Variables](../reference/environment-variables#library-import) for variable details.
+
+## Notes
+
+- Library listing is capped per request (up to 10,000 files).
+- When auth is enabled, library import endpoints require a valid session.
+- The mounted library is a source; removing it does not delete already imported documents.
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/custom-openai.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/custom-openai.md
new file mode 100644
index 0000000..6822333
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/custom-openai.md
@@ -0,0 +1,40 @@
+---
+title: Custom OpenAI
+---
+
+Use any custom OpenAI-compatible TTS service with OpenReader.
+
+Use this integration when your endpoint is not directly covered by built-in dropdown defaults.
+
+## Provider
+
+- Provider: `Custom OpenAI-Like`
+- `API_BASE`: required (your service base URL)
+- `API_KEY`: set if required by your service
+
+Custom providers should expose:
+
+- `GET /v1/audio/voices`
+- `POST /v1/audio/speech`
+
+## OpenReader setup
+
+1. In OpenReader Settings, choose provider `Custom OpenAI-Like`.
+2. Set `API_BASE` to your service base URL (typically ending with `/v1`).
+3. Set `API_KEY` if your service requires authentication.
+4. Choose a model and voice supported by your backend.
+
+## Notes
+
+:::warning Compatibility required
+Custom providers must implement OpenAI-compatible TTS endpoints, including `GET /v1/audio/voices` and `POST /v1/audio/speech`.
+:::
+
+:::info Voice troubleshooting
+If voices do not load, verify the `/v1/audio/voices` response shape and that the endpoint is reachable from the OpenReader server.
+:::
+
+## References
+
+- [TTS Providers](../tts-providers)
+- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/deepinfra.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/deepinfra.md
new file mode 100644
index 0000000..f7f3116
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/deepinfra.md
@@ -0,0 +1,33 @@
+---
+title: Deepinfra
+---
+
+Use Deepinfra as a hosted OpenAI-compatible TTS provider.
+
+## Provider
+
+- Provider: `Deepinfra`
+- Default endpoint: `https://api.deepinfra.com/v1/openai` (auto-filled)
+- `API_KEY`: required for authenticated DeepInfra usage
+
+## OpenReader setup
+
+1. In OpenReader Settings, choose provider `Deepinfra`.
+2. Keep the default `API_BASE`.
+3. Set `API_KEY`.
+4. Choose your model and voice.
+
+## Notes
+
+:::tip Built-in endpoint
+`Deepinfra` is a built-in provider, so OpenReader auto-fills the default `API_BASE`.
+:::
+
+:::info Model support
+DeepInfra exposes multiple TTS models, including Kokoro-family options.
+:::
+
+## References
+
+- [TTS Providers](../tts-providers)
+- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/kokoro-fastapi.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/kokoro-fastapi.md
new file mode 100644
index 0000000..0d6d03c
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/kokoro-fastapi.md
@@ -0,0 +1,68 @@
+---
+title: Kokoro-FastAPI
+---
+
+You can run the Kokoro TTS API server directly with Docker.
+
+:::warning
+For Kokoro issues and support, use the upstream repository: [remsky/Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI).
+:::
+
+## Provider
+
+- Provider: `Custom OpenAI-Like`
+- Typical model: `Kokoro`
+- `API_BASE`: required (typically your Kokoro URL ending with `/v1`)
+- `API_KEY`: set only if your deployment requires one
+
+## Run Kokoro (CPU)
+
+```bash
+docker run --name kokoro-tts \
+ --restart unless-stopped \
+ -d \
+ -p 8880:8880 \
+ -e ONNX_NUM_THREADS=8 \
+ -e ONNX_INTER_OP_THREADS=4 \
+ -e ONNX_EXECUTION_MODE=parallel \
+ -e ONNX_OPTIMIZATION_LEVEL=all \
+ -e ONNX_MEMORY_PATTERN=true \
+ -e ONNX_ARENA_EXTEND_STRATEGY=kNextPowerOfTwo \
+ -e API_LOG_LEVEL=DEBUG \
+ ghcr.io/remsky/kokoro-fastapi-cpu:v0.2.4
+```
+
+## Run Kokoro (GPU)
+
+```bash
+docker run --name kokoro-tts \
+ --restart unless-stopped \
+ -d \
+ --gpus all \
+ --user 1001:1001 \
+ -p 8880:8880 \
+ -e USE_GPU=true \
+ -e PYTHONUNBUFFERED=1 \
+ -e API_LOG_LEVEL=DEBUG \
+ ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.4
+```
+
+## OpenReader setup
+
+1. Start Kokoro using either the CPU or GPU image.
+2. In OpenReader Settings, choose provider `Custom OpenAI-Like`.
+3. Set `API_BASE` to your Kokoro endpoint (for Docker Compose, commonly `http://kokoro-tts:8880/v1`).
+4. Set `API_KEY` only if your deployment requires one.
+5. Choose model `Kokoro`.
+
+## Notes
+
+:::tip Runtime guidance
+GPU mode requires NVIDIA Docker support and is best on NVIDIA hardware. CPU mode is a good default on Apple Silicon and modern x86 CPUs.
+:::
+
+## References
+
+- [remsky/Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI)
+- [TTS Providers](../tts-providers)
+- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/openai.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/openai.md
new file mode 100644
index 0000000..995c074
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/openai.md
@@ -0,0 +1,33 @@
+---
+title: OpenAI
+---
+
+Use OpenAI directly as an OpenAI-compatible TTS provider.
+
+## Provider
+
+- Provider: `OpenAI`
+- Default endpoint: `https://api.openai.com/v1` (auto-filled)
+- `API_KEY`: required for OpenAI access
+
+## OpenReader setup
+
+1. In OpenReader Settings, choose provider `OpenAI`.
+2. Keep the default `API_BASE`.
+3. Set `API_KEY`.
+4. Choose your model and voice.
+
+## Notes
+
+:::tip Built-in endpoint
+`OpenAI` is a built-in provider, so OpenReader auto-fills the default `API_BASE`.
+:::
+
+:::info Server-side requests
+OpenReader sends TTS requests from the server runtime, not directly from the browser.
+:::
+
+## References
+
+- [TTS Providers](../tts-providers)
+- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/orpheus-fastapi.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/orpheus-fastapi.md
new file mode 100644
index 0000000..ba4ab5c
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-provider-guides/orpheus-fastapi.md
@@ -0,0 +1,36 @@
+---
+title: Orpheus-FastAPI
+---
+
+Use Orpheus-FastAPI as an OpenAI-compatible TTS backend for OpenReader.
+
+## Provider
+
+- Provider: `Custom OpenAI-Like`
+- Typical model: `Orpheus`
+- `API_BASE`: required (usually your Orpheus URL ending with `/v1`)
+- `API_KEY`: set only if your deployment requires one
+
+## OpenReader setup
+
+1. Start your Orpheus-FastAPI server.
+2. In OpenReader Settings, choose provider `Custom OpenAI-Like`.
+3. Set `API_BASE` to your Orpheus base URL (typically ending with `/v1`).
+4. Set `API_KEY` only if your Orpheus deployment requires one.
+5. Choose model `Orpheus` (or another model exposed by your deployment).
+
+## Notes
+
+:::info OpenAI-compatible API
+OpenReader expects OpenAI-compatible audio endpoints when using Orpheus through `Custom OpenAI-Like`.
+:::
+
+:::tip Endpoint shape
+Use an `API_BASE` that points at the Orpheus API root (typically ending with `/v1`).
+:::
+
+## References
+
+- [Lex-au/Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI)
+- [TTS Providers](../tts-providers)
+- [TTS Environment Variables](../../reference/environment-variables#tts-provider-and-request-behavior)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-providers.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-providers.md
new file mode 100644
index 0000000..296a0b1
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-providers.md
@@ -0,0 +1,79 @@
+---
+title: TTS Providers
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+OpenReader supports OpenAI-compatible TTS providers through a common API shape.
+
+:::tip
+If you are running a self-hosted TTS server (Kokoro/Orpheus/etc.), use **Custom OpenAI-Like** in Settings.
+:::
+
+## Quick Setup by Provider
+
+
+
+
+1. In Settings, choose provider: `OpenAI`.
+2. Keep the default `API_BASE` (auto-filled).
+3. Set `API_KEY` to your OpenAI key.
+4. Choose model/voice.
+
+
+
+
+1. In Settings, choose provider: `Deepinfra`.
+2. Keep the default `API_BASE` (auto-filled).
+3. Set `API_KEY` to your DeepInfra key.
+4. Choose model/voice.
+
+
+
+
+1. In Settings, choose provider: `Custom OpenAI-Like`.
+2. Set `API_BASE` to your endpoint (example: `http://host.docker.internal:8880/v1`).
+3. Set `API_KEY` if your provider requires one.
+4. Choose model/voice.
+
+
+
+
+## Provider Dropdown Behavior
+
+In Settings, provider options include:
+
+- `OpenAI`
+- `Deepinfra`
+- `Custom OpenAI-Like` (Kokoro, Orpheus, and other OpenAI-compatible endpoints)
+
+`API_BASE` guidance:
+
+- `OpenAI` and `Deepinfra` auto-fill default endpoints.
+- `Custom OpenAI-Like` requires setting `API_BASE` manually.
+
+:::info OpenAI-Compatible API Shape
+Custom providers should expose:
+
+- `GET /v1/audio/voices`
+- `POST /v1/audio/speech`
+:::
+
+:::warning Server-Reachable API Base
+TTS requests are sent from the Next.js server, not directly from the browser. `API_BASE` must be reachable from the server runtime.
+:::
+
+## Provider Guides
+
+- [Kokoro-FastAPI](./tts-provider-guides/kokoro-fastapi)
+- [Orpheus-FastAPI](./tts-provider-guides/orpheus-fastapi)
+- [DeepInfra](./tts-provider-guides/deepinfra)
+- [OpenAI](./tts-provider-guides/openai)
+- [Custom OpenAI-Like](./tts-provider-guides/custom-openai)
+
+## Related Configuration
+
+- [TTS Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
+- [TTS Rate Limiting](./tts-rate-limiting)
+- [Auth](./auth)
diff --git a/docs-site/versioned_docs/version-v2.0.0/configure/tts-rate-limiting.md b/docs-site/versioned_docs/version-v2.0.0/configure/tts-rate-limiting.md
new file mode 100644
index 0000000..ed507d1
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/configure/tts-rate-limiting.md
@@ -0,0 +1,51 @@
+---
+title: TTS Rate Limiting
+---
+
+This page explains OpenReader's TTS character rate limiting controls.
+
+## Overview
+
+- TTS rate limiting is disabled by default.
+- To enable it, set `TTS_ENABLE_RATE_LIMIT=true`.
+- Limits are enforced per day in UTC.
+- Enforcement applies only when auth is enabled.
+
+## How enforcement works
+
+When enabled, OpenReader enforces:
+
+- Per-user daily character limits.
+- IP backstop daily character limits.
+- Anonymous device backstop tracking (cookie-based) to reduce limit resets.
+
+If a request exceeds the active limit, the TTS API returns `429` with reset metadata for the next UTC day.
+
+## Required auth behavior
+
+- Auth must be enabled (`BASE_URL` + `AUTH_SECRET`) for TTS char limits to apply.
+- If auth is disabled, TTS character limits are effectively unlimited.
+- `DISABLE_AUTH_RATE_LIMIT` only affects Better Auth's own request throttling.
+- `DISABLE_AUTH_RATE_LIMIT` does not disable TTS character limits.
+
+## Environment variables
+
+Enable/disable:
+
+- `TTS_ENABLE_RATE_LIMIT` (default: `false`)
+
+Per-user daily limits:
+
+- `TTS_DAILY_LIMIT_ANONYMOUS` (default: `50000`)
+- `TTS_DAILY_LIMIT_AUTHENTICATED` (default: `500000`)
+
+IP backstop daily limits:
+
+- `TTS_IP_DAILY_LIMIT_ANONYMOUS` (default: `100000`)
+- `TTS_IP_DAILY_LIMIT_AUTHENTICATED` (default: `1000000`)
+
+## Related docs
+
+- TTS/rate-limit environment variables: [Environment Variables](../reference/environment-variables#tts-provider-and-request-behavior)
+- Auth configuration: [Auth](./auth)
+- Provider setup: [TTS Providers](./tts-providers)
diff --git a/docs-site/versioned_docs/version-v2.0.0/deploy/local-development.md b/docs-site/versioned_docs/version-v2.0.0/deploy/local-development.md
new file mode 100644
index 0000000..dedc337
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/deploy/local-development.md
@@ -0,0 +1,138 @@
+---
+title: Local Development
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Prerequisites
+
+- Node.js (recommended with [nvm](https://github.com/nvm-sh/nvm))
+- `pnpm` (recommended) or `npm`
+
+```bash
+npm install -g pnpm
+```
+
+- A reachable TTS API server
+- [SeaweedFS](https://github.com/seaweedfs/seaweedfs) `weed` binary (required unless using external S3 storage)
+
+
+
+
+```bash
+brew install seaweedfs
+```
+
+
+
+
+Install the `weed` binary from the [SeaweedFS releases](https://github.com/seaweedfs/seaweedfs/releases) and ensure it is available on `PATH`.
+
+
+
+
+Optional, depending on features:
+
+- [libreoffice](https://www.libreoffice.org) (required for DOCX conversion)
+
+```bash
+brew install libreoffice
+```
+
+- [whisper.cpp](https://github.com/ggml-org/whisper.cpp) (optional, for word-by-word highlighting)
+
+```bash
+# clone and build whisper.cpp (no model download needed – OpenReader handles that)
+git clone https://github.com/ggml-org/whisper.cpp.git
+cd whisper.cpp
+cmake -B build
+cmake --build build -j --config Release
+
+# point OpenReader to the compiled whisper-cli binary
+echo WHISPER_CPP_BIN="$(pwd)/build/bin/whisper-cli"
+```
+
+:::tip
+Set `WHISPER_CPP_BIN` in your `.env` to enable word-by-word highlighting.
+:::
+
+## Steps
+
+1. Clone the repository.
+
+```bash
+git clone https://github.com/richardr1126/openreader.git
+cd openreader
+```
+
+2. Install dependencies.
+
+```bash
+pnpm i
+```
+
+3. Configure the environment.
+
+```bash
+cp .env.example .env
+```
+
+Then edit `.env`.
+
+- No auth mode: leave `BASE_URL` or `AUTH_SECRET` unset.
+- Auth enabled mode: set both `BASE_URL` (typically `http://localhost:3003`) and `AUTH_SECRET` (generate with `openssl rand -hex 32`).
+
+Optional:
+
+- `AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://192.168.0.116:3003`
+- Stable S3 credentials via `S3_ACCESS_KEY_ID` and `S3_SECRET_ACCESS_KEY`
+- External S3 storage by setting `USE_EMBEDDED_WEED_MINI=false` and related S3 vars
+
+:::info
+For all environment variables, see [Environment Variables](../reference/environment-variables).
+:::
+
+See [Auth](../configure/auth) for app/auth behavior.
+Storage configuration details are in [Object / Blob Storage](../configure/object-blob-storage).
+Refer to [Database](../configure/database) for database modes.
+Learn about migration behavior and commands in [Migrations](../configure/migrations).
+
+4. Run DB migrations.
+
+- Migrations run automatically on startup through the shared entrypoint for both `pnpm dev` and `pnpm start`.
+- You only need manual migration commands for one-off troubleshooting or explicit migration workflows:
+
+```bash
+pnpm migrate
+```
+
+:::info
+If `POSTGRES_URL` is set, migrations target Postgres; otherwise local SQLite is used. To disable automatic startup migrations, set `RUN_DRIZZLE_MIGRATIONS=false` and/or `RUN_FS_MIGRATIONS=false`. You can run storage migration manually with `pnpm migrate-fs`.
+:::
+
+5. Start the app.
+
+
+
+
+```bash
+pnpm dev
+```
+
+
+
+
+```bash
+pnpm build
+pnpm start
+```
+
+
+
+
+:::warning API Base Reachability
+`API_BASE` must be reachable from the Next.js server process, not just your browser.
+:::
+
+Visit [http://localhost:3003](http://localhost:3003).
diff --git a/docs-site/versioned_docs/version-v2.0.0/deploy/vercel-deployment.md b/docs-site/versioned_docs/version-v2.0.0/deploy/vercel-deployment.md
new file mode 100644
index 0000000..ebe0b6c
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/deploy/vercel-deployment.md
@@ -0,0 +1,108 @@
+---
+title: Vercel Deployment
+---
+
+This guide covers deploying OpenReader to Vercel with external Postgres and S3-compatible object storage.
+
+## What works on Vercel
+
+- Documents (PDF/EPUB/TXT/MD) work with `POSTGRES_URL` + external S3 storage.
+- Audiobook routes work on Node.js serverless functions using `ffmpeg-static`.
+
+:::warning DOCX Conversion Limitation
+`docx` conversion requires `soffice` (LibreOffice), which is not available in a standard Vercel runtime.
+:::
+
+## 1. Environment Variables
+
+Recommended production setup (auth enabled):
+
+```bash
+API_BASE=https://api.deepinfra.com/v1/openai
+API_KEY=your_deepinfra_key
+POSTGRES_URL=postgres://...
+USE_EMBEDDED_WEED_MINI=false
+S3_ACCESS_KEY_ID=...
+S3_SECRET_ACCESS_KEY=...
+S3_BUCKET=...
+S3_REGION=us-east-1
+S3_PREFIX=openreader
+BASE_URL=https://your-app.vercel.app
+AUTH_SECRET=...
+# Optional client/runtime feature defaults:
+NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false
+NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false
+NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra
+NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M
+NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false
+NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true
+NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false
+# Optional (non-AWS S3-compatible providers):
+# S3_ENDPOINT=https://...
+# S3_FORCE_PATH_STYLE=true
+```
+
+:::info Production Configuration & Feature Flags
+We recommend setting these defaults for a production-like environment:
+
+- `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION=false`: Disables DOCX upload (requires external tools anyway)
+- `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS=false`: Hides destructive "Delete All" actions
+- `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER=deepinfra`: Points default TTS to a scalable provider
+- `NEXT_PUBLIC_DEFAULT_TTS_MODEL=hexgrad/Kokoro-82M`: Uses a high-quality default model
+- `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS=false`: Restricts usage to free models if no key is provided
+- `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT=true`: (Optional) Controls audiobook export UI
+- `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT=false`: (Optional) Controls word highlighting UI (requires timestamp backend)
+:::
+
+:::warning Auth recommendation
+For internet-exposed Vercel deployments, set both `BASE_URL` and `AUTH_SECRET`. Running without auth is possible, but not recommended for public environments.
+:::
+
+:::tip
+For all variables and defaults, see [Environment Variables](../reference/environment-variables).
+:::
+
+## 2. FFmpeg packaging in Vercel functions
+
+`ffmpeg-static` binaries must be included in function traces. This repo already does that in `next.config.ts` via `outputFileTracingIncludes` for:
+
+- `/api/audiobook`
+- `/api/audiobook/chapter`
+- `/api/audiobook/status`
+- `/api/whisper`
+
+:::info
+`serverExternalPackages` should include `ffmpeg-static` so package paths resolve at runtime instead of being bundled into route output.
+:::
+
+If you change route paths or split handlers, update `outputFileTracingIncludes` accordingly.
+
+## 3. Function memory sizing
+
+FFmpeg workloads benefit from more memory/CPU. This repo includes:
+
+```json
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "functions": {
+ "app/api/audiobook/route.ts": { "memory": 3009 },
+ "app/api/whisper/route.ts": { "memory": 3009 }
+ }
+}
+```
+
+Adjust memory per route if your files are larger or your plan differs.
+
+## 4. Runtime expectations and caveats
+
+- Audiobook APIs require S3 configuration; otherwise they return `503`.
+- For production Vercel deploys, use `POSTGRES_URL` instead of SQLite.
+- Filesystem-to-object-store migrations run via server scripts/entrypoint (`scripts/migrate-fs-v2.mjs`), not API routes.
+- Vercel deployments do not run `scripts/openreader-entrypoint.mjs`, so run `pnpm migrate-fs` in a controlled environment when migrating legacy filesystem data.
+
+## 5. Smoke test after deploy
+
+1. Upload and read a PDF/EPUB document.
+2. Confirm sync/blob fetch works across refreshes/devices.
+3. Generate at least one audiobook chapter and play/download it.
+4. If using word highlighting, verify timestamps are produced and rendered.
diff --git a/docs-site/versioned_docs/version-v2.0.0/docker-quick-start.md b/docs-site/versioned_docs/version-v2.0.0/docker-quick-start.md
new file mode 100644
index 0000000..0fd8902
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/docker-quick-start.md
@@ -0,0 +1,121 @@
+---
+title: Docker Quick Start
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Prerequisites
+
+- A recent Docker version installed
+- A TTS API server that OpenReader can reach (Kokoro-FastAPI, Orpheus-FastAPI, DeepInfra, OpenAI, or equivalent)
+
+:::note
+If you have suitable hardware, you can run Kokoro locally with Docker. See [Kokoro-FastAPI](./configure/tts-provider-guides/kokoro-fastapi).
+:::
+
+## 1. Start the Docker container
+
+
+
+
+Auth disabled, embedded storage ephemeral, no library import:
+
+```bash
+docker run --name openreader \
+ --restart unless-stopped \
+ -p 3003:3003 \
+ -p 8333:8333 \
+ ghcr.io/richardr1126/openreader:latest
+```
+
+
+
+
+Persistent storage, embedded SeaweedFS `weed mini`, optional auth, optional library mount:
+
+```bash
+docker run --name openreader \
+ --restart unless-stopped \
+ -p 3003:3003 \
+ -p 8333:8333 \
+ -v openreader_docstore:/app/docstore \
+ -v /path/to/your/library:/app/docstore/library:ro \
+ -e API_BASE=http://host.docker.internal:8880/v1 \
+ -e API_KEY=none \
+ -e BASE_URL=http://localhost:3003 \
+ -e AUTH_SECRET=$(openssl rand -hex 32) \
+ ghcr.io/richardr1126/openreader:latest
+```
+
+
+
+
+Use this when the app should be reachable from other devices on your LAN:
+
+```bash
+docker run --name openreader \
+ --restart unless-stopped \
+ -p 3003:3003 \
+ -p 8333:8333 \
+ -v openreader_docstore:/app/docstore \
+ -e API_BASE=http://host.docker.internal:8880/v1 \
+ -e BASE_URL=http://:3003 \
+ -e AUTH_SECRET=$(openssl rand -hex 32) \
+ -e AUTH_TRUSTED_ORIGINS=http://localhost:3003,http://127.0.0.1:3003 \
+ -e USE_ANONYMOUS_AUTH_SESSIONS=true \
+ ghcr.io/richardr1126/openreader:latest
+```
+
+Replace `` with the Docker host IP address on your local network to allow access from other devices.
+
+
+
+
+:::tip Quick Tips
+- Remove `/app/docstore/library` if you do not need server library import.
+- Remove either `BASE_URL` or `AUTH_SECRET` to keep auth disabled.
+- Set `API_BASE` to your reachable TTS server base URL.
+:::
+
+:::warning Port `8333` Exposure
+Expose `8333` for direct browser presigned upload/download with embedded SeaweedFS.
+
+If `8333` is not reachable from the browser, direct presigned access is unavailable. Uploads can still fall back to `/api/documents/blob/upload/fallback`, and document reads/downloads continue through `/api/documents/blob`.
+:::
+
+:::info Auth and Migrations
+- Auth is enabled only when both `BASE_URL` and `AUTH_SECRET` are set.
+- DB/storage migrations run automatically at container startup via the shared entrypoint.
+:::
+
+:::info Related Docs
+- [Environment Variables](./reference/environment-variables)
+- [Auth](./configure/auth)
+- [Database](./configure/database)
+- [Object / Blob Storage](./configure/object-blob-storage)
+- [Migrations](./configure/migrations)
+:::
+
+## 2. Configure settings in the app UI
+
+- Set TTS provider and model in Settings
+- Set TTS API base URL and API key if needed
+- Select the model voice from the voice dropdown
+
+## 3. Update Docker image
+
+Legacy image compatibility: `ghcr.io/richardr1126/openreader-webui:latest` remains available as an alias.
+
+```bash
+docker stop openreader || true && \
+docker rm openreader || true && \
+docker image rm ghcr.io/richardr1126/openreader:latest || true && \
+docker pull ghcr.io/richardr1126/openreader:latest
+```
+
+:::tip
+If you use a mounted volume for `/app/docstore`, your persisted data remains after image updates.
+:::
+
+Visit [http://localhost:3003](http://localhost:3003) after startup.
diff --git a/docs-site/versioned_docs/version-v2.0.0/introduction.md b/docs-site/versioned_docs/version-v2.0.0/introduction.md
new file mode 100644
index 0000000..f3ed2a3
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/introduction.md
@@ -0,0 +1,51 @@
+---
+id: intro
+title: Introduction
+slug: /
+---
+
+OpenReader is an open source text-to-speech document reader built with Next.js. It provides a read-along experience with narration for **EPUB, PDF, TXT, MD, and DOCX documents**.
+
+> Previously named **OpenReader-WebUI**.
+
+It supports multiple TTS providers including OpenAI, DeepInfra, and custom OpenAI-compatible endpoints such as [Kokoro-FastAPI](https://github.com/remsky/Kokoro-FastAPI) and [Orpheus-FastAPI](https://github.com/Lex-au/Orpheus-FastAPI).
+
+## ✨ Highlights
+
+- 🎯 **Multi-Provider TTS Support**
+ - [**Kokoro-FastAPI**](https://github.com/remsky/Kokoro-FastAPI): supports multi-voice combinations (for example `af_heart+af_bella`)
+ - [**Orpheus-FastAPI**](https://github.com/Lex-au/Orpheus-FastAPI)
+ - **Custom OpenAI-compatible**: any TTS API with `/v1/audio/voices` and `/v1/audio/speech` endpoints
+ - **Cloud TTS providers**:
+ - [**DeepInfra**](https://deepinfra.com/models/text-to-speech): Kokoro-82M and other hosted models
+ - [**OpenAI API**](https://platform.openai.com/docs/pricing#transcription-and-speech): `tts-1`, `tts-1-hd`, and `gpt-4o-mini-tts`
+- 🛜 **Server-side Document Storage**
+ - Documents are persisted in server blob/object storage for consistent access
+- 📚 **External Library Import**
+ - Import documents from server-mounted folders
+- 🎧 **Server-side Audiobook Export** in `m4b`/`mp3` with resumable chapter generation
+- 📖 **Read Along Experience**
+ - Real-time highlighting for PDF/EPUB, with optional word-level [whisper.cpp](https://github.com/ggml-org/whisper.cpp) timestamps
+- 🔐 **Auth Optional by Design**
+ - Run no-auth for local use, or enable auth with user isolation and claim flow
+- 🗂️ **Flexible Storage and Database Modes** with embedded defaults or external S3/Postgres
+- 🚀 **Production-ready Server Behavior** with TTS caching/retries/rate limits and startup migrations
+- 🎨 **Customizable Experience**
+ - Theme, TTS, and document handling controls
+
+## 🧭 Key Docs
+
+- [Docker Quick Start](./docker-quick-start)
+- [Local Development](./deploy/local-development)
+- [Vercel Deployment](./deploy/vercel-deployment)
+- [Environment Variables](./reference/environment-variables)
+- [Auth](./configure/auth)
+- [Database](./configure/database)
+- [Object / Blob Storage](./configure/object-blob-storage)
+- [Migrations](./configure/migrations)
+- [Server Library Import](./configure/server-library-import)
+- [TTS Providers](./configure/tts-providers)
+
+## Source Repository
+
+- GitHub: [richardr1126/openreader](https://github.com/richardr1126/openreader)
diff --git a/docs-site/versioned_docs/version-v2.0.0/reference/environment-variables.md b/docs-site/versioned_docs/version-v2.0.0/reference/environment-variables.md
new file mode 100644
index 0000000..daef2bd
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/reference/environment-variables.md
@@ -0,0 +1,395 @@
+---
+title: Environment Variables
+toc_max_heading_level: 3
+---
+
+This is the single reference page for OpenReader environment variables.
+
+## Quick Reference Table
+
+| Variable | Area | Default | When to set |
+| --- | --- | --- | --- |
+| `NEXT_PUBLIC_ENABLE_DOCX_CONVERSION` | Client feature flags | `true` unless set to `false` | Set `false` to hide DOCX support |
+| `NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS` | Client feature flags | `true` unless set to `false` | Set `false` to hide destructive actions |
+| `NEXT_PUBLIC_DEFAULT_TTS_PROVIDER` | Client feature flags | `custom-openai` | Override default TTS provider |
+| `NEXT_PUBLIC_DEFAULT_TTS_MODEL` | Client feature flags | `kokoro` | Override default TTS model |
+| `NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS` | Client feature flags | `true` unless set to `false` | Set `false` to restrict DeepInfra models |
+| `NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT` | Client feature flags | `true` unless set to `false` | Set `false` to hide audiobook export UI |
+| `NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT` | Client feature flags | `true` unless set to `false` | Set `false` to disable word highlight + alignment |
+| `API_BASE` | TTS provider | none | Point to your OpenAI-compatible TTS base URL |
+| `API_KEY` | TTS provider | `none` fallback in TTS route | Set when provider requires auth |
+| `TTS_CACHE_MAX_SIZE_BYTES` | TTS caching | `268435456` (256 MB) | Tune in-memory TTS cache size |
+| `TTS_CACHE_TTL_MS` | TTS caching | `1800000` (30 min) | Tune in-memory TTS cache TTL |
+| `TTS_MAX_RETRIES` | TTS retry | `2` | Tune retry attempts for upstream 429/5xx |
+| `TTS_RETRY_INITIAL_MS` | TTS retry | `250` | Tune initial retry delay |
+| `TTS_RETRY_MAX_MS` | TTS retry | `2000` | Tune max retry delay |
+| `TTS_RETRY_BACKOFF` | TTS retry | `2` | Tune exponential backoff factor |
+| `TTS_ENABLE_RATE_LIMIT` | Rate limiting | `false` | Set `true` to enable TTS per-user/IP daily character limits |
+| `TTS_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `50000` | Override anonymous per-user daily character limit |
+| `TTS_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `500000` | Override authenticated per-user daily character limit |
+| `TTS_IP_DAILY_LIMIT_ANONYMOUS` | Rate limiting | `100000` | Override anonymous IP backstop daily limit |
+| `TTS_IP_DAILY_LIMIT_AUTHENTICATED` | Rate limiting | `1000000` | Override authenticated IP backstop daily limit |
+| `BASE_URL` | Auth | unset | Required (with `AUTH_SECRET`) to enable auth |
+| `AUTH_SECRET` | Auth | unset | Required (with `BASE_URL`) to enable auth |
+| `AUTH_TRUSTED_ORIGINS` | Auth | empty | Add extra allowed origins |
+| `USE_ANONYMOUS_AUTH_SESSIONS` | Auth | `false` | Set `true` to enable anonymous auth sessions |
+| `GITHUB_CLIENT_ID` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_SECRET` to enable GitHub sign-in |
+| `GITHUB_CLIENT_SECRET` | Auth/OAuth | unset | Set with `GITHUB_CLIENT_ID` to enable GitHub sign-in |
+| `DISABLE_AUTH_RATE_LIMIT` | Rate limiting | `false` | Set `true` to disable auth-layer rate limiting |
+| `POSTGRES_URL` | Database | unset (SQLite mode) | Set to switch metadata/auth DB to Postgres |
+| `USE_EMBEDDED_WEED_MINI` | Storage | `true` when unset | Set `false` to use external S3-compatible storage only |
+| `WEED_MINI_DIR` | Storage | `docstore/seaweedfs` | Override embedded SeaweedFS data directory |
+| `WEED_MINI_WAIT_SEC` | Storage | `20` | Tune SeaweedFS startup wait timeout |
+| `S3_ACCESS_KEY_ID` | Storage | auto-generated in embedded mode | Set explicitly for stable/external credentials |
+| `S3_SECRET_ACCESS_KEY` | Storage | auto-generated in embedded mode | Set explicitly for stable/external credentials |
+| `S3_BUCKET` | Storage | `openreader-documents` in embedded mode | Required for external S3-compatible storage |
+| `S3_REGION` | Storage | `us-east-1` in embedded mode | Required for external S3-compatible storage |
+| `S3_ENDPOINT` | Storage | derived in embedded mode | Set for S3-compatible providers (MinIO/SeaweedFS/R2/etc.) |
+| `S3_FORCE_PATH_STYLE` | Storage | `true` in embedded mode | Set per provider requirement |
+| `S3_PREFIX` | Storage | `openreader` | Customize object key prefix |
+| `RUN_DRIZZLE_MIGRATIONS` | Database migrations | `true` | Set `false` to skip startup Drizzle schema migrations |
+| `RUN_FS_MIGRATIONS` | Storage migrations | `true` | Set `false` to skip startup filesystem -> S3/DB migration pass |
+| `IMPORT_LIBRARY_DIR` | Library import | `docstore/library` fallback | Set a single server library root |
+| `IMPORT_LIBRARY_DIRS` | Library import | unset | Set multiple roots (comma/colon/semicolon separated) |
+| `WHISPER_CPP_BIN` | Word timing | unset | Set to enable `whisper.cpp` timestamps |
+| `FFMPEG_BIN` | Audio runtime | auto-detected (`ffmpeg-static`) | Override ffmpeg binary path |
+
+
+
+## TTS Provider and Request Behavior
+
+### API_BASE
+
+Base URL for OpenAI-compatible TTS API requests.
+
+- Example: `http://host.docker.internal:8880/v1`
+- Can be overridden per request from UI settings
+- Related docs: [TTS Providers](../configure/tts-providers)
+
+### API_KEY
+
+Default API key for TTS provider requests.
+
+- Example: `none` or your provider token
+- Can be overridden by request headers from app settings
+- Related docs: [TTS Providers](../configure/tts-providers)
+
+### TTS_CACHE_MAX_SIZE_BYTES
+
+Maximum in-memory TTS audio cache size in bytes.
+
+- Default: `268435456` (256 MB)
+
+### TTS_CACHE_TTL_MS
+
+In-memory TTS audio cache TTL in milliseconds.
+
+- Default: `1800000` (30 minutes)
+
+### TTS_MAX_RETRIES
+
+Maximum retries for upstream TTS failures (429/5xx).
+
+- Default: `2`
+
+### TTS_RETRY_INITIAL_MS
+
+Initial retry delay in milliseconds for TTS upstream requests.
+
+- Default: `250`
+
+### TTS_RETRY_MAX_MS
+
+Maximum retry delay in milliseconds.
+
+- Default: `2000`
+
+### TTS_RETRY_BACKOFF
+
+Exponential backoff multiplier between retries.
+
+- Default: `2`
+
+### TTS_ENABLE_RATE_LIMIT
+
+Controls TTS character rate limiting in the TTS API.
+
+- Default: `false` (TTS char limits disabled)
+- Set to `true` to enforce `TTS_DAILY_LIMIT_*` and `TTS_IP_DAILY_LIMIT_*`
+- For behavior details and examples, see [TTS Rate Limiting](../configure/tts-rate-limiting)
+
+### TTS_DAILY_LIMIT_ANONYMOUS
+
+Anonymous per-user daily character limit.
+
+- Default: `50000`
+
+### TTS_DAILY_LIMIT_AUTHENTICATED
+
+Authenticated per-user daily character limit.
+
+- Default: `500000`
+
+### TTS_IP_DAILY_LIMIT_ANONYMOUS
+
+Anonymous IP backstop daily character limit.
+
+- Default: `100000`
+
+### TTS_IP_DAILY_LIMIT_AUTHENTICATED
+
+Authenticated IP backstop daily character limit.
+
+- Default: `1000000`
+
+## Auth and Identity
+
+### BASE_URL
+
+External base URL for this OpenReader instance.
+
+- Required with `AUTH_SECRET` to enable auth
+- Example: `http://localhost:3003` or `https://reader.example.com`
+- Related docs: [Auth](../configure/auth)
+
+### AUTH_SECRET
+
+Secret key used by auth/session handling.
+
+- Required with `BASE_URL` to enable auth
+- Generate with `openssl rand -hex 32`
+- Related docs: [Auth](../configure/auth)
+
+### AUTH_TRUSTED_ORIGINS
+
+Additional allowed origins for auth requests.
+
+- Comma-separated list
+- `BASE_URL` origin is always trusted automatically
+- Related docs: [Auth](../configure/auth)
+
+### USE_ANONYMOUS_AUTH_SESSIONS
+
+Controls whether auth-enabled deployments can create/use anonymous sessions.
+
+- Default: `false` (anonymous sessions disabled)
+- Set `true` to allow anonymous sessions and guest-style flows
+- When `false`, users must sign in or sign up with an account
+- Related docs: [Auth](../configure/auth)
+
+### GITHUB_CLIENT_ID
+
+GitHub OAuth client ID.
+
+- Enable only with `GITHUB_CLIENT_SECRET`
+
+### GITHUB_CLIENT_SECRET
+
+GitHub OAuth client secret.
+
+- Enable only with `GITHUB_CLIENT_ID`
+
+### DISABLE_AUTH_RATE_LIMIT
+
+Controls Better Auth rate limiting.
+
+- Default behavior: auth-layer rate limiting enabled
+- Set to `true` to disable auth-layer rate limiting
+- This does not affect TTS character rate limiting
+- Related docs: [Auth](../configure/auth)
+
+## Database and Object Blob Storage
+
+### POSTGRES_URL
+
+Switches metadata/auth storage from SQLite to Postgres.
+
+- Unset: SQLite at `docstore/sqlite3.db`
+- Set: Postgres mode
+- Related docs: [Database](../configure/database)
+
+### USE_EMBEDDED_WEED_MINI
+
+Controls embedded SeaweedFS startup.
+
+- Default behavior: treated as enabled when unset
+- Set `false` to rely on external S3-compatible storage
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### WEED_MINI_DIR
+
+Data directory for embedded SeaweedFS (`weed mini`).
+
+- Default: `docstore/seaweedfs`
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### WEED_MINI_WAIT_SEC
+
+Maximum seconds to wait for embedded SeaweedFS startup.
+
+- Default: `20`
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_ACCESS_KEY_ID
+
+Access key for S3-compatible storage.
+
+- Auto-generated in embedded mode if unset
+- Set explicitly for stable credentials or external providers
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_SECRET_ACCESS_KEY
+
+Secret key for S3-compatible storage.
+
+- Auto-generated in embedded mode if unset
+- Set explicitly for stable credentials or external providers
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_BUCKET
+
+Bucket name used for document blobs.
+
+- Default in embedded mode: `openreader-documents`
+- Required for external S3-compatible storage
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_REGION
+
+Region used by the S3 client.
+
+- Default in embedded mode: `us-east-1`
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_ENDPOINT
+
+Endpoint URL for S3-compatible storage.
+
+- In embedded mode, defaults to `http://:8333` (or detected host)
+- For AWS S3, usually leave unset
+- For MinIO/SeaweedFS/R2/B2-style APIs, typically set explicitly
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_FORCE_PATH_STYLE
+
+Path-style S3 addressing toggle.
+
+- Default in embedded mode: `true`
+- Set according to provider requirements
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+### S3_PREFIX
+
+Prefix prepended to stored object keys.
+
+- Default: `openreader`
+- Related docs: [Object / Blob Storage](../configure/object-blob-storage)
+
+## Migration Controls
+
+### RUN_DRIZZLE_MIGRATIONS
+
+Controls startup migration execution in shared entrypoint.
+
+- Default: `true`
+- Set `false` to skip automatic startup Drizzle schema migrations
+- Related docs: [Migrations](../configure/migrations), [Database](../configure/database)
+
+### RUN_FS_MIGRATIONS
+
+Controls startup filesystem-to-object-store migration execution in shared entrypoint.
+
+- Default: `true`
+- Runs `scripts/migrate-fs-v2.mjs` at startup after DB migrations
+- Set `false` to skip automatic storage migration pass
+- Related docs: [Migrations](../configure/migrations), [Database](../configure/database), [Object / Blob Storage](../configure/object-blob-storage)
+
+## Library Import
+
+### IMPORT_LIBRARY_DIR
+
+Single directory root for server library import.
+
+- Used when `IMPORT_LIBRARY_DIRS` is unset
+- Default fallback root: `docstore/library`
+- Related docs: [Server Library Import](../configure/server-library-import)
+
+### IMPORT_LIBRARY_DIRS
+
+Multiple library roots for server library import.
+
+- Separator: comma, colon, or semicolon
+- Takes precedence over `IMPORT_LIBRARY_DIR`
+- Related docs: [Server Library Import](../configure/server-library-import)
+
+## Audio Tooling and Alignment
+
+### WHISPER_CPP_BIN
+
+Absolute path to compiled `whisper.cpp` binary for word-level timestamps.
+
+- Example: `/whisper.cpp/build/bin/whisper-cli`
+- Required only for optional word-by-word highlighting
+
+### FFMPEG_BIN
+
+Absolute path or executable name for the ffmpeg binary used by audiobook/processing routes.
+
+- Resolution order: `FFMPEG_BIN` -> `ffmpeg-static`
+- Example: `/var/task/node_modules/ffmpeg-static/ffmpeg`
+
+## Client Runtime and Feature Flags
+
+### NEXT_PUBLIC_ENABLE_DOCX_CONVERSION
+
+ Controls whether the experimental DOCX-to-PDF conversion and upload feature is enabled.
+
+ - Default: `true` (enabled)
+ - Set `false` to hide DOCX support in the upload UI
+
+ ### NEXT_PUBLIC_ENABLE_DESTRUCTIVE_DELETE_ACTIONS
+
+ Controls whether the "Delete all user docs" and other bulk-delete buttons are shown in Settings.
+
+ - Default: `true` (enabled)
+ - Set `false` to hide destructive actions (recommended for production)
+
+ ### NEXT_PUBLIC_DEFAULT_TTS_PROVIDER
+
+ Sets the default TTS provider for new users.
+
+ - Default: `custom-openai`
+ - Example values: `deepinfra`, `openai`, `custom-openai`
+
+ ### NEXT_PUBLIC_DEFAULT_TTS_MODEL
+
+ Sets the default TTS model for new users.
+
+ - Default: `kokoro`
+ - Example values: `hexgrad/Kokoro-82M`, `tts-1`
+
+ ### NEXT_PUBLIC_SHOW_ALL_DEEPINFRA_MODELS
+
+ Controls whether the DeepInfra model list shows all models or just the free tier when no API key is set.
+
+ - Default: `true` (show all)
+ - Set `false` to restrict to free tier models when no API key is provided
+
+### NEXT_PUBLIC_ENABLE_AUDIOBOOK_EXPORT
+
+Controls whether audiobook export UI/actions are shown in the client.
+
+- Default behavior: enabled unless explicitly set to `false`
+- Applies in both development and production
+- Affects export entry points in PDF/EPUB pages and document settings UI
+
+### NEXT_PUBLIC_ENABLE_WORD_HIGHLIGHT
+
+Controls word-by-word highlighting UI and timestamp-alignment behavior.
+
+- Default behavior: enabled unless explicitly set to `false`
+- Applies in both development and production
+- Requires working timestamp generation (for example `WHISPER_CPP_BIN`)
+- Affects:
+ - Word-highlight toggles in document settings
+ - Alignment requests during TTS playback
diff --git a/docs-site/versioned_docs/version-v2.0.0/reference/stack.md b/docs-site/versioned_docs/version-v2.0.0/reference/stack.md
new file mode 100644
index 0000000..cc45fa2
--- /dev/null
+++ b/docs-site/versioned_docs/version-v2.0.0/reference/stack.md
@@ -0,0 +1,44 @@
+---
+title: Stack
+---
+
+## Framework
+
+- [Next.js](https://nextjs.org/) 15 (App Router)
+- [React](https://react.dev/) 19
+- [TypeScript](https://www.typescriptlang.org/)
+
+## Containerization and runtime
+
+- [Docker](https://www.docker.com/) (linux/amd64 and linux/arm64)
+- Shared entrypoint that runs DB migrations by default and can bootstrap embedded SeaweedFS before app startup
+
+## Next.js client
+
+- UI: [Tailwind CSS](https://tailwindcss.com), [Headless UI](https://headlessui.com), [@tailwindcss/typography](https://tailwindcss.com/docs/typography-plugin)
+- Interactions: `react-dnd`, `react-dropzone`
+- Authentication: [Better Auth](https://www.better-auth.com/) client SDK
+- Local storage/cache: [Dexie.js](https://dexie.org/) (IndexedDB)
+- Document rendering:
+ - PDF: [react-pdf](https://github.com/wojtekmaj/react-pdf), [pdf.js](https://mozilla.github.io/pdf.js/)
+ - EPUB: [react-reader](https://github.com/gerhardsletten/react-reader), [epubjs](https://github.com/futurepress/epub.js/)
+ - Markdown/Text: [react-markdown](https://github.com/remarkjs/react-markdown), [remark-gfm](https://github.com/remarkjs/remark-gfm)
+- Text preprocessing/matching: [compromise](https://github.com/spencermountain/compromise), [cmpstr](https://github.com/remsky/cmpstr)
+
+## Next.js server
+
+- APIs: Route Handlers for sync, blob/content access, migrations, audiobook export, TTS/Whisper proxying
+- State sync: request-based today (not realtime push updates)
+- Authentication: [Better Auth](https://www.better-auth.com/) server handlers/adapters
+- Metadata DB: [Drizzle ORM](https://orm.drizzle.team/) with SQLite (`better-sqlite3`) by default and optional Postgres (`pg`)
+ - App tables are manually maintained in Drizzle schema files
+ - Auth tables are auto-generated by the [Better Auth](https://www.better-auth.com/) CLI and migrated alongside app tables via Drizzle
+- Blob storage: embedded [SeaweedFS](https://github.com/seaweedfs/seaweedfs) (`weed mini`) by default, or external S3-compatible storage via AWS SDK v3
+- Audio/processing pipeline: OpenAI-compatible TTS providers, [ffmpeg](https://ffmpeg.org/) for audiobook assembly, optional [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for word timestamps
+
+## Tooling and testing
+
+- ESLint
+- TypeScript
+- [Playwright](https://playwright.dev/) end-to-end tests
+- Drizzle migration/generation scripts
diff --git a/docs-site/versioned_sidebars/version-v2.0.0-sidebars.json b/docs-site/versioned_sidebars/version-v2.0.0-sidebars.json
new file mode 100644
index 0000000..fbb91d6
--- /dev/null
+++ b/docs-site/versioned_sidebars/version-v2.0.0-sidebars.json
@@ -0,0 +1,70 @@
+{
+ "tutorialSidebar": [
+ "intro",
+ {
+ "type": "doc",
+ "id": "docker-quick-start",
+ "label": "🐳 Docker Quick Start"
+ },
+ {
+ "type": "category",
+ "label": "⚙️ Configure",
+ "items": [
+ {
+ "type": "category",
+ "label": "🔊 TTS Providers",
+ "link": {
+ "type": "doc",
+ "id": "configure/tts-providers"
+ },
+ "items": [
+ "configure/tts-provider-guides/kokoro-fastapi",
+ "configure/tts-provider-guides/orpheus-fastapi",
+ "configure/tts-provider-guides/deepinfra",
+ "configure/tts-provider-guides/openai",
+ "configure/tts-provider-guides/custom-openai"
+ ]
+ },
+ {
+ "type": "doc",
+ "id": "configure/auth",
+ "label": "🔐 Auth"
+ },
+ {
+ "type": "doc",
+ "id": "configure/server-library-import",
+ "label": "📥 Server Library Import"
+ },
+ "configure/tts-rate-limiting",
+ "configure/database",
+ "configure/object-blob-storage",
+ "configure/migrations"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "🚀 Deploy",
+ "items": [
+ "deploy/local-development",
+ "deploy/vercel-deployment"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "Reference",
+ "items": [
+ "reference/environment-variables",
+ "reference/stack"
+ ]
+ },
+ {
+ "type": "category",
+ "label": "About",
+ "items": [
+ "about/support-and-contributing",
+ "about/acknowledgements",
+ "about/license"
+ ]
+ }
+ ]
+}
diff --git a/docs-site/versions.json b/docs-site/versions.json
new file mode 100644
index 0000000..7ff64ac
--- /dev/null
+++ b/docs-site/versions.json
@@ -0,0 +1,3 @@
+[
+ "v2.0.0"
+]