/** * Reading feature flags. * * Server-side only. No flag value reaches the browser bundle, and there is no * Unleash dependency in package.json — the SDK lives in FastAPI, which already * owns every other piece of data this app renders. * * Flags are declared in backend/flags.py. A purely front-end flag still has to * be declared there; it is a flat data edit, and the return is that one list * answers "what flags exist" for the whole system. */ export type Flags = Record; /* * Reading flags pins the calling route to this ISR floor: Next uses the LOWEST * revalidate among a route's fetches to set the whole route's revalidation * frequency. 300s matches what /school/[slug] already sits at, so a page that * reads flags is no more dynamic than a school page already is. * * It is also what makes a flip propagate without a webhook: five minutes on * school pages, an hour on place pages, against flags that flip monthly. */ export const FLAGS_REVALIDATE = 300; const API = process.env.FASTAPI_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api'; /** * Every flag and its value. Never throws: an unreadable flag is a dark one. * * `revalidate` is the caller's, because reading flags pins the whole route to * the lowest revalidate among its fetches. Every SEO route here declares * 604800; gating one at the 300s default would drop it from a weekly cache to * a 5-minute one. Pass the route's own floor and gating costs it nothing. * * The trade is flag-flip latency: a route that revalidates weekly takes up to * a week to notice a flip. Pass a smaller number where a flip must land fast. */ export async function getFlags(revalidate: number = FLAGS_REVALIDATE): Promise { try { const res = await fetch(`${API}/flags`, { next: { revalidate }, }); if (!res.ok) return {}; return await res.json(); } catch { return {}; } }