app/status/page.tsx (Server Component)
import { bucketDays, overallUptime, type Check } from "@/lib/uptime";
import { UptimeStrip } from "@/components/uptime-strip";
export const revalidate = 300; // a 90-day history doesn't need per-second freshness
const services = ["API", "Dashboard", "Webhooks"];
export default async function StatusPage() {
const rows = await Promise.all(
services.map(async (name) => {
const days = bucketDays(await getChecks(name));
return { name, days, overall: overallUptime(days) };
}),
);
return (
<main className="mx-auto max-w-3xl space-y-4 p-6">
{rows.map((r) => (
<UptimeStrip key={r.name} name={r.name} days={r.days} overall={r.overall} />
))}
</main>
);
}
async function getChecks(service: string): Promise<Check[]> {
// your monitor's rows for the last 90 days: a database table your cron job
// writes, or a hosted checker's API
void service;
return [];
}
components/uptime-strip.tsx (no "use client")
import type { CSSProperties } from "react";
import type { Day } from "@/lib/uptime";
const pct = new Intl.NumberFormat("en-US", { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 3 });
function color(uptime: number | null) {
if (uptime === null) return "bg-zinc-700";
if (uptime >= 0.999) return "bg-emerald-500";
if (uptime >= 0.99) return "bg-amber-400";
return "bg-rose-500";
}
function label(d: Day) {
const when = new Date(d.date + "T00:00:00Z").toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
if (d.uptime === null) return `${when}: no data`;
return `${when}: ${pct.format(d.uptime)} uptime, ${d.down} of ${d.checks} checks failed`;
}
// No "use client": the tooltip is plain CSS (group-hover, group-focus-within),
// so the whole strip renders on the server and ships no JavaScript.
export function UptimeStrip({ name, days, overall }: { name: string; days: Day[]; overall: number | null }) {
const phoneFrom = days.length - 30; // older days hide on phones
return (
<section className="rounded-xl border border-zinc-800 bg-zinc-950 p-4">
<header className="mb-3 flex items-baseline justify-between text-sm">
<h2 className="font-medium text-zinc-100">{name}</h2>
<span className="tabular-nums text-zinc-400">{overall === null ? "No data" : `${pct.format(overall)} uptime`}</span>
</header>
<ol className="flex h-9 gap-[2px]" aria-label={`${name}, daily uptime, oldest first`}>
{days.map((d, i) => (
<li
key={d.date}
className={`group relative flex-1 ${i < phoneFrom ? "hidden md:flex" : "flex"}`}
style={{ "--i": i } as CSSProperties}
>
<button
type="button"
aria-label={label(d)}
className={`uptime-bar w-full rounded-[2px] ${color(d.uptime)} ${i === days.length - 1 ? "motion-safe:animate-pulse" : ""} focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white`}
/>
<span
aria-hidden="true"
className={`pointer-events-none absolute bottom-full z-10 mb-2 hidden w-max max-w-48 rounded-md border border-zinc-800 bg-zinc-900 px-2 py-1 text-xs text-zinc-200 group-hover:block group-focus-within:block ${i < days.length / 2 ? "left-0" : "right-0"}`}
>
{label(d)}
</span>
</li>
))}
</ol>
<div className="mt-2 flex justify-between text-xs text-zinc-500">
<span className="md:hidden">30 days ago</span>
<span className="hidden md:inline">{days.length} days ago</span>
<span>Today</span>
</div>
</section>
);
}