fix(api): restore the ?domain= dispatch in the /api/icon Vercel wrapper - #297
Merged
Merged
Conversation
What: since PR #260 (2026-08-05) production answered every /api/icon?domain=<host> request with 400 "Missing url". That is the client's primary favicon path (feed-favicon.tsx), so the client fell back to proxying raw site URLs; the generic proxy relayed whatever came back — 1.3 MB of NYT homepage HTML per attempt — as an "icon" with max-age=0, so the CDN never cached it. Vercel alerted on a 5xx spike on /api/icon (2026-09-08, upstream 502s from the fallback) and on Fast Origin Transfer hitting 100% of the free 10 GB (2026-09-04). Why: #260 rewrote api/icon.ts as a thin call to handleProxyRequest and dropped the query-shape dispatch that server.ts and vite.config.js carry. The wrapper contract test checks that api/*.ts import from src/, not what they route; nothing exercised the wrapper's behaviour and no smoke test hit the live endpoint. Fix: the wrapper dispatches ?domain= to handleFaviconRequest, everything else to the image proxy — the same branch as the other two entry points. Prevention: tests/api/icon-wrapper.test.ts runs the wrapper's GET with only fetch mocked and asserts both shapes; tests/smoke/icon.test.ts asserts the deployed function serves ?domain= as a cacheable image. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GQoLHbPfaTCHvPuaoqGZeJ
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
forcingfx
enabled auto-merge (squash)
September 9, 2026 20:30
forcingfx
added a commit
that referenced
this pull request
Sep 9, 2026
…ction (#298) The test added in #297 failed on its first live run for two reasons that the unit run could not show: example.com has no favicon (its ?domain= answer is an empty text/plain 200 and its /favicon.ico a 404), and the default happy-dom environment blocks the cross-origin fetch outright. Fixtures are now our own domain and the file runs under node like every other smoke test. Verified green against my.feedzero.app before commit. Claude-Session: https://claude.ai/code/session_01GQoLHbPfaTCHvPuaoqGZeJ Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Since #260 (2026-08-05) production answered every
/api/icon?domain=<host>request with 400 "Missing url". That is the client's primary favicon path (feed-favicon.tsx), so every client fell back to proxying raw site URLs, and the generic proxy relayed whatever came back, including 1.3 MB of NYT homepage HTML per attempt, as an "icon" withmax-age=0, never CDN-cached.Two Vercel alerts trace to this: the 5xx spike on /api/icon (2026-09-08, upstream 502s from the fallback path) and Fast Origin Transfer at 100% of the free 10 GB (2026-09-04).
Verified live before the fix:
Why
#260 rewrote
api/icon.tsas a thin call tohandleProxyRequestand dropped the query-shape dispatch thatserver.tsandvite.config.jsboth carry. The wrapper contract test only checks thatapi/*.tsimport fromsrc/, not what they route, and there was no smoke test for the endpoint.Fix
The wrapper dispatches
?domain=tohandleFaviconRequestand everything else to the image proxy, the same branch as the other two entry points (three-entry-point rule).Prevention
tests/api/icon-wrapper.test.ts: runs the wrapper'sGETwith onlyfetchmocked and asserts both shapes (red before: 400).tests/smoke/icon.test.ts: asserts the deployed function serves?domain=as a cacheable image.Verification
npx vitest run: full suite green.npx tsc --noEmit: clean.scripts/build-api.jsbundles the new wrapper.SMOKE_TESTS=1 npx vitest run tests/smoke/icon.test.tsagainst production.🤖 Generated with Claude Code
https://claude.ai/code/session_01GQoLHbPfaTCHvPuaoqGZeJ