fix(build): make admin tree dynamic and catch Supabase fetch errors at build
Deploy to route.crispygoat.com / deploy (push) Failing after 4m16s
Deploy to route.crispygoat.com / deploy (push) Failing after 4m16s
Two errors were aborting the Gitea build:
1. DYNAMIC_SERVER_USAGE on /admin/settings/square-sync (and any admin page):
getAdminUser() reads cookies() via next/headers. The admin layout tried
to prerender statically, so the first child page that hit cookies()
aborted the build. Added 'export const dynamic = "force-dynamic"' to
src/app/admin/layout.tsx so the whole admin tree opts out of static
prerender.
2. Prerender ECONNREFUSED on /indian-river-direct/stops and the sitemap:
getPublicStopsForBrand / getActiveStopsForSitemap / getBrandSettingsPublic
fetch NEXT_PUBLIC_SUPABASE_URL at build time. The Gitea runner sets the
Supabase env vars (so the existing env-var guard passes) but the URL
is unreachable, so fetch throws ECONNREFUSED and the prerender aborts.
Wrapped each fetch in try/catch returning [] / {success: false} so the
prerender completes; runtime behavior is unchanged when the fetch
succeeds.
Also added force-dynamic to the square-sync page itself as belt-and-braces
in case the layout change doesn't propagate.
This commit is contained in:
@@ -279,22 +279,29 @@ export async function getBrandSettingsPublic(brandSlug: string): Promise<GetBran
|
||||
return { success: false, error: "Supabase not configured", wholesaleEnabled: undefined };
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_brand_settings_by_slug`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ p_brand_slug: brandSlug }),
|
||||
}
|
||||
);
|
||||
// Wrapped in try/catch so a build-time Supabase outage (ECONNREFUSED)
|
||||
// doesn't crash the prerender — the page just falls back to its
|
||||
// default brand name and revalidates from a real request later.
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_brand_settings_by_slug`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ p_brand_slug: brandSlug }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) return { success: false, error: "Failed to fetch brand settings", wholesaleEnabled: undefined };
|
||||
const data = await response.json();
|
||||
return {
|
||||
success: true,
|
||||
settings: data,
|
||||
wholesaleEnabled: data?.wholesale_enabled,
|
||||
};
|
||||
if (!response.ok) return { success: false, error: "Failed to fetch brand settings", wholesaleEnabled: undefined };
|
||||
const data = await response.json();
|
||||
return {
|
||||
success: true,
|
||||
settings: data,
|
||||
wholesaleEnabled: data?.wholesale_enabled,
|
||||
};
|
||||
} catch {
|
||||
return { success: false, error: "Failed to fetch brand settings", wholesaleEnabled: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveBrandSettings(params: {
|
||||
|
||||
+39
-26
@@ -116,19 +116,25 @@ export async function getActiveStopsForSitemap(): Promise<StopForSitemap[]> {
|
||||
// an empty list lets the sitemap render with only the static URLs.
|
||||
if (!supabaseUrl || !supabaseKey) return [];
|
||||
|
||||
// Get all active stops with their brand slug
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_active_stops_with_brand`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
// Get all active stops with their brand slug. Wrapped in try/catch so a
|
||||
// build-time outage (ECONNREFUSED) doesn't crash the prerender — the
|
||||
// sitemap just renders without stop URLs.
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_active_stops_with_brand`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) return [];
|
||||
if (!response.ok) return [];
|
||||
|
||||
const stops = await response.json();
|
||||
return Array.isArray(stops) ? stops : [];
|
||||
const stops = await response.json();
|
||||
return Array.isArray(stops) ? stops : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,21 +171,28 @@ export async function getPublicStopsForBrand(
|
||||
// fetch path is unchanged.
|
||||
if (!supabaseUrl || !supabaseKey) return [];
|
||||
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_public_stops_for_brand`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ p_brand_slug: brandSlug }),
|
||||
next: {
|
||||
revalidate: 300,
|
||||
tags: ["stops", `brand:${brandSlug}:stops`],
|
||||
},
|
||||
}
|
||||
);
|
||||
// Wrapped in try/catch so a build-time Supabase outage (ECONNREFUSED)
|
||||
// doesn't crash the prerender — the page just renders with no stops
|
||||
// and revalidates from a real request once the cache is warm.
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${supabaseUrl}/rest/v1/rpc/get_public_stops_for_brand`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: { ...svcHeaders(supabaseKey), "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ p_brand_slug: brandSlug }),
|
||||
next: {
|
||||
revalidate: 300,
|
||||
tags: ["stops", `brand:${brandSlug}:stops`],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) return [];
|
||||
if (!response.ok) return [];
|
||||
|
||||
const stops = await response.json();
|
||||
return Array.isArray(stops) ? (stops as PublicStop[]) : [];
|
||||
const stops = await response.json();
|
||||
return Array.isArray(stops) ? (stops as PublicStop[]) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user