1. Documentation
  2. Frameworks
  3. React Router
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

React Router

Mount the API, render the page and schedule checks in a React Router app.

The adapter turns a status page into the loader and action exports of one catch-all route. React Router passes the Web Request through unchanged, so nothing is translated.

bash
bun create @heyo-sh/heyo-status my-status-page --template react-router

Project shape

  • my-status-page
    • heyo-status.config.ts
    • heyo-status.database.ts
    • src
      • root.tsx
      • routes.ts
      • routes
        • _index.tsx
        • api.heyo-status.$.ts
      • styles
        • status.css
    • react-router.config.ts
    • vite.config.ts

The API route

src/routes.ts
import { index, route, type RouteConfig } from "@react-router/dev/routes";export default [  index("routes/_index.tsx"),  route("api/heyo-status/*", "routes/api.heyo-status.$.ts"),] satisfies RouteConfig;
src/routes/api.heyo-status.$.ts
import { statusHandler } from "@heyo-sh/heyo-status/react-router";import { statusPage } from "../../heyo-status.config";export const { loader, action } = statusHandler(statusPage);

loader serves GET, action serves POST, PATCH, PUT and DELETE. The route path and basePath must agree: api/heyo-status/* pairs with the default "/api/heyo-status".

The page

src/routes/_index.tsx
import { StatusPage } from "@heyo-sh/heyo-status/ui";export async function loader() {  const { statusPage } = await import("../../heyo-status.config");  return {    basePath: statusPage.basePath,    branding: statusPage.branding,    colors: statusPage.colors,    description: statusPage.description,    metrics: statusPage.metrics,    mode: statusPage.mode,    snapshot: await statusPage.api.getSnapshot(),    subscribersEnabled: statusPage.subscribers.enabled,    subscriberMethods: statusPage.subscribers.methods,    theme: statusPage.theme,    title: statusPage.title,  };}export function meta({ data }: { data?: Awaited<ReturnType<typeof loader>> }) {  return [    { title: data?.title ?? "Heyo Status" },    { name: "description", content: data?.description ?? "Service status" },  ];}export default function Index({ loaderData }: { loaderData: Awaited<ReturnType<typeof loader>> }) {  return <StatusPage {...loaderData} />;}
Import the configuration dynamically

await import("../../heyo-status.config") inside the loader keeps the embedded database adapter on the server-only path, out of the client graph and out of build-time static analysis.

Root layout

src/root.tsx
import { statusThemeBootstrapScript } from "@heyo-sh/heyo-status/theme/script";import { Links, Meta, Outlet, Scripts, ScrollRestoration, useRouteLoaderData } from "react-router";import "@heyo-sh/heyo-status/theme/status.css";import "./styles/status.css";export async function loader() {  const { statusPage } = await import("../heyo-status.config");  return { mode: statusPage.mode };}export function Layout({ children }: { children: React.ReactNode }) {  const data = useRouteLoaderData<typeof loader>("root");  return (    <html lang="en" suppressHydrationWarning>      <head>        <meta charSet="utf-8" />        <meta content="width=device-width, initial-scale=1" name="viewport" />        <Meta />        <Links />        <script          dangerouslySetInnerHTML={{            __html: statusThemeBootstrapScript(undefined, data?.mode),          }}        />      </head>      <body>        {children}        <ScrollRestoration />        <Scripts />      </body>    </html>  );}export default function App() {  return <Outlet />;}

Layout also renders for error boundaries, where the loader never ran — hence the optional chaining on data.

Scripts

package.json
{  "scripts": {    "dev": "heyo-status dev -- bunx --bun react-router dev",    "build": "react-router build",    "start": "react-router-serve ./build/server/index.js",    "check": "heyo-status check run",    "db:migrate": "heyo-status db migrate",    "db:seed": "heyo-status db seed --uptime 99.95 --days 90"  }}

heyo-status dev -- wraps the framework dev server so the local cron scheduler runs beside it and stops with it.

Requirements and deployment

  • Node.js 20.19+ in production for native tcp and ssl checks.
  • Cloudflare Workers: the overlay adds src/status-worker.ts, which binds D1 before the request handler runs and forwards scheduled events. See Deploying to Cloudflare.
  • Vercel: the overlay adds src/routes/api.cron.$task.ts and a vercel.json with the three cron entries. See Deploying to Vercel.

Related

  • Deploying to Cloudflare
  • StatusPage props
  • CLI commands
Colour mode< PreviousNext.jsNext >

Powered by heyo

On this page

Project shapeThe API routeThe pageRoot layoutScriptsRequirements and deployment