feat: implement caching strategy for API responses and adjust loading timeout in CoverClient for improved performance

This commit is contained in:
Julien Froidefond
2025-10-17 23:20:42 +02:00
parent a22e77c4eb
commit ae4b766085
8 changed files with 58 additions and 20 deletions

View File

@@ -3,12 +3,16 @@ import { HomeService } from "@/lib/services/home.service";
import { ERROR_CODES } from "@/constants/errorCodes"; import { ERROR_CODES } from "@/constants/errorCodes";
import { AppError } from "@/utils/errors"; import { AppError } from "@/utils/errors";
import { getErrorMessage } from "@/utils/errors"; import { getErrorMessage } from "@/utils/errors";
export const dynamic = "force-dynamic"; export const revalidate = 60;
export async function GET() { export async function GET() {
try { try {
const data = await HomeService.getHomeData(); const data = await HomeService.getHomeData();
return NextResponse.json(data); return NextResponse.json(data, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120'
}
});
} catch (error) { } catch (error) {
console.error("API Home - Erreur:", error); console.error("API Home - Erreur:", error);
if (error instanceof AppError) { if (error instanceof AppError) {

View File

@@ -4,7 +4,7 @@ import { ERROR_CODES } from "@/constants/errorCodes";
import { AppError } from "@/utils/errors"; import { AppError } from "@/utils/errors";
import { getErrorMessage } from "@/utils/errors"; import { getErrorMessage } from "@/utils/errors";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
export const dynamic = "force-dynamic"; export const revalidate = 60;
const DEFAULT_PAGE_SIZE = 20; const DEFAULT_PAGE_SIZE = 20;
@@ -26,7 +26,14 @@ export async function GET(
LibraryService.getLibrary(libraryId) LibraryService.getLibrary(libraryId)
]); ]);
return NextResponse.json({ series, library }); return NextResponse.json(
{ series, library },
{
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120'
}
}
);
} catch (error) { } catch (error) {
console.error("API Library Series - Erreur:", error); console.error("API Library Series - Erreur:", error);
if (error instanceof AppError) { if (error instanceof AppError) {

View File

@@ -4,7 +4,7 @@ import { ERROR_CODES } from "@/constants/errorCodes";
import { AppError } from "@/utils/errors"; import { AppError } from "@/utils/errors";
import { getErrorMessage } from "@/utils/errors"; import { getErrorMessage } from "@/utils/errors";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
export const dynamic = "force-dynamic"; export const revalidate = 60;
const DEFAULT_PAGE_SIZE = 20; const DEFAULT_PAGE_SIZE = 20;
@@ -25,7 +25,14 @@ export async function GET(
SeriesService.getSeries(seriesId) SeriesService.getSeries(seriesId)
]); ]);
return NextResponse.json({ books, series }); return NextResponse.json(
{ books, series },
{
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120'
}
}
);
} catch (error) { } catch (error) {
console.error("API Series Books - Erreur:", error); console.error("API Series Books - Erreur:", error);
if (error instanceof AppError) { if (error instanceof AppError) {

View File

@@ -5,7 +5,7 @@ import { AppError } from "@/utils/errors";
import type { KomgaSeries } from "@/types/komga"; import type { KomgaSeries } from "@/types/komga";
import { getErrorMessage } from "@/utils/errors"; import { getErrorMessage } from "@/utils/errors";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
export const dynamic = "force-dynamic"; export const revalidate = 60;
export async function GET( export async function GET(
request: NextRequest, request: NextRequest,
@@ -15,7 +15,11 @@ export async function GET(
const seriesId: string = (await params).seriesId; const seriesId: string = (await params).seriesId;
const series: KomgaSeries = await SeriesService.getSeries(seriesId); const series: KomgaSeries = await SeriesService.getSeries(seriesId);
return NextResponse.json(series); return NextResponse.json(series, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120'
}
});
} catch (error) { } catch (error) {
console.error("API Series - Erreur:", error); console.error("API Series - Erreur:", error);
if (error instanceof AppError) { if (error instanceof AppError) {

View File

@@ -55,7 +55,9 @@ export function ClientLibraryPage({
params.append("search", search); params.append("search", search);
} }
const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`); const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`, {
cache: 'default' // Utilise le cache HTTP du navigateur
});
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
@@ -98,7 +100,9 @@ export function ClientLibraryPage({
params.append("search", search); params.append("search", search);
} }
const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`); const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`, {
cache: 'reload' // Force un nouveau fetch après invalidation
});
if (!response.ok) { if (!response.ok) {
throw new Error("Error refreshing library"); throw new Error("Error refreshing library");
@@ -130,7 +134,9 @@ export function ClientLibraryPage({
params.append("search", search); params.append("search", search);
} }
const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`); const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`, {
cache: 'reload' // Force un nouveau fetch lors du retry
});
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();

View File

@@ -46,7 +46,9 @@ export function ClientSeriesPage({
unread: String(unreadOnly), unread: String(unreadOnly),
}); });
const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`); const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`, {
cache: 'default' // Utilise le cache HTTP du navigateur
});
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
@@ -85,7 +87,9 @@ export function ClientSeriesPage({
unread: String(unreadOnly), unread: String(unreadOnly),
}); });
const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`); const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`, {
cache: 'reload' // Force un nouveau fetch après invalidation
});
if (!response.ok) { if (!response.ok) {
throw new Error("Erreur lors du rafraîchissement de la série"); throw new Error("Erreur lors du rafraîchissement de la série");
@@ -113,7 +117,9 @@ export function ClientSeriesPage({
unread: String(unreadOnly), unread: String(unreadOnly),
}); });
const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`); const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`, {
cache: 'reload' // Force un nouveau fetch lors du retry
});
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();

