A web app that analyzes photos of restaurant menus to identify which dishes are safe or risky based on a user's dietary restrictions and allergies.
Status: Proof of concept Last updated: 2026-07-28
- Upload or photograph a restaurant menu
- Select dietary restrictions and allergies (saved in the browser between visits)
- Get dish-by-dish analysis with three ratings: Likely safe, Ask server, Avoid
- Expandable explanations for each flagged dish
- Optional password gate, and a per-visitor daily usage limit
Analysis runs in two separate Claude calls in src/app/api/analyze/route.ts:
- Transcription — the menu image is read and the text extracted verbatim, with no interpretation.
- Analysis — the extracted text is classified against the selected restrictions and returned as JSON.
Splitting the two keeps dish names accurate, and means the image is never given to the step that makes judgments about it.
- Node.js 20.9 or later (required by Next.js 16)
- An Anthropic API key from platform.claude.com
Create a .env.local file in the project root:
ANTHROPIC_API_KEY=your_api_key_here
ACCESS_PASSWORD=your_password_here
NEXT_PUBLIC_AUTH_ENABLED=true
| Variable | Required | Purpose |
|---|---|---|
ANTHROPIC_API_KEY |
Yes | Authenticates calls to Claude. Never commit this value. |
ACCESS_PASSWORD |
Only when the gate is on | Password for the login screen. Accepts several comma-separated passwords (alpha,bravo,charlie) so different people can be given different ones. |
NEXT_PUBLIC_AUTH_ENABLED |
Yes | true shows the password gate; any other value gives open access via a "Get Started" button. |
Note that NEXT_PUBLIC_AUTH_ENABLED is baked into the browser bundle at build time, so changing it requires a rebuild or redeploy — not just a restart. ACCESS_PASSWORD and ANTHROPIC_API_KEY stay server-side and are never sent to the browser.
npm install
npm run devOpen http://localhost:3000 in a browser.
Deploy to Vercel and add all three environment variables (ANTHROPIC_API_KEY, ACCESS_PASSWORD, NEXT_PUBLIC_AUTH_ENABLED) under project settings. Redeploy after changing any of them.
Values worth knowing about, and where to change them:
| Setting | Value | Location |
|---|---|---|
| Claude model | claude-sonnet-5 |
MODEL in src/app/api/analyze/route.ts |
| Response size ceiling | 8192 tokens | MAX_TOKENS in src/app/api/analyze/route.ts |
| Daily analyses per visitor | 20 | DAILY_LIMIT in src/lib/rate-limit.ts |
| Accepted uploads | JPEG, PNG, HEIC, WebP under 10MB | ACCEPTED_TYPES / MAX_SIZE_MB in src/components/ImageUpload.tsx |
- Models get retired. Claude models are withdrawn on a published schedule, and requests to a retired model fail. If every upload starts erroring, check the model deprecation page and update
MODEL. The app reports this case explicitly rather than showing a generic error. - The daily limit is not durable. Usage counts are held in server memory, so on Vercel they reset whenever a new serverless instance starts. The limit discourages casual overuse but will not stop a determined user.
- Large uploads may fail. Uploads are accepted up to 10MB, but encoding for transport inflates them by roughly a third, which can exceed Vercel's 4.5MB request limit. Large photos taken at full resolution are the likely case.
- HEIC is not converted. HEIC files are labelled as JPEG before being sent rather than actually converted, which Claude will reject. iPhones normally convert to JPEG on upload, so this mainly affects
.heicfiles dragged in from a Mac. - This is not medical advice. Output depends on how completely a menu describes its dishes, and hidden ingredients and cross-contamination will not appear in the text. Treat results as a starting point for a conversation with restaurant staff.