Skip to content

Status Page

Written for the person refreshing it during an outage: current state above the fold, the maintenance window as a real block with an end time, and a component list that names what still works.

Take 1 of 26 blocks155 lineslucide-reactAdded 13 Sept 2026

What's included

  • app/status-page.tsx
  • 6 blocks it is built from, installed with it
  • Needs lucide-react

Works with

  • React
  • Next.js
  • Tailwind CSS
  • TypeScript

npx hoverlab add status-page

Or over MCP, from your editor's agent — no account needed.

License

Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.

Was this useful?

Preview

1480 changes are saved on this device

You are offline. Keep working — edits are written locally and sent automatically the moment the connection is back. Nothing is lost by closing this tab, though it will not send until you open it again.

Trying again in 8s · attempt 2

Not available until you reconnect

  • Quartzly push
  • Halyard payment runs

Scheduled maintenance this Sunday

Database failover rehearsal in eu-west. The API stays up; exports and scheduled reports are paused for the window.

This is taking longer than planned. We have passed the 4:00 AM UTC estimate. The status page has the current position and is hosted separately from the systems under maintenance.

Still working

  • REST API reads and writes
  • Web application
  • Inbound webhooks
Live status

Hosted separately, so it stays up when this does not.

Knowledge freshness

Answers can only be as current as the source they come from. The oldest connected source was last read degraded for 22 minutes, so anything changed since then may not be reflected yet.

  • REST API

    Public endpoint

    Up to date · last read checked 40 seconds ago

    0 docs

  • Web application

    Interface

    Up to date · last read checked 40 seconds ago

    0 docs

  • Integration sync

    Background worker

    Partly indexed · last read degraded for 22 minutes

    0 docs

  • Exports and reports

    Background worker

    Up to date · last read checked 1 minute ago

    0 docs

  • Outbound webhooks

    Delivery

    Up to date · last read checked 40 seconds ago

    0 docs

Ninety days of uptime

1,453 incident-free days across 306 active days in the last year

LessLevel 0Level 1Level 2Level 3Level 4More
Ninety days of uptime by month
Monthincident-free days
Sep 202572
Oct 202590
Nov 202587
Dec 2025109
Jan 2026111
Feb 202669
Mar 2026132
Apr 2026146
May 2026148
Jun 2026157
Jul 2026170
Aug 2026162

Incident history

Every incident with customer impact, written up within two working days. Post-mortems are public by default and redacted only where a customer is named.

  1. Integration sync backlog

    Degraded
    • changed11:42 UTC — Quartzly push latency above threshold; queue building.
    • fixed11:58 UTC — Cause identified as a slow query after an index change. Rolled back.
    • changed12:04 UTC — Backlog draining. No data lost; every push carries an idempotency key.
  2. Exports delayed for 71 minutes

    Resolved
    • fixedA single tenant’s 4.1M-row export starved the shared worker pool.
    • addedPer-tenant concurrency cap added the same week. This cannot recur in the same form.
  3. Partial API outage, eu-west, 9 minutes

    Resolved
    • fixedCertificate rotation applied to one load balancer ahead of its pair.
    • changedRotation is now staged behind a health check rather than by schedule.

The real page, rendered in your current theme — every section below is a live block, not a screenshot.

The other take

Same page type, different bet about what the reader came for. Neither is the “correct” one — pick the layout whose opening section matches the question your visitors arrive with.

Take 2 of 2
Open the full page for this page

Status Page 02

The status page read during diligence rather than during an outage: ninety days of uptime, the latency curve, and every incident carrying its post-mortem — including the bad one.

System Pages7 blocks237 lines

Built from