View File

@@ -19,7 +19,9 @@ export function ClientHomePage() {
setError(null); setError(null);
try { try {
const response = await fetch("/api/komga/home"); const response = await fetch("/api/komga/home", {
cache: 'default' // Utilise le cache HTTP du navigateur
});
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
@@ -61,7 +63,9 @@ export function ClientHomePage() {
} }
// Récupérer les nouvelles données // Récupérer les nouvelles données
const response = await fetch("/api/komga/home"); const response = await fetch("/api/komga/home", {
cache: 'reload' // Force un nouveau fetch après invalidation
});
if (!response.ok) { if (!response.ok) {
throw new Error("Erreur lors du rafraîchissement de la page d'accueil"); throw new Error("Erreur lors du rafraîchissement de la page d'accueil");

View File

@@ -22,14 +22,14 @@ export const CoverClient = ({
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const timeoutRef = useRef<NodeJS.Timeout | null>(null); const timeoutRef = useRef<NodeJS.Timeout | null>(null);
// Timeout de sécurité : si l'image ne se charge pas en 10 secondes, on arrête le loading // Timeout de sécurité : si l'image ne se charge pas en 30 secondes, on arrête le loading
useEffect(() => { useEffect(() => {
timeoutRef.current = setTimeout(() => { timeoutRef.current = setTimeout(() => {
if (isLoading) { if (isLoading) {
console.warn("Image loading timeout for:", imageUrl);
setIsLoading(false); setIsLoading(false);
setImageError(true);
} }
}, 10000); }, 30000);
return () => { return () => {
if (timeoutRef.current) { if (timeoutRef.current) {
@@ -49,8 +49,8 @@ export const CoverClient = ({
if (timeoutRef.current) { if (timeoutRef.current) {
clearTimeout(timeoutRef.current); clearTimeout(timeoutRef.current);
} }
console.error("Image loading error for:", imageUrl);
setImageError(true); setImageError(true);
setIsLoading(false);
}; };
if (imageError) { if (imageError) {