feat: update service worker to version 2.5 and enhance caching strategies for network requests, including cache bypass for refresh actions in LibraryClientWrapper, SeriesClientWrapper, and HomeClientWrapper components
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 5m3s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 5m3s
This commit is contained in:
17
public/sw.js
17
public/sw.js
@@ -1,7 +1,7 @@
|
|||||||
// StripStream Service Worker - Version 2
|
// StripStream Service Worker - Version 2
|
||||||
// Architecture: SWR (Stale-While-Revalidate) for all resources
|
// Architecture: SWR (Stale-While-Revalidate) for all resources
|
||||||
|
|
||||||
const VERSION = "v2.4";
|
const VERSION = "v2.5";
|
||||||
const STATIC_CACHE = `stripstream-static-${VERSION}`;
|
const STATIC_CACHE = `stripstream-static-${VERSION}`;
|
||||||
const PAGES_CACHE = `stripstream-pages-${VERSION}`; // Navigation + RSC (client-side navigation)
|
const PAGES_CACHE = `stripstream-pages-${VERSION}`; // Navigation + RSC (client-side navigation)
|
||||||
const API_CACHE = `stripstream-api-${VERSION}`;
|
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
|
* Stale-While-Revalidate: Serve from cache immediately, update in background
|
||||||
* Used for: API calls, images
|
* Used for: API calls, images
|
||||||
|
* Respects Cache-Control: no-cache to force network-first (for refresh buttons)
|
||||||
*/
|
*/
|
||||||
async function staleWhileRevalidateStrategy(request, cacheName, options = {}) {
|
async function staleWhileRevalidateStrategy(request, cacheName, options = {}) {
|
||||||
const cache = await caches.open(cacheName);
|
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)
|
// Start network request (don't await)
|
||||||
const fetchPromise = fetch(request)
|
const fetchPromise = fetch(request)
|
||||||
|
|||||||
@@ -17,21 +17,45 @@ interface LibraryClientWrapperProps {
|
|||||||
preferences: UserPreferences;
|
preferences: UserPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LibraryClientWrapper({ children }: LibraryClientWrapperProps) {
|
export function LibraryClientWrapper({
|
||||||
|
children,
|
||||||
|
libraryId,
|
||||||
|
currentPage,
|
||||||
|
unreadOnly,
|
||||||
|
search,
|
||||||
|
pageSize,
|
||||||
|
}: LibraryClientWrapperProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
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();
|
router.refresh();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch {
|
} catch {
|
||||||
return { success: false, error: "Error refreshing library" };
|
return { success: false, error: "Error refreshing library" };
|
||||||
} finally {
|
} finally {
|
||||||
// Petit délai pour laisser le temps au serveur de revalider
|
setIsRefreshing(false);
|
||||||
setTimeout(() => setIsRefreshing(false), 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ interface SeriesClientWrapperProps {
|
|||||||
|
|
||||||
export function SeriesClientWrapper({
|
export function SeriesClientWrapper({
|
||||||
children,
|
children,
|
||||||
|
seriesId,
|
||||||
|
currentPage,
|
||||||
|
unreadOnly,
|
||||||
|
pageSize,
|
||||||
}: SeriesClientWrapperProps) {
|
}: SeriesClientWrapperProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
@@ -25,14 +29,30 @@ export function SeriesClientWrapper({
|
|||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
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();
|
router.refresh();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch {
|
} catch {
|
||||||
return { success: false, error: "Error refreshing series" };
|
return { success: false, error: "Error refreshing series" };
|
||||||
} finally {
|
} finally {
|
||||||
// Petit délai pour laisser le temps au serveur de revalider
|
setIsRefreshing(false);
|
||||||
setTimeout(() => setIsRefreshing(false), 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,10 +72,7 @@ export function SeriesClientWrapper({
|
|||||||
canRefresh={pullToRefresh.canRefresh}
|
canRefresh={pullToRefresh.canRefresh}
|
||||||
isHiding={pullToRefresh.isHiding}
|
isHiding={pullToRefresh.isHiding}
|
||||||
/>
|
/>
|
||||||
<RefreshProvider refreshSeries={handleRefresh}>
|
<RefreshProvider refreshSeries={handleRefresh}>{children}</RefreshProvider>
|
||||||
{children}
|
|
||||||
</RefreshProvider>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,25 @@ export function HomeClientWrapper({ children }: HomeClientWrapperProps) {
|
|||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
try {
|
try {
|
||||||
setIsRefreshing(true);
|
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();
|
router.refresh();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error({ err: error }, "Erreur lors du rafraîchissement:");
|
logger.error({ err: error }, "Erreur lors du rafraîchissement:");
|
||||||
return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" };
|
return { success: false, error: "Erreur lors du rafraîchissement de la page d'accueil" };
|
||||||
} finally {
|
} finally {
|
||||||
// Petit délai pour laisser le temps au serveur de revalider
|
setIsRefreshing(false);
|
||||||
setTimeout(() => setIsRefreshing(false), 500);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user