Note: Readest Private Deployment Notes

Published 2025-07-28 13:48 Updated 2025-07-28 18:58 1508 words 8 min read

Readest is a modern open-source e-book reader designed for immersive reading. It has 10.1k stars on GitHub, making it an excellent project. However, the GitHub project page only provides a build guide without detailed instructions on how to configure the project. Then I scoured the entire internet and found that there is no deployment tutorial available anywhere online; all the content is just promotional articles introducing Readest. The content of these articles seems to be almost identical, with dozens of pieces appearing to be copied from each other. This article serves as my personal notes on private deployment, and so far I have only completed the web-based deployment, for reference only.

Translated by AI model Qwen/Qwen3-8B.

Source Language: Simplified Chinese, Target Language: english, Translation Time: 2026-05-01 04:04

.

AI translation is for reference only. Accuracy is not guaranteed, please refer to the original text.

Readest is a modern open-source e-book reader for immersive reading.

It has 10.1k stars on Github, which can be considered an excellent project.

However, the Github project page only provides guidance for building the project, but does not detail how to configure it. Then I searched through the entire internet and found that, the hell, there's no deployment tutorial anywhere on the web. All content is just promotional articles about Readest. The content looks almost identical, with dozens of articles copying each other.

This article is my personal notes on private deployment. Currently, only the web deployment has been completed, for reference only.

My Deployment Environment

Netlify Build

Runtime: Next.js Base directory: / Package directory: Not set Build command: git submodule update --init --recursive && pnpm i && pnpm --filter @readest/readest-app setup-pdfjs && export NODE_OPTIONS=--max-old-space-size=4096 && pnpm --filter @readest/readest-app build-web Publish directory: apps/readest-app/.next

Node.js: 22.x Build image: Ubuntu Noble 24.04 (default)

ENV

The project contains a file named .env.local.example located at apps/readest-app/.env.local.example. However, the content of this example file is incomplete. The entire project depends on 45 environment variables, but this example file only provides 20. Please refer to the following configuration for supplementation:

Below is the list of required environment variables:

  • NEXT_PUBLIC_API_BASE_URL
  • NEXT_PUBLIC_POSTHOG_KEY
  • NEXT_PUBLIC_POSTHOG_HOST
  • NEXT_PUBLIC_SUPABASE_URL
  • NEXT_PUBLIC_SUPABASE_ANON_KEY
  • SUPABASE_ADMIN_KEY
# ====== 核心应用配置 ======
NEXT_PUBLIC_APP_PLATFORM=web # 建议不要填它 应用平台: web/tauri
NEXT_PUBLIC_API_BASE_URL=https://your-api-base-url.com # API基础URL
NEXT_PUBLIC_NODE_BASE_URL= # Node.js服务基础URL(可选)

# ====== 分析监控 ======
NEXT_PUBLIC_POSTHOG_KEY=YOUR_POSTHOG_KEY # PostHog分析密钥
NEXT_PUBLIC_POSTHOG_HOST=YOUR_POSTHOG_HOST # PostHog服务地址
NEXT_PUBLIC_DEFAULT_POSTHOG_URL_BASE64= # PostHog备用URL(base64编码)
NEXT_PUBLIC_DEFAULT_POSTHOG_KEY_BASE64= # PostHog备用密钥(base64编码)

# ====== 数据库 & 认证 ======
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL # Supabase项目URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY # Supabase匿名密钥
SUPABASE_ADMIN_KEY=YOUR_SUPABASE_ADMIN_KEY # Supabase管理员密钥
NEXT_PUBLIC_DEFAULT_SUPABASE_URL_BASE64= # 备用Supabase URL(base64)
NEXT_PUBLIC_DEFAULT_SUPABASE_KEY_BASE64= # 备用Supabase密钥(base64)

# ====== 支付 & 订阅 ======
# Stripe配置
STRIPE_SECRET_KEY= # Stripe生产环境密钥
STRIPE_SECRET_KEY_DEV= # Stripe开发环境密钥
STRIPE_WEBHOOK_SECRET= # Stripe webhook验证密钥
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_BASE64= # Stripe生产公钥(base64)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_DEV_BASE64= # Stripe开发公钥(base64)

