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:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homepage",
|
"name": "homepage",
|
||||||
"version": "1.67.4",
|
"version": "1.67.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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 }) => {
|
export const load: PageLoad = async ({ params, fetch, parent }) => {
|
||||||
const parentData = await parent();
|
const parentData = await parent();
|
||||||
const apiBase = `/api/${params.recipeLang}`;
|
const apiBase = `/api/${params.recipeLang}`;
|
||||||
const useOfflineData =
|
const canUseOffline = browser && canUseOfflineData();
|
||||||
browser && (isOffline() || parentData.isOffline) && canUseOfflineData();
|
const knownOffline = browser && (isOffline() || parentData.isOffline);
|
||||||
|
|
||||||
if (useOfflineData) {
|
// Skip the network entirely when device is known offline.
|
||||||
|
if (canUseOffline && knownOffline) {
|
||||||
try {
|
try {
|
||||||
if (await isOfflineDataAvailable()) {
|
const offline = await loadFromIndexedDB();
|
||||||
const [allBrief, seasonRecipes] = await Promise.all([
|
if (offline) return offline;
|
||||||
getAllBriefRecipes(),
|
|
||||||
getBriefRecipesInSeasonOn(new Date())
|
|
||||||
]);
|
|
||||||
return {
|
|
||||||
all_brief: rand_array(allBrief),
|
|
||||||
season: rand_array(seasonRecipes),
|
|
||||||
heroIndex: Math.random(),
|
|
||||||
isOffline: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load offline data:', e);
|
console.error('Failed to load offline data:', e);
|
||||||
}
|
}
|
||||||
@@ -54,7 +59,18 @@ export const load: PageLoad = async ({ params, fetch, parent }) => {
|
|||||||
favorites = body.favorites ?? [];
|
favorites = body.favorites ?? [];
|
||||||
}
|
}
|
||||||
} catch {
|
} 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);
|
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 }) => {
|
export const load: PageLoad = async ({ params, fetch, parent }) => {
|
||||||
const parentData = await parent();
|
const parentData = await parent();
|
||||||
const month = parseInt(params.month, 10);
|
const month = parseInt(params.month, 10);
|
||||||
const apiBase = `/api/${params.recipeLang}`;
|
const apiBase = `/api/${params.recipeLang}`;
|
||||||
const useOfflineData =
|
const canUseOffline = browser && canUseOfflineData();
|
||||||
browser && (isOffline() || parentData.isOffline) && canUseOfflineData();
|
const knownOffline = browser && (isOffline() || parentData.isOffline);
|
||||||
|
|
||||||
if (useOfflineData) {
|
// Skip the network entirely when device is known offline.
|
||||||
|
if (canUseOffline && knownOffline) {
|
||||||
try {
|
try {
|
||||||
if (await isOfflineDataAvailable()) {
|
const offline = await loadFromIndexedDB(month);
|
||||||
const recipes = await getBriefRecipesOverlappingMonth(month);
|
if (offline) return offline;
|
||||||
return {
|
|
||||||
month,
|
|
||||||
season: rand_array(recipes),
|
|
||||||
isOffline: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load offline season data:', 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 ?? [];
|
favorites = body.favorites ?? [];
|
||||||
}
|
}
|
||||||
} catch {
|
} 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 {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user