Sync URL and prev/next nav with the chapter being viewed

Once continuous scroll crosses a chapter boundary, the URL was stuck at
the originally-opened chapter so browser back / reload would jump the
user back there. Double-tap left/right also walked off the wrong chapter
since prevChapter/nextChapter were frozen at mount time.

- replaceState the URL as currentChapterNum changes (no server refetch).
- Derive prevChapter/nextChapter dynamically via useMemo on
  currentChapterNum, dropping the now-redundant server-computed props.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-04-12 14:40:13 +08:00
parent cad59143a3
commit 1c74348fae
2 changed files with 19 additions and 16 deletions

View File

@ -50,16 +50,6 @@ export default async function ChapterReaderPage({ params }: Props) {
const currentChapter = manga.chapters.find((c) => c.number === chapterNum); const currentChapter = manga.chapters.find((c) => c.number === chapterNum);
if (!currentChapter) notFound(); if (!currentChapter) notFound();
const chapterIndex = manga.chapters.findIndex(
(c) => c.number === chapterNum
);
const prevChapter =
chapterIndex > 0 ? manga.chapters[chapterIndex - 1].number : null;
const nextChapter =
chapterIndex < manga.chapters.length - 1
? manga.chapters[chapterIndex + 1].number
: null;
const allChapters = manga.chapters.map((c) => ({ const allChapters = manga.chapters.map((c) => ({
id: c.id, id: c.id,
number: c.number, number: c.number,
@ -72,8 +62,6 @@ export default async function ChapterReaderPage({ params }: Props) {
mangaSlug={manga.slug} mangaSlug={manga.slug}
mangaTitle={manga.title} mangaTitle={manga.title}
startChapterNumber={currentChapter.number} startChapterNumber={currentChapter.number}
prevChapter={prevChapter}
nextChapter={nextChapter}
chapters={allChapters} chapters={allChapters}
initialChapterMeta={initialChapterMeta} initialChapterMeta={initialChapterMeta}
/> />

View File

@ -29,8 +29,6 @@ type PageReaderProps = {
mangaSlug: string; mangaSlug: string;
mangaTitle: string; mangaTitle: string;
startChapterNumber: number; startChapterNumber: number;
prevChapter: number | null;
nextChapter: number | null;
chapters: ChapterMeta[]; chapters: ChapterMeta[];
initialChapterMeta: PageMeta[]; initialChapterMeta: PageMeta[];
}; };
@ -51,8 +49,6 @@ export function PageReader({
mangaSlug, mangaSlug,
mangaTitle, mangaTitle,
startChapterNumber, startChapterNumber,
prevChapter,
nextChapter,
chapters, chapters,
initialChapterMeta, initialChapterMeta,
}: PageReaderProps) { }: PageReaderProps) {
@ -272,6 +268,25 @@ export function PageReader({
}); });
}, [mangaSlug, currentChapterNum, currentPageNum]); }, [mangaSlug, currentChapterNum, currentPageNum]);
// Keep URL in sync with the chapter currently in the viewport so browser
// back / reload returns to the latest chapter, not the one first opened.
useEffect(() => {
const url = `/manga/${mangaSlug}/${currentChapterNum}`;
if (window.location.pathname === url) return;
window.history.replaceState(window.history.state, "", url);
}, [mangaSlug, currentChapterNum]);
const { prevChapter, nextChapter } = useMemo(() => {
const idx = chapters.findIndex((c) => c.number === currentChapterNum);
return {
prevChapter: idx > 0 ? chapters[idx - 1].number : null,
nextChapter:
idx >= 0 && idx < chapters.length - 1
? chapters[idx + 1].number
: null,
};
}, [chapters, currentChapterNum]);
const router = useRouter(); const router = useRouter();
const touchMovedRef = useRef(false); const touchMovedRef = useRef(false);
const singleTapTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); const singleTapTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);