# Apple应用内支付
APPLE_IAP_KEY_ID= # Apple IAP密钥ID
APPLE_IAP_ISSUER_ID= # Apple IAP发行者ID
APPLE_IAP_BUNDLE_ID= # Apple应用包ID
APPLE_IAP_PRIVATE_KEY_BASE64= # Apple私钥(base64编码)

# ====== 文件存储 ======
# 存储类型: r2/s3
NEXT_PUBLIC_OBJECT_STORAGE_TYPE=r2 

# Cloudflare R2配置
R2_ACCESS_KEY_ID=YOUR_R2_ACCESS_KEY_ID
R2_SECRET_ACCESS_KEY=YOUR_R2_SECRET_ACCESS_KEY
R2_BUCKET_NAME=YOUR_R2_BUCKET_NAME
R2_ACCOUNT_ID=YOUR_R2_ACCOUNT_ID
R2_REGION=auto # R2存储区域

# AWS S3配置
S3_ENDPOINT= # S3服务端点
S3_ACCESS_KEY_ID= # S3访问密钥
S3_SECRET_ACCESS_KEY= # S3密钥
S3_BUCKET_NAME= # S3存储桶名称
S3_REGION= # S3存储区域

# ====== 资源配额 ======
NEXT_PUBLIC_STORAGE_FIXED_QUOTA=1073741824 # 固定存储配额(字节)
NEXT_PUBLIC_TRANSLATION_FIXED_QUOTA= # 固定翻译配额(可选)

# ====== 翻译服务 ======
DEEPL_PRO_API_KEYS=YOUR_DEEPL_PRO_API_KEYS # DeepL专业版API密钥
DEEPL_FREE_API_KEYS=YOUR_DEEPL_FREE_API_KEYS # DeepL免费版API密钥
DEEPL_X_FINGERPRINT= # DeepL安全指纹
DEEPL_PRO_API= # DeepL专业版API端点
DEEPL_FREE_API= # DeepL免费版API端点

# ====== 高级功能 ======
NEXT_PUBLIC_USE_APPLE_SIGN_IN=false # 启用Apple登录
NEXT_PUBLIC_DISABLE_UPDATER= # 禁用自动更新
NEXT_PUBLIC_DIST_CHANNEL=readest # 发布渠道
ENABLE_IAP_API_TESTS=false # 启用IAP测试
USE_CUSTOM_OAUTH= # 使用自定义OAuth

# ====== 服务器配置 ======
PROTOCOL=http # 服务协议: http/https
HOST=localhost:3000 # 服务主机
ANALYZE=false # 启用bundle分析
R2_TOKEN_VALUE=YOUR_R2_TOKEN_VALUE # R2访问令牌(可选)

Database Supabase Configuration

Create Database Tables

Execute the following SQL statements to create tables.

The official Supabase Tables Schema for Sync API lacks two columns source_title and metadata in the public.books table. The transform.ts file (apps/readest-app/src/utils/transform.ts) will attempt to write data to these two columns, and the absence of these columns in the public.books table will directly cause errors.

In short, running my SQL below will suffice.