All blocks
  1. Offline With Queued ChangesLeads with what still works — three edits saved on this device — instead of with the failure. The retry backoff counts down in the open, and the three things that genuinely cannot work offline are named rather than discovered.
  2. Scheduled Maintenance WindowPlanned downtime with a live countdown, an end time in the reader’s timezone, what still works — and a different sentence once the window overruns rather than a negative timer.
  3. Index Freshness PanelA green tick means the connector works, not that the answers are current. Freshness as an age in words per source, the headline taken from the stalest one, and a failed sync that still says what age it is answering from.
  4. Daily Activity HeatmapA year of daily activity as one square per day, bucketed against a quantile so a single outlier cannot flatten the scale.
  5. Release TimelineA dated changelog on a vertical rail with added/changed/fixed tags and machine-readable <time> values.
  6. App Footer with StatusThe footer of a signed-in product rather than a marketing site: a live status line with words as well as colour, plus locale and theme as native selects.

Only want one section? Open it and copy that block instead — browse Empty & Error States.

Shipped in these templates

Want the whole project rather than this one screen? A template ships every route, the theme tokens and the layout.

Source

app/status-page.tsx
/**
 * The status page.
 *
 *   banner     the current state, first and unmissable
 *   maintenance the scheduled window, when there is one
 *   components what is degraded, by subsystem
 *   history    ninety days of uptime, as a heatmap
 *   incidents  the write-ups, most recent first
 *
 * Written for the person refreshing it during an outage, which is the only
 * visitor a status page has that matters. That reader wants one sentence
 * above the fold and does not want to scroll; everything below the first
 * screen is for the person doing a procurement review next Tuesday.
 *
 * Two things this page does that most do not. The **maintenance window is
 * rendered as a real block with an end time**, not a footnote — "scheduled
 * maintenance" with no end time is indistinguishable from an outage to
 * someone who has just arrived. And the **component list names what still
 * works**, because during a partial outage the actionable information is
 * usually which half to route around.
 *
 * A status page hosted on the same infrastructure as the product is a status
 * page that goes down with it. Deploy this one somewhere else — that is not a
 * detail the markup can enforce, which is why it is written here.
 */

import * as React from 'react'
import { OfflineStateBanner } from '@/components/offline-state-banner'
import { MaintenanceWindowState } from '@/components/maintenance-window-state'
import { RetrievalIndexStatus } from '@/components/retrieval-index-status'
import { ActivityHeatmap } from '@/components/activity-heatmap'
import { ChangelogTimeline } from '@/components/changelog-timeline'
import { FooterStatusLocale } from '@/components/footer-status-locale'

const COMPONENTS = [
  {
    id: 'api',
    name: 'REST API',
    kind: 'Public endpoint',
    state: 'fresh' as const,
    age: 'checked 40 seconds ago',
    documents: 0,
  },
  {
    id: 'web',
    name: 'Web application',
    kind: 'Interface',
    state: 'fresh' as const,
    age: 'checked 40 seconds ago',
    documents: 0,
  },
  {
    id: 'sync',
    name: 'Integration sync',
    kind: 'Background worker',
    state: 'partial' as const,
    age: 'degraded for 22 minutes',
    documents: 0,
    problem: 'Quartzly pushes are queuing. Nothing is lost; the backlog is draining at about 900 lines a minute.',
  },
  {
    id: 'exports',
    name: 'Exports and reports',
    kind: 'Background worker',
    state: 'fresh' as const,
    age: 'checked 1 minute ago',
    documents: 0,
  },
  {
    id: 'webhooks',
    name: 'Outbound webhooks',
    kind: 'Delivery',
    state: 'fresh' as const,
    age: 'checked 40 seconds ago',
    documents: 0,
  },
]

