A free, ad-free streaming and discovery platform dedicated to Korean animation - Aeni.
Resaeni is a focused catalogue and watch experience for Korean animation, also known as Aeni. Instead of becoming another broad anime index, the project is intentionally scoped around helping people discover, explore, choose an episode, and watch Korean animated titles in one place.
The product philosophy is simple: watching should stay clean and interruption-free. Resaeni does not intentionally place advertisements around common playback interactions such as play, pause, seeking forward, or rewinding.
Korean animation is harder to discover than mainstream Japanese anime, so Resaeni treats focus as a product decision. The app prioritizes a narrow catalogue, direct navigation, and a quiet viewing surface over extra social or engagement features.
Current flow:
Discover -> Explore -> Choose Episode -> Watch
- Home discovery: featured Aeni and curated tonight picks from the Worker
/api/homeendpoint. - Aeni Library: searchable, filterable, paginated catalogue at
/anime. - Aeni details: title metadata, synopsis, genres, status, episode count, episodes, characters, and voice actor data.
- Streaming page: native HTML video playback with previous/next episode navigation and an episode list.
- Info & Support: an About Resaeni page for product context, plus a Report an Issue form so visitors can flag problems directly from the site.
- Responsive interface: desktop navigation plus a mobile bottom navigation and responsive layouts across catalogue, detail, and watch pages.
- Loading and fallback states: skeleton screens, image placeholders, image error assets, empty states, and API error states.
The Info & Support area groups the project's informational and feedback surfaces:
Info & Support
├── About Resaeni # /info/about - what the project is and why it exists
└── Report an Issue # /info/report - report problems directly from the site
Report an Issue lets visitors submit problems without leaving the app. The form collects:
- Issue Type (required): Playback problem, Missing or incorrect episode, Incorrect Aeni information, Artwork or image problem, Audio or subtitle problem, or Other.
- Aeni Title and Episode (both optional) for context.
- Description (required, 10-1000 characters).
- Optional image attachments: up to 3 images, maximum 10 MB per image, PNG/JPEG/WebP only.
The frontend validates input for fast feedback, and the Worker re-validates everything server-side - including checking that attachment bytes actually match their declared image type - before anything is persisted.
- Separate frontend and API packages: the React/Vite application lives at the repository root, while the Hono API is maintained as a separate
worker/package. This keeps UI code and edge API code independently runnable. - Cloudflare-oriented deployment: the frontend uses the Cloudflare Vite plugin and deploys built assets through Wrangler; the API runs as a Cloudflare Worker with its own Wrangler config.
- Turso-backed data: Worker routes use
@libsql/clientto read anime metadata, episodes, episode links, characters, and voice actors from Turso/libSQL. Turso also stores issue reports and their attachment metadata. - Cloudflare R2 attachments: validated report images are uploaded through the Worker's R2 binding (
REPORTS_BUCKET); no R2 credentials are exposed to the frontend. Attachment binaries live in R2 while their metadata lives in Turso. - Full-stack Report an Issue flow: React builds a multipart
FormData, Axios posts it through a shared service to the Hono Worker, which validates, persists the report row to Turso, uploads attachments to R2, then records attachment metadata back in Turso. - Server-side validation: frontend checks exist for UX; the Worker independently enforces field lengths, allowed issue types, attachment count, size limits, MIME allowlisting, and file-signature verification.
- Compensating cleanup across systems: there are no cross-system transactions between Turso and R2, so if a report insert or attachment upload fails partway through, the handler deletes any R2 objects it uploaded and removes the partially created report row, reducing orphaned data.
- Route-focused API design:
/api/home,/api/anime,/api/anime/:mal_id,/api/download, and/api/reportsare split into route folders with handlers and utilities that match their domain. - Pragmatic data fetching: TanStack Query handles client-side request caching and retry behavior for reads, while mutations (such as report submission) use
useMutation. - SEO as active maintenance: route-level meta tags, canonical URLs, Open Graph/Twitter metadata, og:site_name, JSON-LD helpers, sitemap generation from static plus API-fetched detail URLs, robots.txt generation, and build-time HTML prerendering. Utility routes such as
/info/reportare servednoindex, followwhile content pages stay indexable.
- React 19
- Vite
- Chakra UI v3
- React Router
- TanStack Query
- Axios
- Lucide React
- Cloudflare Workers
- Hono
- TypeScript
@libsql/client
- Turso/libSQL for catalogue data, issue reports, and attachment metadata
- Cloudflare R2 for report image attachments (public delivery via
reports.resaeni.cc) - Environment-configured video base URL with episode link paths returned by the API
- Wrangler
- Cloudflare Vite plugin
- ESLint
- Prettier
- Node test runner for SEO helper coverage
- Vitest configured for Worker tests
/ Home
/anime Aeni Library
/anime/:mal_id Aeni Details
/anime/:mal_id/episode/:episode_number Watch
/info Redirects to /info/about
/info/about About Resaeni
/info/report Report an Issue (noindex)
GET /health Service health check
GET /api/home Home discovery payload
GET /api/anime Paginated, filterable Aeni list
GET /api/anime/:mal_id Aeni details, episodes, characters
GET /api/download Download endpoint
POST /api/reports Create an issue report (multipart form data)
Report an Issue Form
|
v
POST /api/reports (multipart FormData)
|
v
Cloudflare Worker (validation)
| \
v v
Turso Cloudflare R2
report image
+ meta attachments
The Worker validates all fields and attachments first, inserts the report row into Turso (issue_reports), uploads each accepted image to R2 under issue-reports/<report-id>/, and records each upload in issue_report_attachments. If any step fails, uploaded R2 objects and the partial report row are cleaned up before returning an error.
Requires Node.js 24 or newer.
Install and run the frontend:
npm install
npm run devInstall and run the API Worker:
cd worker
npm install
npm run devFrontend environment variables:
VITE_API_BASE_URL # Backend API base URL
VITE_SITE_URL # Canonical site URL used by SEO build scripts
VITE_BASE_VIDEO_URL # Base URL for video playback sources
VITE_BASE_ASSETS_URL # Base URL for image/artwork assets
Worker environment variables:
TURSO_DATABASE_URL # Turso/libSQL database URL
TURSO_AUTH_TOKEN # Turso auth token
REPORTS_BUCKET # Cloudflare R2 binding (configured via wrangler.jsonc, not a secret)
Root application:
npm run dev
npm run build # sitemap generation + Vite build + prerender
npm run test
npm run lint
npm run format:check
npm run deployWorker:
cd worker
npm run dev
npm run typecheck
npm test
npm run deployTechnical SEO is treated as ongoing product work rather than a finished checklist:
- Route-specific titles, descriptions, robots directives, and canonical URLs via a shared
Seocomponent. - Open Graph and Twitter Card metadata, including
og:site_nameand a dedicated social image. - JSON-LD structured data helpers for content pages.
- Sitemap generation at build time, combining static routes with Aeni detail URLs fetched from the API.
- robots.txt generation alongside the sitemap.
- Build-time HTML prerendering so crawlers receive meaningful markup for SPA routes.
Content pages remain indexable while utility pages such as /info/report are intentionally served noindex, follow.
Resaeni is maintained as an evolving product rather than a finished demo. The codebase and dependencies are reviewed regularly, with dependency maintenance planned as part of a monthly maintenance cycle.
Catalogue freshness is currently a maintenance responsibility, not an automatic synchronization feature in this repository. If automated content updates are added later, they should be documented from the implementation rather than implied.
