Storefront API returning HTML error pages with 200 status code

We’re seeing an increase in cases where the Storefront GraphQL API returns HTML error pages instead of JSON, while still responding with HTTP 200. This causes JSON parsing to fail on the client side.

The response body appears to be a Shopify error/maintenance page rather than a valid GraphQL JSON response. Since the status code is 200, standard HTTP error handling doesn’t catch it — the failure only surfaces when attempting to parse the response.

This has been happening intermittently for a while but the frequency has increased noticeably over the past week or so.

Expected behavior: The Storefront API should always return valid JSON for GraphQL requests, or at minimum return an appropriate non-200 status code when it cannot serve the request properly, so clients can handle the error without attempting to parse HTML as JSON.

Hi @Zak - thanks for reporting this.

It looks like this may be happening to a few other community members as well, judging from the reactions on this thread. It also looks like there could be a few different reasons for this.

To investigate we’ll need to trace some of these requests, but since this is intermittent you may not have the details from past occurrences handy. If you’re able to add some logging to capture response headers when it happens, we’re looking for the x-request-id and/or cf-ray values specifically - depending on where in in the request chain the HTML is being generated, one or both should be present. 3-5 examples would be ideal if you have them available.

If anyone else here is seeing the same thing, please feel free to share examples in this thread as well as this will help us look into this further.

Hey Wes, unfortunately none of those headers is present when this issue happens. I can give you myshopify.com domain, timestamp, user IP and which query was being sent when this happened, if that can be useful to debug this.

You can see below the Sentry graph for this error, since we started actively tracking it in April. Something has changed since May.

Thanks for the additional context @Zak - We’re currently investigating and I’ve sent you a DM requesting some details. I’ll follow up on this thread as soon as I have any additional information to share.

Rectifying here, the response comes back with content type application/json; charset=utf-8 and the body is simply empty, no HTML response, just empty with status 200.

Hey guys, I’ve got this too.. This is a big conversion killer, please give this priority!!

Hey everyone :wave: 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.