Crosstalk-Solutions/project-nomad · error

Failed to fetch remote ZIM files.

Error message

Failed to fetch remote ZIM files.

What it means

Thrown inside a React Query infinite-query pageFetchParam callback when api.listRemoteZimFiles({start, count, query}) resolves falsy. The Kiwix remote catalog request failed or returned an empty/unparseable body while paginating the remote ZIM explorer.

Source

Thrown at admin/inertia/pages/settings/zim/remote-explorer.tsx:153

  } = useQuery<BrowseResult>({
    queryKey: ['browse-library', browseUrl],
    queryFn: () => api.browseLibrary(browseUrl!) as Promise<BrowseResult>,
    enabled: !!browseUrl && selectedSource !== 'default',
    refetchOnWindowFocus: false,
    retry: false,
  })

  const { data, fetchNextPage, isFetching, isLoading } =
    useInfiniteQuery<ListRemoteZimFilesResponse>({
      queryKey: ['remote-zim-files', query],
      queryFn: async ({ pageParam = 0 }) => {
        // pageParam is an opaque Kiwix offset returned by the backend as `next_start`.
        // The backend accumulates across multiple upstream pages when needed (#731), so the
        // frontend can't derive the next offset from a 12-item page assumption.
        const start = typeof pageParam === 'number' ? pageParam : 0
        const res = await api.listRemoteZimFiles({ start, count: 12, query: query || undefined })
        if (!res) {
          throw new Error('Failed to fetch remote ZIM files.')
        }
        return res.data
      },
      initialPageParam: 0,
      getNextPageParam: (lastPage) => (lastPage.has_more ? lastPage.next_start : undefined),
      refetchOnWindowFocus: false,
      placeholderData: keepPreviousData,
      enabled: selectedSource === 'default',
    })

  const flatData = useMemo(() => {
    const mapped = data?.pages.flatMap((page) => page.items) || []
    const localNames = new Set(localFiles?.map((f) => f.name) ?? [])
    return mapped.filter((item) => {
      const isDownloading = downloads?.some((download) => {
        const filename = item.download_url.split('/').pop()
        return filename && download.filepath.endsWith(filename)
      })

View on GitHub (pinned to 0bd1c6f4f9)

Solutions

  1. Test the backend ZIM catalog proxy endpoint directly with the same start/count to see the raw upstream status
  2. Verify internet connectivity/egress rules from the appliance to the Kiwix library
  3. Add rate-limit/backoff on the infinite scroll and rely on React Query retry rather than throwing on first falsy response
  4. If upstream schema changed, update the backend mapper to keep returning {data, has_more, next_start}

Example fix

// before
const res = await api.listRemoteZimFiles({ start, count: 12, query: query || undefined })
if (!res) {
  throw new Error('Failed to fetch remote ZIM files.')
}
return res.data
// after
const res = await api.listRemoteZimFiles({ start, count: 12, query: query || undefined })
if (!res) {
  throw new Error(`Failed to fetch remote ZIM files (offset ${start}).`)
}
return res.data
Defensive patterns

Strategy: retry

Validate before calling

if (!(await navigator.onLine)) throw new Error('Offline — remote ZIM catalog unavailable')

Type guard

const isZimPage = (r: unknown): r is { data: ZimFile[]; has_more: boolean; next_start?: number } =>
  Array.isArray((r as any)?.data)

Try / catch

pageFn: async ({ pageParam }) => {
  try {
    const res = await api.listRemoteZimFiles({ start: pageParam ?? 0, count: 12, query: query || undefined })
    if (!res) throw new Error('Failed to fetch remote ZIM files.')
    return res.data
  } catch (e) {
    queryClient.invalidateQueries({ queryKey: ['remote-zim'] })
    throw e
  }
}

Prevention

When it happens

Trigger: Fetching page 2+ of the Kiwix library catalog with an opaque next_start offset when the upstream Kiwix server is unreachable, rate-limits the appliance, or the backend proxy returns an error body without throwing.

Common situations: Appliance offline or with restricted egress to library.kiwix.org, upstream Kiwix API schema change breaking the backend mapper, rate limiting after rapid infinite-scroll paging.

Related errors


AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27). Data as JSON: /api/errors/ac8eaa23ca122bd2. Report an issue: GitHub.