Environment: Next.js (App Router), @vercel/blob v2.6.1 (latest), Hobby plan.
Issue: Client uploads using the documented Client Upload flow (handleUpload on a server Route Handler + upload() from @vercel/blob/client in the browser) fail in production. The token generation step succeeds, but the browser's subsequent request is blocked by CORS:
Access to fetch at 'https://vercel.com/api/blob/?pathname=...' from origin 'https://<my-project>.vercel.app' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
The response does return a body (Content-Length ~134 bytes, application/json) but without a CORS header, so the browser blocks reading it. The SDK then retries repeatedly (I've seen 80-150+ requests for a single upload attempt), leaving the UI stuck indefinitely.
What I've verified:
- Inspected the installed
@vercel/blobpackage source — confirmedvercel.com/api/blobis the correct/expected endpoint used by the clientupload()function itself (not something exclusive to server-sideput()). - My implementation matches the documented Client Upload pattern exactly.
BLOB_READ_WRITE_TOKENis correctly set for Production, connected via the project's Storage tab, confirmed with a clean redeploy (no build cache).- Tested in an incognito window (rules out browser extensions).
- Tested on a different network — mobile data instead of Wi-Fi (rules out local network/ISP).
- Tested using the deployment-specific hashed URL instead of the custom domain alias (rules out domain-specific issue).
- Project Firewall: no denied/challenged/rate-limited requests. Bot Protection and Attack Mode: off. Deployment Protection: not actively enforced per the dashboard's own indication.
All of the above fail identically. Vercel support (via the in-dashboard chat) confirmed this needs internal investigation and pointed me here since dedicated Blob support isn't included on the Hobby plan.
Workaround in place: proxying uploads through my own server (fetch with FormData to my own API route, which then calls put() server-to-server) — works, but caps file size at ~4MB (Vercel Functions body limit) instead of the 25MB I had with direct client uploads.
Has anyone seen this, or does anyone from the Vercel team have insight into why vercel.com/api/blob might not be returning CORS headers for a specific project?