diff --git a/frontend/src/components/VErrorSection/VErrorSection.vue b/frontend/src/components/VErrorSection/VErrorSection.vue
index 42363862e6a..51edfcf524e 100644
--- a/frontend/src/components/VErrorSection/VErrorSection.vue
+++ b/frontend/src/components/VErrorSection/VErrorSection.vue
@@ -18,10 +18,9 @@
diff --git a/frontend/src/constants/errors.ts b/frontend/src/constants/errors.ts
index 9b48069ac10..7cf4057954e 100644
--- a/frontend/src/constants/errors.ts
+++ b/frontend/src/constants/errors.ts
@@ -5,7 +5,45 @@
export const NO_RESULT = "NO_RESULT"
export const SERVER_TIMEOUT = "SERVER_TIMEOUT"
+export const ECONNABORTED = "ECONNABORTED"
-export const errorCodes = [NO_RESULT, SERVER_TIMEOUT] as const
+export const ERR_UNKNOWN = "ERR_UNKNOWN"
+
+export const customErrorCodes = [
+ NO_RESULT,
+ SERVER_TIMEOUT,
+ ECONNABORTED,
+ ERR_UNKNOWN,
+] as const
+
+/**
+ * The error codes Axios uses.
+ * @see https://github.com/axios/axios/blob/9588fcdec8aca45c3ba2f7968988a5d03f23168c/lib/core/AxiosError.js#L57C2-L71
+ */
+const axiosErrorCodes = [
+ "ERR_BAD_OPTION_VALUE",
+ "ERR_BAD_OPTION",
+ "ECONNABORTED",
+ "ETIMEDOUT",
+ "ERR_NETWORK",
+ "ERR_FR_TOO_MANY_REDIRECTS",
+ "ERR_DEPRECATED",
+ "ERR_BAD_RESPONSE",
+ "ERR_BAD_REQUEST",
+ "ERR_CANCELED",
+ "ERR_NOT_SUPPORT",
+ "ERR_INVALID_URL",
+] as const
+
+export const errorCodes = [...customErrorCodes, ...axiosErrorCodes] as const
+
+export const clientSideErrorCodes: readonly ErrorCode[] = [
+ ECONNABORTED,
+ SERVER_TIMEOUT,
+ NO_RESULT,
+ ERR_UNKNOWN,
+ "ERR_NETWORK",
+ "ETIMEDOUT",
+] as const
export type ErrorCode = (typeof errorCodes)[number]
diff --git a/frontend/src/middleware/search.ts b/frontend/src/middleware/search.ts
index f0f2cd78c87..93a19c545b6 100644
--- a/frontend/src/middleware/search.ts
+++ b/frontend/src/middleware/search.ts
@@ -1,6 +1,7 @@
import { useSearchStore } from "~/stores/search"
import { useMediaStore } from "~/stores/media"
-import { NO_RESULT } from "~/constants/errors"
+
+import { handledClientSide } from "~/utils/errors"
import type { Middleware } from "@nuxt/types"
@@ -44,13 +45,7 @@ export const searchMiddleware: Middleware = async ({
const results = await mediaStore.fetchMedia()
const fetchingError = mediaStore.fetchState.fetchingError
- // NO_RESULTS and timeout are handled client-side, for other errors show server error page
- if (
- !results &&
- fetchingError &&
- !fetchingError?.message?.includes(NO_RESULT) &&
- !fetchingError?.message?.includes("timeout")
- ) {
+ if (!results && fetchingError && !handledClientSide(fetchingError)) {
nuxtError(fetchingError)
}
}
diff --git a/frontend/src/middleware/single-result.ts b/frontend/src/middleware/single-result.ts
index ca9f2f81b65..83babf0218e 100644
--- a/frontend/src/middleware/single-result.ts
+++ b/frontend/src/middleware/single-result.ts
@@ -1,6 +1,7 @@
import { useSingleResultStore } from "~/stores/media/single-result"
import { useSearchStore } from "~/stores/search"
import { useRelatedMediaStore } from "~/stores/media/related-media"
+import { isRetriable } from "~/utils/errors"
import { AUDIO, IMAGE } from "~/constants/media"
@@ -17,11 +18,15 @@ export const singleResultMiddleware: Middleware = async ({
if (process.server) {
const media = await singleResultStore.fetch(mediaType, route.params.id)
- await useRelatedMediaStore($pinia).fetchMedia(mediaType, route.params.id)
if (!media) {
- error(singleResultStore.fetchState.fetchingError ?? {})
+ const fetchingError = singleResultStore.fetchState.fetchingError
+
+ if (fetchingError && !isRetriable(fetchingError)) {
+ error(fetchingError ?? {})
+ }
}
+ await useRelatedMediaStore($pinia).fetchMedia(mediaType, route.params.id)
} else {
// Client-side rendering
singleResultStore.setMediaById(mediaType, route.params.id)
diff --git a/frontend/src/pages/audio/_id/index.vue b/frontend/src/pages/audio/_id/index.vue
index 28afd835bd7..8091a2b9dfc 100644
--- a/frontend/src/pages/audio/_id/index.vue
+++ b/frontend/src/pages/audio/_id/index.vue
@@ -1,6 +1,11 @@
-
+
+
@@ -33,7 +38,7 @@