diff --git a/app/api/rates/route.ts b/app/api/rates/route.ts index 6f9e1cc..8bebc27 100644 --- a/app/api/rates/route.ts +++ b/app/api/rates/route.ts @@ -5,7 +5,7 @@ import { RATES_CACHE_CONTROL_VALUE, } from "@/lib/server/rates-cache"; -export const revalidate = 300; +export const revalidate = 60; export async function GET() { try { @@ -19,13 +19,18 @@ export async function GET() { }); } catch (error) { const cachedRates = getLastCachedRates(); + const fallbackErrorMessage = + error instanceof Error + ? error.message.replace(/[\r\n]+/g, " ") + : "Upstream provider error"; if (cachedRates) { return NextResponse.json(cachedRates, { status: 200, headers: { "Cache-Control": RATES_CACHE_CONTROL_VALUE, - "X-Cache-Fallback": "stale-on-error" + "X-Cache-Fallback": "stale-on-error", + "X-Cache-Fallback-Error": fallbackErrorMessage } }); } diff --git a/components/converter/converter-card.tsx b/components/converter/converter-card.tsx index dcb2b16..cdcb338 100644 --- a/components/converter/converter-card.tsx +++ b/components/converter/converter-card.tsx @@ -243,6 +243,24 @@ export function ConverterCard({ } }; + const displayUpdatedAt = useMemo(() => { + if (!data) { + return null; + } + + const timestamps = [new Date(data.updatedAt).getTime()]; + + if (marketData?.updatedAt) { + timestamps.push(new Date(marketData.updatedAt).getTime()); + } + + const latest = Math.max( + ...timestamps.filter((timestamp) => Number.isFinite(timestamp)), + ); + + return Number.isFinite(latest) ? new Date(latest).toISOString() : data.updatedAt; + }, [data, marketData?.updatedAt]); + if (isLoading && !data) { return ; } @@ -497,7 +515,7 @@ export function ConverterCard({

Last updated

- {formatTimestamp(data.updatedAt)} + {formatTimestamp(displayUpdatedAt ?? data.updatedAt)}

diff --git a/hooks/use-market-rates.ts b/hooks/use-market-rates.ts index 12c5165..a5c027d 100644 --- a/hooks/use-market-rates.ts +++ b/hooks/use-market-rates.ts @@ -28,9 +28,11 @@ export function useMarketRates() { const payload = await response.json(); const parsed = parseRatesResponse(payload); + const isFallback = response.headers.get("X-Cache-Fallback") === "stale-on-error"; + const fallbackError = response.headers.get("X-Cache-Fallback-Error"); setData(parsed); - setError(null); + setError(isFallback ? (fallbackError ?? "Upstream provider error") : null); } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; setError(message); diff --git a/lib/server/rates-cache.ts b/lib/server/rates-cache.ts index 9747cd0..608bcfb 100644 --- a/lib/server/rates-cache.ts +++ b/lib/server/rates-cache.ts @@ -1,9 +1,9 @@ import { fetchUnifiedRates } from "@/lib/api/normalize"; import type { RatesResponse } from "@/lib/rates"; -export const RATES_CACHE_TTL_MS = 300_000; +export const RATES_CACHE_TTL_MS = 60_000; export const RATES_CACHE_CONTROL_VALUE = - "s-maxage=300, stale-while-revalidate=1800"; + "s-maxage=60, stale-while-revalidate=600"; let cachedRates: RatesResponse | null = null; let cacheTimestamp = 0;