create table public.books (
  user_id uuid not null,
  book_hash text not null,
  format text null, -- 'EPUB' | 'PDF' | 'MOBI' | 'CBZ' | 'FB2' | 'FBZ'
  title text null,
  source_title text null,
  author text null,
  "group" text null,
  tags text[] null,
  metadata text null,
  created_at timestamp with time zone null default now(),
  updated_at timestamp with time zone null default now(),
  deleted_at timestamp with time zone null,
  uploaded_at timestamp with time zone null,
  progress integer[] null,
  group_id text null,
  group_name text null,
  constraint books_pkey primary key (user_id, book_hash),
  constraint books_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

ALTER TABLE public.books ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_books ON public.books
  FOR SELECT to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY insert_books ON public.books
  FOR INSERT to authenticated WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY update_books ON public.books
  FOR UPDATE to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY delete_books ON public.books
  FOR DELETE to authenticated USING ((select auth.uid()) = user_id);

create table public.book_configs (
  user_id uuid not null,
  book_hash text not null,
  location text null,
  progress jsonb null,
  search_config jsonb null,
  view_settings jsonb null,
  created_at timestamp with time zone null default now(),
  updated_at timestamp with time zone null default now(),
  deleted_at timestamp with time zone null,
  constraint book_configs_pkey primary key (user_id, book_hash),
  constraint book_configs_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

ALTER TABLE public.book_configs ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_book_configs ON public.book_configs
  FOR SELECT to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY insert_book_configs ON public.book_configs
  FOR INSERT to authenticated WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY update_book_configs ON public.book_configs
  FOR UPDATE to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY delete_book_configs ON public.book_configs
  FOR DELETE to authenticated USING ((select auth.uid()) = user_id);

create table public.book_notes (
  user_id uuid not null,
  book_hash text not null,
  id text not null,
  type text null,
  cfi text null,
  text text null,
  style text null,
  color text null,
  note text null,
  created_at timestamp with time zone null default now(),
  updated_at timestamp with time zone null default now(),
  deleted_at timestamp with time zone null,
  constraint book_notes_pkey primary key (user_id, book_hash, id),
  constraint book_notes_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

ALTER TABLE public.book_notes ENABLE ROW LEVEL SECURITY;

CREATE POLICY select_book_notes ON public.book_notes
  FOR SELECT to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY insert_book_notes ON public.book_notes
  FOR INSERT to authenticated WITH CHECK ((select auth.uid()) = user_id);
CREATE POLICY update_book_notes ON public.book_notes
  FOR UPDATE to authenticated USING ((select auth.uid()) = user_id);
CREATE POLICY delete_book_notes ON public.book_notes
  FOR DELETE to authenticated USING ((select auth.uid()) = user_id);

-- Create the `files` table
create table public.files (
  id uuid not null default gen_random_uuid (),
  user_id uuid not null,
  book_hash text null,
  file_key text not null,
  file_size bigint not null,
  created_at timestamp with time zone null default now(),
  deleted_at timestamp with time zone null,
  constraint files_pkey primary key (id),
  constraint files_file_key_key unique (file_key),
  constraint files_user_id_fkey foreign KEY (user_id) references auth.users (id) on delete CASCADE
) TABLESPACE pg_default;

-- Add an index for efficient querying by user_id and deleted_at
create index idx_files_user_id_deleted_at
on public.files (user_id, deleted_at);

create index idx_files_file_key
on public.files (file_key);

create index idx_files_file_key_deleted_at
on public.files (file_key, deleted_at);

-- Enable RLS on the `files` table
alter table public.files enable row level security;

create policy "Users can insert their own files"
on public.files
for insert
with check (
  auth.uid() = user_id
);


create policy "Users can view their own active files"
on public.files
for select
using (
  auth.uid() = user_id and deleted_at is null
);


create policy "Users can soft-delete their own files"
on public.files
for update
using (
  auth.uid() = user_id
)
with check (
  deleted_at is null or deleted_at > now()
);

create policy "Users can delete their own files permanently"
on public.files
for delete
using (
  auth.uid() = user_id
);

Configure Supabase site URL

In the Supabase dashboard, find Authentication (on the left) > site URL and change the site URL to your website URL.

Configure Users

Readest's users are managed in Supabase. You can add or delete users in Authentication > Users.

In Authentication > Policies, you can set login/register methods (Email/Google/Github/Apple), but Readest will still display these options on the login interface, just disabling them will result in login failures.

You can also disable user registration in Authentication > Policies.

I did the entire process on my phone, which is really a piece of junk. Diagonal 9300+ with 16.0GB RAM, I can't figure out why debugging in Termux is so laggy. The web page gets closed as soon as it leaves the foreground. My phone's performance is terrible.

If you enjoyed this, leave a comment~