Evidence-based technical overview and setup guide for the Quaslation web application.
- Version: 3.0.2 from package.json
- Maturity: Active development with semantic versioning and detailed changelog entries (CHANGELOG.md)
- CI: GitHub Actions build workflow present (.github/workflows/nextjs-build.yml)
- Project description
- Key features and where they live
- Architecture overview
- Architecture diagram
- Data model
- Entity relationships
- Setup and local development
- Production and deployment
- CLI tools
- Environment variables
- Testing, linting, and type checking
- CI/CD
- Observability and analytics
- Security
- Performance and scalability
- Roadmap and known limitations
- Contributing
- License and contact
- Differentiators at a glance
Quaslation is a Next.js App Router application for publishing high-quality fan translations of Asian web novels. It provides a reader UX with native commenting, RSS feed, SEO utilities, an admin surface for managing novels and chapters with moderation tools, and CLIs to fetch, translate, and import content into PostgreSQL via Drizzle ORM.
- Reader experience and site pages
- Home and marketing: page.tsx
- Novels and listing: page.tsx
- Novel details: page.tsx
- Chapter reading: page.tsx
- MDX content pages: about/page.mdx, privacy/page.mdx, terms-of-service/page.mdx
- Navigation and layout: RootLayout(), Navbar()
- Comments system
- Comment API endpoints: route.ts
- Comment components: comment-section.tsx, comment-list.tsx, comment-item.tsx, comment-form.tsx
- Comment database schema: Comment table in schema.ts with novelId, userId, content, isHidden, isEdited fields
- Feeds and SEO
- API routes
- Comments API: route.ts - GET endpoint with role-based filtering and Clerk user enrichment
- User management: src/app/api/users
- Authentication and session
- Clerk integration: ClerkProvider within RootLayout()
- Sign in/up routes provided under src/app/auth
- Admin application
- Admin index: page.tsx
- Chapters admin: page.tsx
- Novel admin: page.tsx
- Create/edit flows under src/app/admin
- Community
- Discord join/support UI: join-discord.tsx
- Analytics and ads
- Vercel Analytics and Google Analytics: RootLayout()
- Google AdSense injection: google-adsense.tsx and layout.tsx
- Translation tooling
- Gemini-based translation and uploader: scripts/gemini/README.md, main.ts, upload.ts
- Kakuyomu fetcher and Gradio-based translator: scripts/translation/README.md, kakuyomu-fetch.ts, translate.ts
- Database access layer (Drizzle ORM)
- Schema and relations: schema.ts, relations.ts
- Runtime db instance: index.ts
- Composable queries: query.ts including getReleases(), getNovelBySlug(), getChapterBySlug(), getUserRole()
- Platform and frameworks
- Next.js 16 App Router with React 19.2 and TypeScript
- Styling: Tailwind CSS with typography plugin and animations
- UI primitives: Radix UI and components under src/components/ui
- Persistence and ORM
- PostgreSQL with Drizzle ORM; schema in schema.ts and relations in relations.ts
- Runtime driver uses @vercel/postgres via index.ts; migration tooling via drizzle-kit configured in drizzle.config.ts
- Authentication and authorization
- Clerk for auth with server/client components initialized in RootLayout()
- Role model via enum Role ADMIN/SUBSCRIBER/MEMBER in schema.ts
- Helper for role retrieval getUserRole()
- API layer
- REST API routes under src/app/api for comments and user management
- Role-based access control with Clerk integration
- Batch fetching and enrichment of user data from Clerk
- Caching and content delivery
- Next ISR and tag-based invalidation on RSS route via unstable_cache in GET()
- Client router cache reuse tuned via experimental.staleTimes in next.config.mjs
- Community features
- Native comment system with moderation capabilities
- Discord webhooks for contact/support: actions.ts
- Analytics and monetization
- Vercel Analytics and Google Analytics in RootLayout()
- Google AdSense in layout.tsx
flowchart LR
Browser -->|HTTP| NextApp[Next App Router]
NextApp -->|Auth| Clerk[Clerk]
NextApp -->|ORM| Drizzle[Drizzle ORM]
Drizzle -->|SQL| Postgres[PostgreSQL]
NextApp -->|Analytics| VercelAnalytics[Vercel Analytics]
NextApp -->|GA| GoogleAnalytics[Google Analytics]
NextApp -->|Webhook| Discord[Discord Webhook]
Tools[CLI Tools] -->|Import| Postgres
Tools -->|HTTP| Gradio[Gradio API]
Tools -->|API| Gemini[Gemini API]
Primary entities defined in schema.ts:
- User: clerkId (PK), role (enum: ADMIN/SUBSCRIBER/MEMBER)
- RichText: text, html, markdown
- Novel: slug, title, thumbnail, timestamps, richTextId (unique)
- Volume: number, title, novelId, timestamps
- Chapter: premium, slug, novelId, volumeId, serial, number, title, timestamps, richTextId
- Comment: id, content, novelId, userId, isHidden, isEdited, timestamps Notable constraints:
- Unique Novel.slug/title; unique Novel.richTextId link
- Volume unique on novelId+number
- Chapter unique on novelId+serial and volumeId+number; index on premium
- Comment indexed on novelId, userId, and createdAt; cascading deletes on novel/user removal
erDiagram
NOVEL ||--o{ VOLUME : has
NOVEL ||--o{ CHAPTER : has
NOVEL ||--o{ COMMENT : receives
VOLUME ||--o{ CHAPTER : groups
RICH_TEXT ||--|| NOVEL : describes
RICH_TEXT ||--|| CHAPTER : content
USER ||--o{ COMMENT : writes
erDiagram
USER {
TEXT clerkId PK
ENUM role
}
RICH_TEXT {
INT id PK
TEXT text
TEXT html
TEXT markdown
}
NOVEL {
INT id PK
TEXT slug
TEXT title
TEXT thumbnail
TIMESTAMP createdAt
TIMESTAMP publishedAt
TIMESTAMP updatedAt
INT richTextId FK
}
VOLUME {
INT id PK
DOUBLE number
TEXT title
TIMESTAMP createdAt
TIMESTAMP publishedAt
TIMESTAMP updatedAt
INT novelId FK
}
CHAPTER {
INT id PK
BOOLEAN premium
TEXT slug
INT novelId FK
INT volumeId FK
INT serial
DOUBLE number
TEXT title
TIMESTAMP createdAt
TIMESTAMP publishedAt
TIMESTAMP updatedAt
INT richTextId FK
}
COMMENT {
INT id PK
TEXT content
INT novelId FK
TEXT userId FK
BOOLEAN isHidden
BOOLEAN isEdited
TIMESTAMP createdAt
TIMESTAMP updatedAt
}
NOVEL ||--o{ VOLUME : contains
NOVEL ||--o{ CHAPTER : contains
NOVEL ||--o{ COMMENT : receives
VOLUME ||--o{ CHAPTER : groups
RICH_TEXT ||--|| NOVEL : describes
RICH_TEXT ||--|| CHAPTER : content
USER ||--o{ COMMENT : writes
Prerequisites
- Node.js 20.x (CI uses Node 20 in .github/workflows/nextjs-build.yml)
- PostgreSQL instance (local or cloud); see migration docs under scripts/migrate
Install and configure
- Install dependencies
-
npm install
-
- Create environment file
-
cp .env.example .env.local
-
- Populate required env vars (see Environment variables), especially DIRECT_URL for drizzle-kit
Database schema and migrations
- Drizzle config points to schema.ts and outputs to src/lib/db in drizzle.config.ts
- Commands
- Generate SQL:
npm run db:generate
- Apply migrations:
npm run db:migrate
- Push schema:
npm run db:push
- Studio:
npm run db:studio
- Generate SQL:
Run locally
- Development server:
npm run dev
- Lint:
npm run lint
- Type check:
npx tsc --noEmit
Build and start
- Production build:
npm run build
- Start server:
npm run start
- Runtime DB driver is @vercel/postgres via index.ts, indicating Vercel-friendly deployment. @vercel/analytics is enabled.
- Alternative Postgres providers can be used if environment variables are provided. Migration docs cover Supabase → Neon: scripts/migrate/README.md
- Images remotePatterns whitelist Supabase storage in next.config.mjs
Gemini translation toolkit
- Guide: scripts/gemini/README.md
- Translate with concurrency and resume:
-
npx tsx scripts/gemini/main.ts --volume all --concurrency 2
-
- Upload translated markdown to DB:
-
npx tsx scripts/gemini/upload.ts --novel-id 17 --verbose
-
Kakuyomu fetcher and Gradio translator
- Guide: scripts/translation/README.md
- Fetch chapters:
-
npx tsx scripts/translation/kakuyomu-fetch.ts --url https://kakuyomu.jp/works/16818093090655323692 --out scripts/output/translation
-
- Translate with resume/skip:
-
npx tsx scripts/translation/translate.ts --baseDir scripts/output/translation --work 16818093090655323692 --concurrency 2
-
Defined in .env.example and referenced in code
- Authentication
- CLERK_SECRET_KEY
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
- NEXT_PUBLIC_CLERK_SIGN_IN_URL (default /auth/sign-in)
- NEXT_PUBLIC_CLERK_SIGN_UP_URL (default /auth/sign-up)
- Analytics and ads
- NEXT_PUBLIC_GOOGLE_ANALYTICS_ID used in RootLayout()
- NEXT_PUBLIC_GOOGLE_ADSENSE_ID used in layout.tsx
- Community and support
- DISCORD_WEBHOOK_URL used in utils.ts
- DISCORD_WEBHOOK_SUPPORT_URL used in actions.ts
- Database and tooling
- DIRECT_URL used by drizzle-kit in drizzle.config.ts
- Caching (optional)
- KV_REST_API_URL - Vercel KV for role caching
- KV_REST_API_TOKEN - Vercel KV authentication token
- AI integrations
- GEMINI_API_KEY used in main.ts
- GRADIO_API_URL used in translate.ts and admin translate-actions.ts
- Tests: No test suites detected in package.json. TODO: add unit/integration tests and coverage.
- Linting: ESLint via Next config; run
npm run lint
- Type checking: run
npx tsc --noEmit
- Build verification in GitHub Actions: .github/workflows/nextjs-build.yml uses Node 20, restores Next cache, and runs build.
- No coverage or deploy workflows present in repo; badges intentionally omitted.
- Vercel Analytics initialized in RootLayout()
- Optional Google Analytics via NEXT_PUBLIC_GOOGLE_ANALYTICS_ID
- Authentication via Clerk; app shell wraps with ClerkProvider
- Role model for users via enum Role in schema.ts
- Premium content gating via Chapter.premium and indexed access patterns in schema.ts and queries like getReleases()
- Webhooks to Discord for support requests actions.ts
- Secrets via environment variables; ensure .env.local is not committed
- Query design leverages indices and joins in query.ts
- Cached feed generation via ISR and unstable_cache GET()
- Client router cache reuse via staleTimes in next.config.mjs
- Pagination and limits applied in getReleases(), getChapters()
- Add automated tests and coverage reporting. TODO
- Add deploy workflow (Vercel or other) and optional status badges. TODO
- Clarify runtime DB configuration for local dev when not using @vercel/postgres. TODO
- Containerization and dev database bootstrap scripts. TODO
- Observability beyond analytics: structured logging and tracing. TODO
- This repository is currently source-available; external contributions are not accepted.
- Code quality: run lint and type-check before changes.
- For translation tool contributions, see scripts/gemini/README.md and scripts/translation/README.md
- Treat as source-available; do not redistribute without permission.
- Maintainer contact: shreyashr267@gmail.com
- End-to-end pipeline for novel translation: fetch, translate (Gemini or Gradio), and upload to DB, documented under scripts
- Clean, type-safe data access with Drizzle and explicit schema schema.ts
- Production-aware caching and SEO with RSS + sitemap GET(), sitemap()
- Native comment system with admin moderation and Clerk integration