Lazy page metadata
This reader is never handed a page list. It is given the number of pages it holds and a resolvePage function, and it asks for the metadata of a page only as the reader comes near it. The imaginary endpoint behind this demo waits more than a second before answering, so the pending placeholder a page shows while its metadata is on its way stays on screen long enough to see.
Only the pages within pageResolveOverscan of the current one are asked for, four on either side by default, which is why the request count starts at five rather than at the length of the document. A request for a page the reader leaves far behind is aborted through the AbortSignal it was given, and its metadata is forgotten once the page is further away than both that window and the pages the viewport can still render, so a page returned to much later is resolved again and a signed URL that has expired in the meantime is reissued.
Page images follow a window of their own. imagePreloadSpreads is 1 here, so the viewer fetches and decodes one spread beyond the ones it can render on either side of the reader. Those loads are queued behind the spread on screen and dropped when the reader moves away from them, so the count of decoded pages runs ahead of the three spreads the viewport holds without the current one waiting for them.
The document also grows as it is read. It starts as the first chapter of seven pages, and onEndReached appends the next chapter once the reader comes within two pages of the end, which the page count and the reading progress follow immediately.
Source code
1import * as ComicViewer from "@publira/comic-viewer";2import { useCallback, useState } from "react";34const CHAPTER_LENGTH = 7;56export const Reader = () => {7 const [pageCount, setPageCount] = useState(CHAPTER_LENGTH);89 const resolvePage = useCallback(async (index, { signal }) => {10 // The endpoint signs the URL it hands back, and the viewer fetches the11 // page right after asking, while the signature is still valid.12 const response = await fetch(`/api/pages/${index}`, { signal });1314 return response.json();15 }, []);1617 const loadNextChapter = useCallback(() => {18 setPageCount((count) => count + CHAPTER_LENGTH);19 }, []);2021 return (22 <ComicViewer.Root23 imagePreloadSpreads={1}24 onEndReached={loadNextChapter}25 pageCount={pageCount}26 resolvePage={resolvePage}27 >28 <ComicViewer.Viewport />29 <ComicViewer.Toolbar />30 <ComicViewer.PageNavigation />31 </ComicViewer.Root>32 );33};