diff --git a/src/lib/network-error.test.ts b/src/lib/network-error.test.ts new file mode 100644 index 00000000..af28d7b3 --- /dev/null +++ b/src/lib/network-error.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vitest"; + +import { isNetworkError } from "#/lib/network-error"; + +describe("isNetworkError", () => { + it("matches browser fetch network failures", () => { + expect(isNetworkError(new TypeError("Failed to fetch"))).toBe(true); + expect(isNetworkError(new TypeError("NetworkError when attempting to fetch resource."))).toBe( + true, + ); + expect(isNetworkError(new TypeError("Load failed"))).toBe(true); + }); + + it("does not match other errors", () => { + expect(isNetworkError(new Error("Failed to fetch"))).toBe(false); + expect(isNetworkError(new TypeError("Cannot read properties of undefined"))).toBe(false); + expect(isNetworkError("Failed to fetch")).toBe(false); + }); +}); diff --git a/src/lib/network-error.ts b/src/lib/network-error.ts new file mode 100644 index 00000000..f4d00012 --- /dev/null +++ b/src/lib/network-error.ts @@ -0,0 +1,11 @@ +// Browsers reject fetch with a bare TypeError when a request gets no response, +// including requests cancelled by a page unload. Messages vary by engine. +const networkErrorMessages = new Set([ + "Failed to fetch", + "NetworkError when attempting to fetch resource.", + "Load failed", +]); + +export function isNetworkError(error: unknown) { + return error instanceof TypeError && networkErrorMessages.has(error.message); +} diff --git a/src/router.tsx b/src/router.tsx index ec256561..80b6cd0b 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -4,6 +4,7 @@ import AppErrorScreen from "./components/AppErrorScreen"; import AppNotFoundScreen from "./components/AppNotFoundScreen"; import { capturePostHogClientException } from "./integrations/posthog/provider"; import { getContext } from "./integrations/tanstack-query/root-provider"; +import { isNetworkError } from "./lib/network-error"; import { routeTree } from "./routeTree.gen"; export function getRouter() { @@ -16,6 +17,10 @@ export function getRouter() { defaultPreloadStaleTime: 0, defaultErrorComponent: AppErrorScreen, defaultOnCatch: (error, errorInfo) => { + if (isNetworkError(error)) { + return; + } + capturePostHogClientException(error, { component_stack: errorInfo.componentStack, error_boundary: "TanStackRouter",