const INCIDENTS = [
  {
    date: '2026-01-09',
    version: 'Degraded',
    title: 'Integration sync backlog',
    items: [
      { kind: 'changed' as const, text: '11:42 UTC — Quartzly push latency above threshold; queue building.' },
      { kind: 'fixed' as const, text: '11:58 UTC — Cause identified as a slow query after an index change. Rolled back.' },
      { kind: 'changed' as const, text: '12:04 UTC — Backlog draining. No data lost; every push carries an idempotency key.' },
    ],
  },
  {
    date: '2025-12-18',
    version: 'Resolved',
    title: 'Exports delayed for 71 minutes',
    items: [
      { kind: 'fixed' as const, text: 'A single tenant’s 4.1M-row export starved the shared worker pool.' },
      { kind: 'added' as const, text: 'Per-tenant concurrency cap added the same week. This cannot recur in the same form.' },
    ],
  },
  {
    date: '2025-11-02',
    version: 'Resolved',
    title: 'Partial API outage, eu-west, 9 minutes',
    items: [
      { kind: 'fixed' as const, text: 'Certificate rotation applied to one load balancer ahead of its pair.' },
      { kind: 'changed' as const, text: 'Rotation is now staged behind a health check rather than by schedule.' },
    ],
  },
]

export default function StatusPage() {
  return (
    <div className="min-h-screen bg-background text-foreground">
      <main>
        {/*
          The banner is the page's headline and is the only thing most
          visitors will read. `queuedChanges` carries the concrete number
          rather than the word "some", because during an incident the reader
          is deciding whether to wait or to route around.
        */}
        <OfflineStateBanner queuedChanges={1480} unavailable={['Quartzly push', 'Halyard payment runs']} />

        <MaintenanceWindowState
          title="Scheduled maintenance this Sunday"
          summary="Database failover rehearsal in eu-west. The API stays up; exports and scheduled reports are paused for the window."
          endsAt="2026-01-18T04:00:00Z"
          stillWorking={['REST API reads and writes', 'Web application', 'Inbound webhooks']}
        />

        <div id="incidents">
          <RetrievalIndexStatus sources={COMPONENTS} />
        </div>

        <ActivityHeatmap heading="Ninety days of uptime" noun="incident-free day" />

        <ChangelogTimeline
          heading="Incident history"
          subheading="Every incident with customer impact, written up within two working days. Post-mortems are public by default and redacted only where a customer is named."
          entries={INCIDENTS}
        />
      </main>

      <FooterStatusLocale
        productName="Acme"
        status="degraded"
        version="2026.1.9"
        links={[
          { label: 'Subscribe to updates', href: '#subscribe' },
          { label: 'Incident history', href: '#incidents' },
          { label: 'Uptime SLA', href: '#sla' },
        ]}
      />
    </div>
  )
}

How to use it

  1. 1. Copy each of the 6 blocks above into components/ — each block page has its own copy button.
  2. 2. Drop this file at app/status-page.tsx. The imports already point at @/components/…, so they resolve with no edits.
  3. 3. Delete the sections you do not want. Every block takes props, so the copy changes without the layout moving.

For AI

The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.

See the prompt

More System Pages

Open the full page for this page

404 Page

Drops straight into app/not-found.tsx, and keeps the site header and footer — on a 404 the navigation is the most useful thing on screen.

System Pages1 blocks62 lines
Open the full page for this page

Server Error

Retry first, because a 500 is our failure and offering navigation implies the rest of the site works — which is exactly what is in doubt.

System Pages2 blocks44 lines
Open the full page for this page

Scheduled Maintenance

Planned downtime with an actual end time on it — "back soon" is not a time, and a maintenance page without one looks like an outage nobody has noticed.

System Pages2 blocks35 lines
Open the full page for this page

Permission Denied

A 403 that names who can grant what was refused, because the next question is always "then who can" and a support link is the wrong answer to it.

System Pages2 blocks42 lines
Take 1 of 2
Open the full page for this page

Legal Document

Terms, privacy, DPA and cookies in one frame — the docs layout, because people arrive at a legal page looking for a specific clause and a wall of text makes that a Ctrl-F expedition.

System Pages3 blocks50 lines
Take 2 of 2
Open the full page for this page

Legal Document 02

One document with a plain-language column beside each clause, for the reader who cannot name the clause they need — and the cookie control inline, because a banner is unreachable the moment it has been dismissed.

System Pages6 blocks200 lines