fix: show spinner instead of broken image icon while loading reader pages
All checks were successful
Build, Push & Deploy / deploy (push) Successful in 4m25s

Only render <img> when blob URL is available from prefetch, preventing
broken image icons from src={undefined} or failed direct URL fallbacks.
Reset error state when blob URL arrives to recover from transient failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-18 13:25:42 +01:00
parent 32757a8723
commit 894ea7114c
2 changed files with 23 additions and 22 deletions

View File

@@ -265,9 +265,7 @@ export function PhotoswipeReader({ book, pages, onClose, nextBook }: BookReaderP
isDoublePage={isDoublePage}
shouldShowDoublePage={(page) => shouldShowDoublePage(page, pages.length)}
imageBlobUrls={imageBlobUrls}
getPageUrl={getPageUrl}
isRTL={isRTL}
isPageLoading={isPageLoading}
/>
<NavigationBar

View File

@@ -7,9 +7,7 @@ interface PageDisplayProps {
isDoublePage: boolean;
shouldShowDoublePage: (page: number) => boolean;
imageBlobUrls: Record<number, string>;
getPageUrl: (pageNum: number) => string;
isRTL: boolean;
isPageLoading?: (pageNum: number) => boolean;
}
export function PageDisplay({
@@ -18,9 +16,7 @@ export function PageDisplay({
isDoublePage,
shouldShowDoublePage,
imageBlobUrls,
getPageUrl,
isRTL,
isPageLoading,
}: PageDisplayProps) {
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
@@ -53,6 +49,21 @@ export function PageDisplay({
setSecondPageHasError(false);
}, [currentPage, isDoublePage]);
// Reset error state when blob URL becomes available
useEffect(() => {
if (imageBlobUrls[currentPage] && hasError) {
setHasError(false);
setIsLoading(true);
}
}, [imageBlobUrls[currentPage], currentPage, hasError]);
useEffect(() => {
if (imageBlobUrls[currentPage + 1] && secondPageHasError) {
setSecondPageHasError(false);
setSecondPageLoading(true);
}
}, [imageBlobUrls[currentPage + 1], currentPage, secondPageHasError]);
return (
<div className="relative flex w-full flex-1 items-center justify-center overflow-hidden">
<div className="relative flex h-[calc(100vh-2.5rem)] w-full items-center justify-center px-2 sm:px-4">
@@ -99,15 +110,12 @@ export function PageDisplay({
</svg>
<span className="text-sm opacity-60">Image non disponible</span>
</div>
) : (
) : imageBlobUrls[currentPage] ? (
<>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
key={`page-${currentPage}-${imageBlobUrls[currentPage] || ""}`}
src={
imageBlobUrls[currentPage] ||
(isPageLoading && isPageLoading(currentPage) ? undefined : getPageUrl(currentPage))
}
key={`page-${currentPage}-${imageBlobUrls[currentPage]}`}
src={imageBlobUrls[currentPage]}
alt={`Page ${currentPage}`}
className={cn(
"max-h-full max-w-full cursor-pointer object-contain transition-opacity",
@@ -124,7 +132,7 @@ export function PageDisplay({
}}
/>
</>
)}
) : null}
</div>
{/* Page 2 (double page) */}
@@ -166,17 +174,12 @@ export function PageDisplay({
</svg>
<span className="text-sm opacity-60">Image non disponible</span>
</div>
) : (
) : imageBlobUrls[currentPage + 1] ? (
<>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
key={`page-${currentPage + 1}-${imageBlobUrls[currentPage + 1] || ""}`}
src={
imageBlobUrls[currentPage + 1] ||
(isPageLoading && isPageLoading(currentPage + 1)
? undefined
: getPageUrl(currentPage + 1))
}
key={`page-${currentPage + 1}-${imageBlobUrls[currentPage + 1]}`}
src={imageBlobUrls[currentPage + 1]}
alt={`Page ${currentPage + 1}`}
className={cn(
"max-h-full max-w-full cursor-pointer object-contain transition-opacity",
@@ -193,7 +196,7 @@ export function PageDisplay({
}}
/>
</>
)}
) : null}
</div>
)}
</div>