diff --git a/public/sw.js b/public/sw.js
index a12f2a7..9a12695 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -1,7 +1,7 @@
// StripStream Service Worker - Version 2
// Architecture: SWR (Stale-While-Revalidate) for all resources
-const VERSION = "v2.4";
+const VERSION = "v2.5";
const STATIC_CACHE = `stripstream-static-${VERSION}`;
const PAGES_CACHE = `stripstream-pages-${VERSION}`; // Navigation + RSC (client-side navigation)
const API_CACHE = `stripstream-api-${VERSION}`;
@@ -129,10 +129,23 @@ async function cacheFirstStrategy(request, cacheName, options = {}) {
/**
* Stale-While-Revalidate: Serve from cache immediately, update in background
* Used for: API calls, images
+ * Respects Cache-Control: no-cache to force network-first (for refresh buttons)
*/
async function staleWhileRevalidateStrategy(request, cacheName, options = {}) {
const cache = await caches.open(cacheName);
- const cached = await cache.match(request);
+
+ // Check if client requested no-cache (refresh button, router.refresh(), etc.)
+ // 1. Check Cache-Control header
+ const cacheControl = request.headers.get("Cache-Control");
+ const noCacheHeader =
+ cacheControl && (cacheControl.includes("no-cache") || cacheControl.includes("no-store"));
+ // 2. Check request.cache mode (used by Next.js router.refresh())
+ const noCacheMode =
+ request.cache === "no-cache" || request.cache === "no-store" || request.cache === "reload";
+ const noCache = noCacheHeader || noCacheMode;
+
+ // If no-cache, skip cached response and go network-first
+ const cached = noCache ? null : await cache.match(request);
// Start network request (don't await)
const fetchPromise = fetch(request)
diff --git a/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx b/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx
index 76ec334..efef5c1 100644
--- a/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx
+++ b/src/app/libraries/[libraryId]/LibraryClientWrapper.tsx
@@ -17,21 +17,45 @@ interface LibraryClientWrapperProps {
preferences: UserPreferences;
}
-export function LibraryClientWrapper({ children }: LibraryClientWrapperProps) {
+export function LibraryClientWrapper({
+ children,
+ libraryId,
+ currentPage,
+ unreadOnly,
+ search,
+ pageSize,
+}: LibraryClientWrapperProps) {
const router = useRouter();
const [isRefreshing, setIsRefreshing] = useState(false);
const handleRefresh = async () => {
try {
setIsRefreshing(true);
- // Revalider la page côté serveur
+
+ // Fetch fresh data from network with cache bypass
+ const params = new URLSearchParams({
+ page: String(currentPage),
+ size: String(pageSize),
+ ...(unreadOnly && { unreadOnly: "true" }),
+ ...(search && { search }),
+ });
+
+ const response = await fetch(`/api/komga/libraries/${libraryId}/series?${params}`, {
+ cache: "no-store",
+ headers: { "Cache-Control": "no-cache" },
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to refresh library");
+ }
+
+ // Trigger Next.js revalidation to update the UI
router.refresh();
return { success: true };
} catch {
return { success: false, error: "Error refreshing library" };
} finally {
- // Petit délai pour laisser le temps au serveur de revalider
- setTimeout(() => setIsRefreshing(false), 500);
+ setIsRefreshing(false);
}
};
diff --git a/src/app/series/[seriesId]/SeriesClientWrapper.tsx b/src/app/series/[seriesId]/SeriesClientWrapper.tsx
index 332e6ff..1b9ec9d 100644
--- a/src/app/series/[seriesId]/SeriesClientWrapper.tsx
+++ b/src/app/series/[seriesId]/SeriesClientWrapper.tsx
@@ -18,6 +18,10 @@ interface SeriesClientWrapperProps {
export function SeriesClientWrapper({
children,
+ seriesId,
+ currentPage,
+ unreadOnly,
+ pageSize,
}: SeriesClientWrapperProps) {
const router = useRouter();
const [isRefreshing, setIsRefreshing] = useState(false);
@@ -25,14 +29,30 @@ export function SeriesClientWrapper({
const handleRefresh = async () => {
try {
setIsRefreshing(true);
- // Revalider la page côté serveur
+
+ // Fetch fresh data from network with cache bypass
+ const params = new URLSearchParams({
+ page: String(currentPage),
+ size: String(pageSize),
+ ...(unreadOnly && { unreadOnly: "true" }),
+ });
+
+ const response = await fetch(`/api/komga/series/${seriesId}/books?${params}`, {
+ cache: "no-store",
+ headers: { "Cache-Control": "no-cache" },
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to refresh series");
+ }
+
+ // Trigger Next.js revalidation to update the UI
router.refresh();
return { success: true };
} catch {
return { success: false, error: "Error refreshing series" };
} finally {
- // Petit délai pour laisser le temps au serveur de revalider
- setTimeout(() => setIsRefreshing(false), 500);
+ setIsRefreshing(false);
}
};
@@ -52,10 +72,7 @@ export function SeriesClientWrapper({
canRefresh={pullToRefresh.canRefresh}
isHiding={pullToRefresh.isHiding}
/>
-
- {children}
-
+ {children}
>
);
}
-
diff --git a/src/components/home/HomeClientWrapper.tsx b/src/components/home/HomeClientWrapper.tsx
index 9a5ff9e..63c94f2 100644
--- a/src/components/home/HomeClientWrapper.tsx
+++ b/src/components/home/HomeClientWrapper.tsx
@@ -20,15 +20,25 @@ export function HomeClientWrapper({ children }: HomeClientWrapperProps) {
const handleRefresh = async () => {
try {
setIsRefreshing(true);
- // Revalider la page côté serveur
+
+ // Fetch fresh data from network with cache bypass
+ const response = await fetch("/api/komga/home", {
+ cache: "no-store",
+ headers: { "Cache-Control": "no-cache" },
+ });
+
+ if (!response.ok) {
+ throw new Error("Failed to refresh home");
+ }
+
+ // Trigger Next.js revalidation to update the UI
router.refresh();
return { success: true };
} catch (error) {
logger.error({ err: error }, "Erreur lors du rafraîchissement:");
return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" };
} finally {
- // Petit délai pour laisser le temps au serveur de revalider
- setTimeout(() => setIsRefreshing(false), 500);
+ setIsRefreshing(false);
}
};