How I built a browser-only face-rating app with Next.js + MediaPipe (no upload, $0 per scan) A developer built PSLRate, a browser-only face-rating app that computes attractiveness scores entirely client-side using MediaPipe's FaceLandmarker, with no image uploads or per-scan costs. The free tier analyzes facial geometry metrics like symmetry and proportions via WebGL, while a paid tier adds an LLM-generated personality reading. The architecture ensures infinite free usage by avoiding server-side AI vision APIs. Most "rate my face" tools do one of two sketchy things: they upload your selfie to a server, or they bill an AI vision API on every single scan. I wanted neither. So when I built PSLRate https://pslrate.com , the rule was: the score has to be computed entirely in the browser — no upload, no per-scan cost. Turns out the part of facial-attractiveness scoring that's actually reproducible — geometry — maps perfectly to that constraint. Here's how the whole thing runs client-side, and where I drew the line between "free and infinite" and "costs money." When people say a face is "harmonious," a big chunk of it is measurable proportion: symmetry, the rule of facial thirds, the rule of fifths, eye tilt, facial width-to-height ratio. None of that needs a model on a server — it's just math on landmark coordinates. So I split the product in two: | Free tier | Paid tier | | |---|---|---| | Where it runs | Browser your device | Server | | What it does | Facial geometry → a 0–10 score + per-feature breakdown | An LLM writes a personality/"glow-up" reading | | Cost per use | $0 | An API call | | Your photo | Never leaves the browser | Sent once, on unlock | The free tier being pure geometry is what makes it infinitely free. There's no API meter ticking. That single architectural decision is the whole trick. MediaPipe Tasks Vision https://developers.google.com/mediapipe ships a FaceLandmarker that runs on WebGL and returns 478 3D landmarks per face — entirely client-side. I self-host the model + wasm so there's no third-party CDN call and it loads fine from regions where Google's CDN is flaky : js import { FaceLandmarker, FilesetResolver } from "@mediapipe/tasks-vision"; const vision = await FilesetResolver.forVisionTasks "/mediapipe/wasm" ; const landmarker = await FaceLandmarker.createFromOptions vision, { baseOptions: { modelAssetPath: "/mediapipe/face landmarker.task" }, runningMode: "IMAGE", numFaces: 1, } ; const result = landmarker.detect imageElement ; const pts = result.faceLandmarks 0 ; // 478 points, each {x, y, z} normalized 0..1 That pts array is everything I need. The image element is read straight from an