Skip to content

Adding Widgets

Adding Widgets

All dashboard widgets follow this chain:

React component
→ TanStack Query hook
→ Next.js API route (/api/widgets/<name>)
→ External service or Docker socket

1. Create the API route

src/app/api/widgets/<name>/route.ts

import { NextResponse } from "next/server"
export const dynamic = "force-dynamic"
export async function GET() {
try {
const res = await fetch("http://your-service/api/endpoint", {
signal: AbortSignal.timeout(5000),
cache: "no-store",
})
if (!res.ok) throw new Error(`API ${res.status}`)
const raw = await res.json()
return NextResponse.json({
// shape the response for the widget
status: raw.status,
value: raw.someValue,
})
} catch {
return NextResponse.json({ error: "unreachable" }, { status: 503 })
}
}

!!! warning “Security checklist” - Never forward raw service responses to the client - Never interpolate user input into service URLs - Validate and shape all data before returning

2. Create the widget component

src/components/widgets/<name>.tsx

"use client"
import { useQuery } from "@tanstack/react-query"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
export function MyWidget() {
const { data, isLoading, isError } = useQuery({
queryKey: ["my-widget"],
queryFn: async () => {
const res = await fetch("/api/widgets/my-widget")
if (!res.ok) throw new Error("unreachable")
return res.json()
},
refetchInterval: 30_000,
})
return (
<Card className="flex flex-col h-full">
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-base font-semibold">My Widget</CardTitle>
</CardHeader>
<CardContent className="flex-1 flex flex-col justify-center">
{isLoading ? (
<Skeleton className="h-6 w-1/2" />
) : isError || !data ? (
<span className="text-danger text-sm">Unavailable</span>
) : (
<div>{data.value}</div>
)}
</CardContent>
</Card>
)
}

3. Add to the dashboard

In src/app/page.tsx (or the dashboard layout), import and place the widget in the grid.

Token reference

Use these CSS tokens for consistent styling:

TokenUsage
text-learnHealthy / success (green)
text-warnWarning / degraded (orange)
text-dangerError / offline (red)
text-ink-dimSecondary labels
text-ink-faintTertiary / metadata