gatsby: Catch and handle "The result of this StaticQuery could not be fetched"

Preliminary Checks

Description

When an outdated component attempts to fetch stale static query data from the server, the entire site crashes.

While this issue involves the same error message as #26563, I don’t understand what @wardpeet and @sidharthachatterjee have been tasked with solving in that issue. I am reporting a specific reproducible crash.

Gatsby should be able to tolerate a component fetching a static query data JSON file that no longer exists on the server.

Reproduction Link

N/A. This is very hard to reproduce minimally.

Steps to Reproduce

This set of steps will dependably crash any site in Chrome or Edge:

  1. Implement a component that implements useStaticQuery.
  2. Deploy to Gatsby Cloud.
  3. Open the site in a browser, wait for it to finish loading, then close the tab.
  4. Make a small edit to the static query, like switching the order of two fields.
  5. Deploy to Gatsby Cloud.
  6. Reopen the tab.

Expected Result

The page opens, possibly after a refresh.

Actual Result

The site crashes:

  1. The old chunk is reloaded from the disk cache (due to cache-control: public, max-age=31536000, immutable).
  2. The old chunk tries to reload its static query data JSON file from the server (due to cache-control: public, max-age=0, must-revalidate).
  3. The server responds with a 404.
  4. The component throws Uncaught Error: The result of this StaticQuery could not be fetched.
  5. The site crashes.

One possible solution would be similar to #33844, and would probably involve refreshing the page. An ideal version of this solution would protect against a refresh loop, too.

Environment

Gatsby Cloud. This doesn’t reproduce using gtsb.io or gatsby serve, since these don’t use Gatsby’s recommended caching headers. This issue only occurs when caching chunks.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 24 (19 by maintainers)

Most upvoted comments

Our traffic is very low right now due to post-holiday lull, but I merged #34225 a few days back and I haven’t seen a single error with this message since. I’m gonna call this resolved, and I’ll reopen if needed.

Thank you!

Interestingly, reopening a closed Edge/Chrome tab seems to be the easiest way to reproduce this. It appears to reliably push the browser cache into an unexpected state.

I want to stress that reopening an Edge/Chrome tab is not the only way to trigger this bug. It’s arrived in our logs from iOS Safari, and I’ve also experienced it when waking a sleeping tab on Android. But this unique (possibly buggy) Chromium cache behaviour is definitely making reproduction easier.

I managed to get a partial DevTools capture of a crash; unfortunately even with --auto-open-devtools-for-tabs, Edge doesn’t capture the very beginning, but I think I’ve got a good picture.

The most interesting detail (assuming DevTools isn’t lying!) is that the trigger appears to be a mismatch between the webpack-runtime-[hash].js Gatsby asks for vs. the one it actually gets.

You can see the mismatch here:

image

The browser downloads a page that asks for new code, but then for whatever reason it executes old code anyway.

Even if this is a browser bug - even if every StaticQuery bug ever captured turns out to be caused by various browser bugs - I still think Gatsby needs an error boundary in core that will catch this error and refresh the page. I’m going to try building an error boundary like that tomorrow, to at least try to make our own sites immune to this issue.

I’ll continue to share my findings.

2023 and still having this issue. In v5 now and I have never had the offline plugin; Is there anything in particular that I’m supposed to do? Pretty surprising with a very small e-commerce site, without very much plugin bloat, or weird configurations, that users in a production site just get a bit fat white screen until they hard refresh the page.

@aaronadamsCA We merged https://github.com/gatsbyjs/gatsby/pull/34225 (for now available with gatsby@next, but will be included in next @latest).

The way I could reproduce this problem somewhat reliably always revolved around using cached HTML from previous build (which would use previous js bundles), that would fetch new “data files” (from newer builds) and if queries would be edited between those builds it would cause error like this.

The problems I could see was not that network requests for static queries failed/404ed. It was that wrong static queries were fetched (not the ones that loaded (old) js bundle expected). We added a integrity check so runtime can know if loaded html+js is matching data files and if not - force single reload (note about refresh loop is completely valid, so we do setup flag with session storage to prevent refresh loop).

Since the error triggers a window reload, the component has a second chance to fetch its data; and since the initial fetch failure is transient, it works the second time, and the component renders normally. (In our case, it’s that “change pickup/delivery location” component you can see at the bottom left.)

The console only shows the error message because I’ve enabled “preserve log”. It’s from the prior pageload; the refreshed page has no errors.

This is literally the exact same solution as @KyleAMathews’s ChunkLoadError workaround - when in doubt, reload the page - which also assumes errors are transient.

Both solutions fall down when there’s an actual Webpack error; they induce a refresh loop, as opposed to a simple broken page. But given that’s an extreme edge case, I’m thinking that could be solved further down the road. This solves the actual “Gatsby is crashy after deploys” issue that drives so many bug reports (which, in fairness, is exactly what the error message suggests should be done).

Confirmed, the error boundary successfully catches this error in production.

Screenshot showing that I was able to reproduce a StaticQuery error, but the page loaded anyway, due to the automatic reload:

image

This error wasn’t delivered to Sentry either, since this new inner error boundary caught it first.

So, this is a good general pattern I think Gatsby could adopt. I’d maybe suggest adopting a common error class for these kinds of fatal, probably-transient rendering errors, too, so that the boundary could catch by name instead of by parsing the message.

We’re well into opinion territory now, of course, but:

  • If you catch a normal error in core, including an error thrown by a core dependency, I don’t expect to know about it, receive it, or be able to handle it.
  • I see this as the same thing; if the only way to “catch” a particular error in core is with an error boundary, then I think core should ship that error boundary, and I don’t expect to know it’s there.
  • Put another way, somewhere in your internal docs I think there should be a description of each alternate path sequence when each type of critical asset (chunk, page data, or static query data) is unavailable. 99.9% of the time this condition is transient, and a reload will either resolve it, or push a user to the browser’s “you are offline” page. Both are strongly preferable to the current situation.

I’m an intermediate React dev and in over my head with these errors, so take the above with a huge grain of salt, naturally. I’m just desperate to get these app crashes fixed ASAP, because users really don’t expect web apps to crash.

We’ll ship this error boundary tomorrow and watch to see if it has any effect.

@aaronadamsCA You can use gatsby-plugin-loadable-components-ssr to chunk at the component level.