Find tonight's movie without the 45-minute debate.
Movies you both want to watch, with ratings and where to stream.
Matchboxd is a lightweight MVP that compares two public Letterboxd watchlists and builds one shortlist you can actually use. Enter two usernames, get the overlap first, add a few near-miss picks from each person, and open JustWatch searches in the detected or selected country.
The current product is intentionally narrow. Letterboxd is the first acquisition wedge and taste-data source, not necessarily the long-term center of the product. The larger strategic question is whether Matchboxd should grow into a broader social entertainment, taste-matching, group decision, or watch-planning product before any large feature investment.
- Reads two public Letterboxd watchlists
- Finds the shared overlap between them
- Adds a balanced set of near-miss picks from each person's list
- Progressively enriches each film with poster, rating, rating count, runtime, genres, and directors
- Lets users filter by mood, source, runtime, and rating
- Builds JustWatch search links using detected country with a manual override
The live app intentionally keeps the product small:
landing -> pair-loading -> pair-results
There are no accounts, no database-backed history, and no saved matches beyond the small session state stored in local storage for the current browser.
- Current product: public Letterboxd watchlist comparison for two people.
- Current constraints: solo developer, low operating budget, and no planned large build without validation or outside funding.
- Current dependency risk: public Letterboxd HTML scraping is useful for the MVP, but brittle as a commercial foundation.
- Current validation priority: understand whether this is a standalone product, a social entertainment wedge, an indie utility, or a VC-scale consumer/social opportunity.
- Strategy handoff package: see docs/strategy-research-package.md for the browse-enabled LLM research prompt and context.
- Product orchestration playbook: see docs/product-orchestration/README.md for the advisory evidence loop, event contract, agent prompts, weekly memo template, and Sheets-ready evidence hub.
- Matchboxd fetches both users' public watchlists through a proxy path because browsers cannot read Letterboxd HTML directly.
- It finds the overlap between the two lists.
- It adds near-miss candidates from each person's non-overlapping titles, alternating between users so one side does not dominate the list.
- It renders stubs quickly, then progressively enriches them with detail-page data.
- The results page lets the user filter, sort, and switch JustWatch country before opening streaming searches.
By default, the pairing logic caps enrichment to:
- up to 60 overlapping titles
- up to 40 near-miss titles
- React 18
- TypeScript
- Vite
- Tailwind CSS
- Framer Motion
- Vitest
- Cloudflare Workers
- GitHub Pages
src/
app/
SessionContext.tsx persisted session + in-memory pairing state
features/
onboarding/ landing page and username validation
pair/ loading/results flow, filters, and hooks
components/ shared layout and UI primitives
services/
letterboxd/ proxy client, watchlist scraping, film scraping
pairWatchlists/ candidate selection, progress, enrichment helpers
countryDetection.ts JustWatch region detection and override logic
worker/
index.js Cloudflare Worker proxy for production
local-proxy.mjs tiny local proxy for development
.github/workflows/
ci.yml typecheck, test, build
deploy.yml GitHub Pages deploy
docs/
strategy-research-package.md product strategy research handoff prompt
product-orchestration/ advisory product-intelligence loop and evidence hub
- Canonical accent pink is
#ec4899. #f9a8d4is reserved for overlap glow and supporting highlight, not for one participant.- The two participant circles are intentionally equal in hue and opacity.
- The overlap should remain the brightest point in the mark.
- Node.js 20+
- npm
npm install
cp .env.example .env
npm run devThe app will be available at the local Vite URL printed in the terminal.
Letterboxd scraping is more reliable in local development when the proxy is running:
node worker/local-proxy.mjsWith the default VITE_LETTERBOXD_PROXY_MODE=auto, the app will prefer the local proxy on http://localhost:8787 during development when it is available.
Important: Matchboxd is a client-side app. Any VITE_* variable ends up in the shipped JavaScript bundle and should be treated as public.
| Variable | Purpose | Default |
|---|---|---|
VITE_LETTERBOXD_PROXY_MODE |
auto uses the local proxy in dev and the hosted worker/default chain elsewhere; custom prepends your own proxy base URL |
auto |
VITE_LETTERBOXD_PROXY_BASE_URL |
Base URL for a custom proxy or Worker that accepts ?url= and returns raw HTML |
(empty) |
VITE_ENABLE_PUBLIC_PROXY_FALLBACKS |
Allows public proxy fallbacks after controlled proxies fail | false |
VITE_APP_BASE_PATH |
Base path for the deployed SPA | / |
VITE_POSTHOG_API_KEY |
Optional public PostHog project API key; leave empty to disable product analytics | (empty) |
VITE_POSTHOG_HOST |
PostHog capture host | https://us.i.posthog.com |
VITE_APP_VERSION |
Optional release label attached to analytics payloads | (empty) |
Deprecated aliases are still supported for one migration pass:
VITE_RSS_ADAPTERVITE_RSS_BASE_URL
| Command | What it does |
|---|---|
npm run dev |
Starts the Vite development server |
npm run build |
Type-checks and builds the production bundle |
npm run preview |
Serves the production build locally |
npm run lint |
Runs TypeScript type-checking with no emit |
npm run perf:selection |
Regenerates the deterministic before/after selection benchmark report |
npm test |
Runs the full Vitest suite once |
npm run test:watch |
Runs Vitest in watch mode |
Run the main checks locally with:
npm run lint
npm test
npm run buildThe repository also includes:
- integration coverage for the app flow
- service tests for filtering, pair selection, pairing, country detection, and proxy behavior
- worker tests for origin allowlists, target allowlists, health checks, and successful proxy responses
GitHub Actions runs these checks on pull_request and on pushes to main via .github/workflows/ci.yml.
Matchboxd includes lightweight first-party product event instrumentation through a small PostHog-compatible wrapper. It is disabled by default and sends nothing unless VITE_POSTHOG_API_KEY is configured.
Available signals:
- Cloudflare zone analytics after DNS proxying: traffic, cache, threats, bot traffic, country, and device signals
- Cloudflare Worker analytics: proxy request volume, latency, and error rates
- PostHog product events once
VITE_POSTHOG_API_KEYis configured - GitHub repository insights: stars, clones, visitors, and referrers
- GitHub Pages and Actions history: deploy/build health, but not product usage
Currently implemented product events:
landing_viewpair_startpair_successpair_failureresults_filter_changedjustwatch_clickrestart_clickedperformance_metric
Future events to add only when the corresponding UI exists:
share_clickedif sharing is addedfeedback_submittedsave_pair_clickedadd_friend_clicked
Do not claim activation, retention, conversion, or willingness-to-pay data until it comes from analytics exports, interviews, or explicit validation tests.
The current product-orchestration setup is intentionally manual before platform automation:
docs/product-orchestration/matchboxd-evidence-hub.xlsxis a Google Sheets-ready evidence hub template.docs/product-orchestration/event-contract.mddefines the product event contract to implement before relying on behavior analytics.docs/product-orchestration/agent-playbook.mdandweekly-memo-template.mddefine the weekly advisory agent loop.docs/product-orchestration/implementation-workflow.mdextends the loop with Implementation Planner, Developer, Tester, and Verifier / Release Gate roles.- n8n, Make, GitHub Actions, or Apps Script should be added only after the manual weekly loop proves useful and repetitive.
The frontend deploys to GitHub Pages through .github/workflows/deploy.yml.
That workflow:
- installs dependencies
- builds the app
- copies
dist/index.htmltodist/404.htmlfor SPA fallback - publishes the build through GitHub Pages
For the preferred production proxy path, deploy the Cloudflare Worker in worker/:
cd worker
npx wrangler deployThe worker:
- only allows approved origins
- only proxies
https://letterboxd.com/...targets - returns
X-Client-Countryso the frontend can derive the default JustWatch region
After deploying your own worker, point the app at it with:
VITE_LETTERBOXD_PROXY_MODE=custom
VITE_LETTERBOXD_PROXY_BASE_URL=https://<your-worker>.workers.dev/- Matchboxd scrapes public Letterboxd HTML pages. It does not use an official Letterboxd API.
- Public profiles and public watchlists are required.
- JustWatch links are search URLs, not guaranteed deep links to an exact title page.
- The controlled worker path is the preferred production setup.
- Public proxy fallbacks are intentionally opt-in.
- Recent repo-side security work added
/.well-known/security.txt, GitHub security policy files, and refreshed Open Graph/Twitter share preview assets. Some security and bot settings still depend on Cloudflare/GitHub dashboard configuration rather than app code.
Couldn't find @user on LetterboxdUsually means the username is misspelled or does not exist.@user's watchlist is set to privateThe profile may exist, but the watchlist is not publicly readable.Couldn't read one or both watchlistsA proxy may be blocked or rate-limited. Retry after a short wait or switch to a controlled proxy.
MIT