fix(offline): IndexedDB fallback when API returns empty on /recipes & /season/[month]

These two pages only fell back to IndexedDB when navigator.onLine was
false. On mobile the device often reports online while the origin is
flaky (502 / slow cellular / cached shell with stale connectivity), so
the API call returned nothing and the pages rendered 0 recipes. Now
both also fall back to IndexedDB when the API attempt yields an empty
list, matching the pattern already used by [name]/+page.ts and
icon/[icon]/+page.ts.
This commit is contained in:
2026-05-08 16:01:59 +02:00
parent eb2ffac536
commit 0814803fc7
3 changed files with 61 additions and 29 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "homepage",
"version": "1.67.4",
"version": "1.67.5",
"private": true,
"type": "module",
"scripts": {
+32 -16
View File
@@ -16,26 +16,31 @@ function addFavoriteStatus<T extends { _id: unknown }>(
}));
}
async function loadFromIndexedDB() {
if (!await isOfflineDataAvailable()) return null;
const [allBrief, seasonRecipes] = await Promise.all([
getAllBriefRecipes(),
getBriefRecipesInSeasonOn(new Date())
]);
return {
all_brief: rand_array(allBrief),
season: rand_array(seasonRecipes),
heroIndex: Math.random(),
isOffline: true as const
};
}
export const load: PageLoad = async ({ params, fetch, parent }) => {
const parentData = await parent();
const apiBase = `/api/${params.recipeLang}`;
const useOfflineData =
browser && (isOffline() || parentData.isOffline) && canUseOfflineData();
const canUseOffline = browser && canUseOfflineData();
const knownOffline = browser && (isOffline() || parentData.isOffline);
if (useOfflineData) {
// Skip the network entirely when device is known offline.
if (canUseOffline && knownOffline) {
try {
if (await isOfflineDataAvailable()) {
const [allBrief, seasonRecipes] = await Promise.all([
getAllBriefRecipes(),
getBriefRecipesInSeasonOn(new Date())
]);
return {
all_brief: rand_array(allBrief),
season: rand_array(seasonRecipes),
heroIndex: Math.random(),
isOffline: true
};
}
const offline = await loadFromIndexedDB();
if (offline) return offline;
} catch (e) {
console.error('Failed to load offline data:', e);
}
@@ -54,7 +59,18 @@ export const load: PageLoad = async ({ params, fetch, parent }) => {
favorites = body.favorites ?? [];
}
} catch {
// Network unreachable — empty data; +page.svelte renders fallback layout.
// Network unreachable — IndexedDB fallback below picks up the slack.
}
// API failed or returned empty (502, slow cellular, server hiccup) — fall
// back to IndexedDB so the cached PWA shell stays useful.
if (canUseOffline && !all_brief.length) {
try {
const offline = await loadFromIndexedDB();
if (offline) return offline;
} catch (e) {
console.error('Failed to load offline data:', e);
}
}
const marked = addFavoriteStatus(all_brief, favorites);
@@ -15,23 +15,28 @@ function addFavoriteStatus<T extends { _id: unknown }>(
}));
}
async function loadFromIndexedDB(month: number) {
if (!await isOfflineDataAvailable()) return null;
const recipes = await getBriefRecipesOverlappingMonth(month);
return {
month,
season: rand_array(recipes),
isOffline: true as const
};
}
export const load: PageLoad = async ({ params, fetch, parent }) => {
const parentData = await parent();
const month = parseInt(params.month, 10);
const apiBase = `/api/${params.recipeLang}`;
const useOfflineData =
browser && (isOffline() || parentData.isOffline) && canUseOfflineData();
const canUseOffline = browser && canUseOfflineData();
const knownOffline = browser && (isOffline() || parentData.isOffline);
if (useOfflineData) {
// Skip the network entirely when device is known offline.
if (canUseOffline && knownOffline) {
try {
if (await isOfflineDataAvailable()) {
const recipes = await getBriefRecipesOverlappingMonth(month);
return {
month,
season: rand_array(recipes),
isOffline: true
};
}
const offline = await loadFromIndexedDB(month);
if (offline) return offline;
} catch (e) {
console.error('Failed to load offline season data:', e);
}
@@ -50,7 +55,18 @@ export const load: PageLoad = async ({ params, fetch, parent }) => {
favorites = body.favorites ?? [];
}
} catch {
// Empty arrays — page will render with no recipes
// Network unreachable — IndexedDB fallback below picks up the slack.
}
// API failed or returned empty (502, slow cellular, server hiccup) — fall
// back to IndexedDB so the cached PWA shell stays useful.
if (canUseOffline && !item_season.length) {
try {
const offline = await loadFromIndexedDB(month);
if (offline) return offline;
} catch (e) {
console.error('Failed to load offline season data:', e);
}
}
return {