PROJECT / 2024
AniMatch
Anime Recommendation
Anime recommender that runs multi-field cosine similarity both server-side (pgvector on Neon Postgres) and client-side (Web Worker), with a Jikan sync pipeline and GitHub Actions keeping embeddings fresh.
- stack
- TypeScript · Next.js · React 19 · Tailwind CSS · Radix UI · Framer Motion · Neon Postgres · pgvector · Google text-embedding-004 · Web Workers · React Query · IndexedDB · Jikan API · Clerk Auth · GitHub Actions
How it works
Jikan
quarterly title syncqueue
daily embedding jobpgvector
five embeddings per titlerecommend
on the server or in your browser
01 / Overview
A Next.js App Router web app for discovering and getting anime recommendations. Users can browse a paginated, filterable catalog, search with debounced full-text queries, and select multiple titles to generate recommendations. The recommendation engine works two ways: a pgvector cosine-distance query on the server, and a Web Worker that computes weighted similarity across five embedding dimensions (synopsis, genres, themes, demographic, rating) entirely in the browser. Embeddings are generated via Google's text-embedding-004 and stored as 768-d vectors in Neon Postgres. A Jikan API sync pipeline backed by GitHub Actions keeps the dataset current — daily runs drain an embedding queue, quarterly runs pull new seasonal titles.
02 / Key features
- –Multi-select recommendations: Pick several anime and get a blended similarity score — the weights across synopsis, genres, themes, demographic, and rating are tunable without rerunning any inference
- –Dual recommendation path: pgvector runs cosine distance on the server for per-anime pages, while a Web Worker handles the multi-select case entirely client-side to avoid per-query server cost
- –Three-tier caching: Postgres is the source of truth, React Query handles in-memory deduplication with stale-while-revalidate, and IndexedDB stores the full embedding dataset in-browser with a 24-hour TTL
- –Browse and search: Paginated anime grid with multi-select genre filters, sort options, and a Ctrl+K search bar that hits Postgres first and falls back to Jikan for missing titles
- –Automated data pipeline: GitHub Actions runs daily to generate embeddings for queued anime (Google text-embedding-004) and quarterly to sync new seasonal titles from the Jikan API
- –Anime detail pages: Individual pages with metadata, server-side recommendations, user reviews from MAL, and character/staff info pulled from Jikan
03 / Challenges
- –Loading thousands of 768-dimensional vectors into the browser for client-side similarity is a big payload. The Web Worker keeps the UI responsive, but memory is the real ceiling — IndexedDB caching with a 24-hour TTL was the compromise between freshness and not re-downloading everything on each visit
- –Vercel's free-tier 10-second function timeout limits embedding generation to 2 anime per invocation. The whole architecture of queue tables, priority levels, and GitHub Actions daily cron exists because you can't just batch-process 500 anime in one serverless call
- –Jikan rate-limits at ~3 requests/second, and a quarterly sync needs to paginate through hundreds of pages. The sync scripts have back-off logic and 429 handling, but bulk imports still take a while — the queue decouples ingestion from embedding so at least the data is immediately searchable
- –The project started on MongoDB with BERT embeddings and migrated to Neon Postgres with pgvector and Google embeddings. That migration involved deduplication across data sources, normalized title matching, and handling edge cases like doubled genre strings from the original scraper
04 / Solution
Next.js handles the frontend, API routes, and worker host. Neon Postgres with pgvector stores both structured anime data and five-field embedding vectors per title. The recommendation engine runs in two modes: server-side pgvector queries for individual anime pages, and a client-side Web Worker for multi-select where the user picks several titles and similarity is computed in-browser from IndexedDB-cached vectors. A priority queue in Postgres decouples data ingestion from embedding generation — search results that arrive without embeddings get enqueued and processed asynchronously by a daily GitHub Actions workflow calling Google's text-embedding-004. Quarterly sync runs pull new titles from the Jikan API.
05 / Outcome
Ships a responsive recommendation app where similarity math runs either on the server or entirely in the browser depending on the use case, with no per-query cost for the multi-select flow. The automated pipeline keeps the dataset growing — new anime get indexed on search, embedded overnight, and available for recommendations by the next visit. Started as a university project with MongoDB and local BERT, then evolved into a production stack on Vercel + Neon with automated syncing.