Hey everyone
thanks for the additional reports. We dug into edge logs and traced specific examples shared by an affected partner, and I want to share what we found because the answer is probably not the one most people are assuming.
TL;DR: In the cases we’ve been able to inspect, the Storefront API is sending a valid 200 with a JSON body. The “HTML error page” / “empty body” symptom is most likely being generated on the client side, by one of three things:
1. A fetch wrapper that assumes every response is JSON / has a body. When a browser makes a cross-origin POST to the Storefront API, it first sends an OPTIONS preflight. SFAPI responds to that preflight with 200 OK, Content-Type: text/html, and an empty body — that’s standard CORS behavior, and browsers only read the Access-Control-Allow-* headers from a preflight. If your client treats OPTIONS responses the same as POST responses and tries to parse every body as JSON, every preflight will surface as “200 with HTML / empty body.”
Fix: scope your error tracking to POST only, or branch on response.headers.get('content-type') and the request method before parsing.
2. @defer responses not being parsed correctly. If your queries use @defer / @stream (most Hydrogen storefronts do, often transitively through Shopify’s own example components), SFAPI returns Content-Type: multipart/mixed; boundary=graphql with a multipart envelope, not raw JSON. A client doing JSON.parse(await response.text()) will throw on that body.
Fix: use a GraphQL client that supports incremental delivery (urql, Apollo Client, graphql-yoga all do), or check the content-type and branch on multipart/mixed.
3. Mobile in-app browser interference. We’re seeing a strong skew toward Facebook, Instagram, Pinterest, TikTok, and WeChat in-app browsers in the requests we looked at. These browsers inject a JavaScript layer that wraps fetch / XMLHttpRequest for ad attribution and “open in default browser” prompts, and that layer has documented cases of swallowing response bodies or stripping headers, especially on iOS WKWebView. If your error reports cluster on these user agents, that’s likely a contributor.
To help us confirm which one is hitting you, could you please share some additional information with us? For example:
- The
User-Agent string of affected requests. (Strings containing FBAN, FBAV, Instagram, Pinterest, MicroMessenger, or TikTok point to an in-app browser.)
- The literal value of
response.headers.get('content-type') for a failing response, and the first ~200 characters of await response.text(). If the content-type is multipart/mixed, that’s #2. If it’s text/html, check whether the request method was OPTIONS — that’s #1.
- The HTTP method of the failing request. If it’s
OPTIONS, that’s #1.
- Whether you can reproduce the failure in a regular Safari / Chrome tab on the same device. If it only reproduces inside an in-app browser, that’s #3.
Thanks again for the replies so far and I hope this helps clarify things.