feat: books fetch on SSR in book reader
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 17s

This commit is contained in:
2026-02-28 08:50:57 +01:00
parent 2669fb9865
commit 2908172777
2 changed files with 51 additions and 11 deletions

View File

@@ -1,13 +1,36 @@
import { Suspense } from "react"; import { Suspense } from "react";
import { ClientBookPage } from "@/components/reader/ClientBookPage"; import { ClientBookPage } from "@/components/reader/ClientBookPage";
import { BookSkeleton } from "@/components/skeletons/BookSkeleton"; import { BookSkeleton } from "@/components/skeletons/BookSkeleton";
import { BookService } from "@/lib/services/book.service";
import { ERROR_CODES } from "@/constants/errorCodes";
import { AppError } from "@/utils/errors";
import { redirect } from "next/navigation";
export default async function BookPage({ params }: { params: Promise<{ bookId: string }> }) { export default async function BookPage({ params }: { params: Promise<{ bookId: string }> }) {
const { bookId } = await params; const { bookId } = await params;
return ( try {
<Suspense fallback={<BookSkeleton />}> // SSR: Fetch directly on server instead of client-side XHR
<ClientBookPage bookId={bookId} /> const data = await BookService.getBook(bookId);
</Suspense> const nextBook = await BookService.getNextBook(bookId, data.book.seriesId);
);
return (
<Suspense fallback={<BookSkeleton />}>
<ClientBookPage bookId={bookId} initialData={{ ...data, nextBook }} />
</Suspense>
);
} catch (error) {
// If config is missing, redirect to settings
if (error instanceof AppError && error.code === ERROR_CODES.KOMGA.MISSING_CONFIG) {
redirect("/settings");
}
// Pass error to client component
const errorCode = error instanceof AppError ? error.code : ERROR_CODES.BOOK.NOT_FOUND;
return (
<Suspense fallback={<BookSkeleton />}>
<ClientBookPage bookId={bookId} initialError={errorCode} />
</Suspense>
);
}
} }

View File

@@ -10,9 +10,15 @@ import logger from "@/lib/logger";
interface ClientBookPageProps { interface ClientBookPageProps {
bookId: string; bookId: string;
initialData?: {
book: KomgaBook;
pages: number[];
nextBook: KomgaBook | null;
};
initialError?: string;
} }
export function ClientBookPage({ bookId }: ClientBookPageProps) { export function ClientBookPage({ bookId, initialData, initialError }: ClientBookPageProps) {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [data, setData] = useState<{ const [data, setData] = useState<{
@@ -21,6 +27,22 @@ export function ClientBookPage({ bookId }: ClientBookPageProps) {
nextBook: KomgaBook | null; nextBook: KomgaBook | null;
} | null>(null); } | null>(null);
// Use SSR data if available
useEffect(() => {
if (initialData) {
setData(initialData);
setLoading(false);
return;
}
if (initialError) {
setError(initialError);
setLoading(false);
return;
}
fetchBookData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bookId, initialData, initialError]);
const fetchBookData = async () => { const fetchBookData = async () => {
try { try {
setLoading(true); setLoading(true);
@@ -43,11 +65,6 @@ export function ClientBookPage({ bookId }: ClientBookPageProps) {
} }
}; };
useEffect(() => {
fetchBookData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bookId]);
const handleRetry = () => { const handleRetry = () => {
fetchBookData(); fetchBookData();
}; };