> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getsabo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Status Page with Better Stack

> Display real-time system status in your footer using Better Stack's status page integration to build trust and transparency with your users.

<Info>
  Sabo includes a pre-built status badge widget that displays real-time system status from Better Stack directly in your application's footer. This provides transparency to users about service availability and ongoing incidents.
</Info>

## Overview

A status page is a dedicated public page that shows the operational status of your services. It's essential for:

* **Building trust** - Show users you're transparent about service health
* **Reducing support tickets** - Users check status before contacting support
* **Professional image** - Demonstrates operational maturity
* **Incident communication** - Keep users informed during outages

Sabo's implementation features:

* **Real-time status badge** embedded in footer
* **Automatic theme matching** (light/dark mode)
* **Hosted status page** at your custom domain (e.g., `status.yourdomain.com`)
* **Historical uptime** data with visual graphs
* **Powered by Better Stack** - Industry-leading status page provider

<Note>
  Better Stack (formerly Checkly) provides enterprise-grade uptime monitoring and status pages. Their free tier includes everything you need to get started.
</Note>

<Tip>
  Need a deeper tour of Better Stack's capabilities (monitors, incidents, alerting)? Keep their official <a href="https://betterstack.com/docs/uptime/start/" target="_blank" rel="noreferrer">Uptime documentation</a> open while you work through this guide.
</Tip>

***

## Quick Start

<Steps>
  <Step title="Create Better Stack account">
    Sign up for a free Better Stack account to get started.

    1. Visit [Better Stack](https://betterstack.com/uptime)
    2. Create a free account (no credit card required)
    3. You'll be redirected to the dashboard

    <Check>
      Better Stack's free tier includes 10 monitors and a branded status page - perfect for most projects.
    </Check>
  </Step>

  <Step title="Create your status page">
    Set up a public status page for your services.

    1. In Better Stack dashboard, go to **Status Pages**
    2. Click **Create Status Page**
    3. Configure:
       * **Name**: Your company/product name (e.g., "Acme")
       * **Subdomain**: Choose a subdomain (e.g., `acme` → `acme.betteruptime.com`)
       * **Custom domain** (optional): `status.yourdomain.com`
    4. Click **Create**

    <Tip>
      Custom domains are available on all plans. Set up a CNAME record pointing to Better Stack to use your own domain.
    </Tip>
  </Step>

  <Step title="Add monitors">
    Create monitors for the services you want to track.

    1. Go to **Monitors** → **Add Monitor**
    2. Configure your first monitor:
       * **Monitor Type**: HTTP(S)
       * **URL**: Your main site (e.g., `https://yourdomain.com`)
       * **Name**: "Main Website"
       * **Check Frequency**: Every 1 minute (free tier)
    3. Click **Create Monitor**

    Repeat for other services (e.g., demo site, docs, API):

    * `https://demo.yourdomain.com` → "Demo Site"
    * `https://docs.yourdomain.com` → "Documentation"
    * `https://api.yourdomain.com` → "API"

    <Check>
      Each monitor will appear on your status page with real-time status and uptime history.
    </Check>
  </Step>

  <Step title="Get your status badge URL">
    Copy the embeddable badge URL for your status page.

    1. Go to **Status Pages** → Your status page
    2. Click **Settings** → **Badge**
    3. Copy the badge URL (format: `https://your-subdomain.betteruptime.com/badge`)

    Example:

    ```
    https://acme.betteruptime.com/badge
    ```

    <Note>
      You'll use this URL in the next step to configure Sabo's status badge widget.
    </Note>
  </Step>

  <Step title="Update status badge URL in Sabo">
    Configure Sabo to display your status badge.

    Open `src/components/shared/status-badge.tsx` and update line 29 with your badge URL:

    ```tsx src/components/shared/status-badge.tsx theme={null}
    return (
      <iframe
        src={`https://your-subdomain.betteruptime.com/badge?theme=${badgeTheme}`}
        width="185"
        height="30"
        frameBorder="0"
        scrolling="no"
        style={{ colorScheme: "none" }}
        title="System Status"
      />
    );
    ```

    Replace `your-subdomain` with your actual Better Stack subdomain.

    <Warning>
      Keep the `?theme=${badgeTheme}` parameter! This ensures the badge matches your site's light/dark mode.
    </Warning>
  </Step>

  <Step title="Verify integration">
    Check that the status badge appears in your footer.

    1. Restart your dev server: `pnpm dev`
    2. Visit `http://localhost:3000`
    3. Scroll to footer
    4. You should see the status badge next to the theme toggle

    **Expected appearance:**

    * Light mode: Badge with light background
    * Dark mode: Badge with dark background
    * Status text: "All services operational" (or current status)

    <Check>
      The badge updates automatically every 60 seconds to reflect current status.
    </Check>
  </Step>
</Steps>

***

## How It Works

### Status Badge Component

Sabo's status badge is implemented in `src/components/shared/status-badge.tsx`:

```tsx src/components/shared/status-badge.tsx theme={null}
"use client";
"use no memo";

import { useTheme } from "next-themes";
import { useEffect, useState, startTransition } from "react";

export function StatusBadge() {
  const { resolvedTheme } = useTheme();
  const [mounted, setMounted] = useState(false);

  // Only render after mounting to avoid hydration mismatch
  useEffect(() => {
    startTransition(() => {
      setMounted(true);
    });
  }, []);

  // Use resolvedTheme which handles system theme automatically
  const badgeTheme = resolvedTheme === "dark" ? "dark" : "light";

  // Return placeholder during SSR and initial render
  if (!mounted) {
    return <div className="h-[30px] w-[185px]" />;
  }

  return (
    <iframe
      src={`https://status.getsabo.com/badge?theme=${badgeTheme}`}
      width="185"
      height="30"
      frameBorder="0"
      scrolling="no"
      style={{ colorScheme: "none" }}
      title="System Status"
    />
  );
}
```

### Key Features

<AccordionGroup>
  <Accordion title="Theme Synchronization">
    The badge automatically matches your site's theme using `next-themes`:

    ```tsx theme={null}
    const badgeTheme = resolvedTheme === "dark" ? "dark" : "light";
    ```

    **How it works:**

    1. `useTheme()` hook detects current theme
    2. `resolvedTheme` returns "dark" or "light" (handles system preference)
    3. Badge URL includes `?theme=` parameter
    4. Better Stack serves appropriate badge design

    **Supported themes:**

    * `light` - Light background, dark text
    * `dark` - Dark background, light text
  </Accordion>

  <Accordion title="Hydration Mismatch Prevention">
    The component uses careful client-side rendering to avoid React hydration errors:

    ```tsx theme={null}
    const [mounted, setMounted] = useState(false);

    useEffect(() => {
      startTransition(() => {
        setMounted(true);
      });
    }, []);

    if (!mounted) {
      return <div className="h-[30px] w-[185px]" />;
    }
    ```

    **Why this matters:**

    * Server renders a placeholder `<div>` (same dimensions as badge)
    * Client renders the actual iframe after hydration
    * Prevents "Expected server HTML to contain..." errors
    * Uses `startTransition` for smoother rendering

    <Tip>
      This pattern is recommended for all components that depend on `next-themes`. See [Next.js hydration docs](https://nextjs.org/docs/messages/react-hydration-error).
    </Tip>
  </Accordion>

  <Accordion title="Footer Integration">
    The status badge is integrated into Sabo's footer component:

    ```tsx src/components/shared/footer.tsx theme={null}
    import { StatusBadge } from "@/components/shared/status-badge";

    export function Footer() {
      return (
        <footer className="bg-background/95 w-full border-t">
          <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
            {/* Main footer content... */}
            
            {/* Bottom Bar */}
            <div className="border-t py-6">
              <div className="flex flex-col items-start justify-between gap-6 md:flex-row">
                <p className="text-muted-foreground text-sm">
                  © 2025 Acme. All rights reserved.
                </p>
                
                <div className="flex items-center gap-4">
                  <StatusBadge />
                  <ThemeToggle />
                </div>
              </div>
            </div>
          </div>
        </footer>
      );
    }
    ```

    **Placement rationale:**

    * Bottom-right corner of footer (alongside theme toggle)
    * Visible on every page
    * Non-intrusive but accessible
    * Industry standard location for status indicators
  </Accordion>

  <Accordion title="iframe Security">
    The badge uses an iframe for security and isolation:

    ```tsx theme={null}
    <iframe
      src={`https://status.getsabo.com/badge?theme=${badgeTheme}`}
      width="185"
      height="30"
      frameBorder="0"
      scrolling="no"
      style={{ colorScheme: "none" }}
      title="System Status"
    />
    ```

    **Security benefits:**

    * **Sandboxed execution**: Badge cannot access your site's cookies or data
    * **CSP friendly**: Content Security Policy compatible
    * **No JavaScript required**: Pure iframe embed
    * **Automatic updates**: Better Stack updates badge without code changes

    **Accessibility:**

    * `title` attribute provides screen reader context
    * Fixed dimensions prevent layout shift
    * No scrolling (compact design)
  </Accordion>
</AccordionGroup>

***

## Better Stack Setup

### Creating Monitors

Monitors check your services at regular intervals and update status automatically.

<Tabs>
  <Tab title="Website Monitor">
    Monitor your main website's availability.

    **Configuration:**

    ```
    Monitor Type: HTTP(S)
    URL: https://yourdomain.com
    Name: Main Website
    Check Frequency: Every 1 minute
    Expected Status Code: 200
    Timeout: 30 seconds
    Regions: Multi-region (automatic)
    ```

    **What it checks:**

    * HTTP response status code
    * Response time
    * SSL certificate validity
    * DNS resolution
  </Tab>

  <Tab title="API Monitor">
    Monitor your API endpoints.

    **Configuration:**

    ```
    Monitor Type: HTTP(S)
    URL: https://api.yourdomain.com/health
    Name: API
    Method: GET
    Expected Status Code: 200
    Expected Response: {"status": "ok"}
    Headers: Authorization: Bearer [token]
    ```

    **Advanced checks:**

    * Response body validation
    * JSON schema validation
    * Custom headers
    * Authentication
  </Tab>

  <Tab title="Database Monitor">
    Monitor database connectivity (via health endpoint).

    **Configuration:**

    ```
    Monitor Type: HTTP(S)
    URL: https://yourdomain.com/api/health/db
    Name: Database
    Expected Response: {"database": "connected"}
    Alert Threshold: 3 consecutive failures
    ```

    **Best practice:**

    * Create a dedicated health check endpoint
    * Don't expose sensitive data
    * Check connection pool status
    * Validate read/write operations
  </Tab>
</Tabs>

### Incident Management

When a monitor detects an issue, Better Stack creates an incident automatically.

**Incident workflow:**

1. **Detection**: Monitor fails X consecutive checks (configurable)
2. **Alert**: Better Stack sends notifications (email, Slack, PagerDuty, etc.)
3. **Status page**: Incident appears on your status page
4. **Resolution**: Monitor recovers, incident auto-resolves
5. **Post-mortem**: Incident history retained for analysis

**Customize incident display:**

1. Go to **Status Pages** → Settings → **Incidents**
2. Configure:
   * Which monitors trigger public incidents
   * Incident title format
   * Automatic status updates
   * Resolution messages

<Tip>
  Not all monitor failures need to be public. Use "Private monitors" for internal services that shouldn't appear on your status page.
</Tip>

### Custom Domain Setup

Use your own domain for a professional status page.

<Steps>
  <Step title="Add CNAME record">
    In your DNS provider, add a CNAME record:

    ```dns theme={null}
    Type: CNAME
    Name: status
    Value: status.betteruptime.com
    TTL: 3600 (or Auto)
    ```

    **Example for popular DNS providers:**

    <Tabs>
      <Tab title="Cloudflare">
        1. Dashboard → DNS → Add record
        2. Type: CNAME
        3. Name: `status`
        4. Target: `status.betteruptime.com`
        5. Proxy status: DNS only (orange cloud OFF)
        6. Save
      </Tab>

      <Tab title="Vercel DNS">
        1. Domains → \[Your domain] → DNS
        2. Add record
        3. Type: CNAME
        4. Name: `status`
        5. Value: `status.betteruptime.com`
        6. Save
      </Tab>

      <Tab title="Namecheap">
        1. Domain List → Manage → Advanced DNS
        2. Add New Record
        3. Type: CNAME Record
        4. Host: `status`
        5. Value: `status.betteruptime.com`
        6. TTL: Automatic
        7. Save
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure in Better Stack">
    Add your custom domain to Better Stack.

    1. Go to **Status Pages** → Settings → **Domain**
    2. Enter: `status.yourdomain.com`
    3. Click **Save**
    4. Wait for DNS propagation (up to 24 hours, usually 15 minutes)

    <Check>
      Better Stack automatically provisions SSL certificate via Let's Encrypt.
    </Check>
  </Step>

  <Step title="Update badge URL">
    Update `status-badge.tsx` with your custom domain:

    ```tsx theme={null}
    src={`https://status.yourdomain.com/badge?theme=${badgeTheme}`}
    ```
  </Step>
</Steps>

***

## Customization

### Badge Appearance

Better Stack provides several badge designs:

```tsx theme={null}
// Default badge (compact)
src={`https://status.yourdomain.com/badge?theme=${badgeTheme}`}

// Large badge (more info)
src={`https://status.yourdomain.com/badge/large?theme=${badgeTheme}`}

// Inline text (no border)
src={`https://status.yourdomain.com/badge/inline?theme=${badgeTheme}`}
```

### Status Page Branding

Customize your status page's appearance in Better Stack dashboard:

**Settings → Branding:**

* **Logo**: Upload your company logo (PNG, SVG, max 2MB)
* **Favicon**: Custom favicon for status page
* **Primary color**: Brand color (hex code)
* **Background**: Light/dark mode backgrounds
* **Custom CSS**: Advanced styling (paid plans)

**Settings → Content:**

* **Header text**: Replace "System Status" with your text
* **Footer links**: Add links to main site, support, etc.
* **About section**: Company description
* **Contact info**: Support email, Twitter handle

<Tip>
  Match your status page's branding to your main site for a cohesive experience.
</Tip>

### Alternative Placements

The status badge can be placed anywhere in your app:

```tsx theme={null}
// In header
import { StatusBadge } from "@/components/shared/status-badge";

export function Header() {
  return (
    <header>
      <nav>...</nav>
      <StatusBadge /> {/* Top-right corner */}
    </header>
  );
}

// In dashboard sidebar
export function Sidebar() {
  return (
    <aside>
      <nav>...</nav>
      <div className="mt-auto">
        <StatusBadge />
      </div>
    </aside>
  );
}

// As a modal/popup
export function StatusModal() {
  return (
    <Dialog>
      <DialogTrigger>
        <StatusBadge />
      </DialogTrigger>
      <DialogContent>
        {/* Full status page iframe */}
        <iframe src="https://status.yourdomain.com" />
      </DialogContent>
    </Dialog>
  );
}
```

<Tip>
  If you reposition the badge (header, footer variants, dashboard sidebar), follow the layout guidance in <a href="/customization-guides/navigation/footer">Footer customization</a> so spacing and responsive behavior stay consistent across breakpoints.
</Tip>

***

## Best Practices

<AccordionGroup>
  <Accordion title="1. Monitor critical user paths">
    Don't just monitor your homepage - track the entire user journey.

    **Essential monitors:**

    * Landing page
    * Sign-up/login page
    * Dashboard/app
    * API endpoints
    * Payment processing
    * Database connectivity

    **Example monitor set:**

    ```
    ✓ https://yourdomain.com (homepage)
    ✓ https://yourdomain.com/sign-in (auth)
    ✓ https://yourdomain.com/dashboard (app)
    ✓ https://api.yourdomain.com/health (API health)
    ✓ https://yourdomain.com/api/checkout (payments)
    ```
  </Accordion>

  <Accordion title="2. Set appropriate check intervals">
    Balance between monitoring frequency and cost.

    **Recommended intervals:**

    * **Critical services** (auth, payments): 1 minute
    * **Main website**: 3 minutes
    * **Documentation**: 5 minutes
    * **Internal tools**: 10 minutes

    **Why not always 1 minute?**

    * Free tier has limited checks per month
    * More frequent checks = higher costs on paid plans
    * Not all services need real-time monitoring

    <Note>
      Better Stack's free tier includes 10 monitors checking every 1 minute = 14,400 checks/day.
    </Note>
  </Accordion>

  <Accordion title="3. Use meaningful monitor names">
    **Bad:**

    * Monitor 1
    * Test
    * Website

    **Good:**

    * Main Website (getsabo.com)
    * API - Production
    * Stripe Webhook Endpoint

    **Why it matters:**

    * Clear incident messages on status page
    * Easier alert triage
    * Better analytics and reporting
  </Accordion>

  <Accordion title="4. Configure alert thresholds">
    Avoid alert fatigue with smart thresholds.

    **Default:** Alert after 1 failed check
    **Better:** Alert after 3 consecutive failures

    **Why?**

    * Prevents false positives (network blips)
    * Confirms actual outages
    * Reduces notification noise

    **Configuration:**

    ```
    Alert Threshold: 3 consecutive failures
    Recovery Threshold: 2 consecutive successes
    Cooldown Period: 5 minutes
    ```
  </Accordion>

  <Accordion title="5. Communicate during incidents">
    Transparency builds trust. Update your status page during outages.

    **Incident update template:**

    ```
    [12:34] Investigating
    We're aware of an issue affecting sign-in and investigating the cause.

    [12:45] Identified
    The issue is caused by a database connection timeout. We're working on a fix.

    [13:15] Monitoring
    A fix has been deployed. We're monitoring to ensure stability.

    [13:30] Resolved
    The issue has been fully resolved. All services are operational.
    ```

    **Best practices:**

    * Update every 15-30 minutes during incidents
    * Be specific about what's affected
    * Provide estimated resolution time (if known)
    * Apologize and explain after resolution
  </Accordion>
</AccordionGroup>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Status badge not appearing">
    **Symptoms:** Footer shows blank space where badge should be.

    **Causes:**

    1. Incorrect badge URL in `status-badge.tsx`
    2. Better Stack status page not created
    3. Ad blocker blocking iframe
    4. CSP headers blocking iframe

    **Fix:**

    1. **Verify URL**: Check line 29 in `status-badge.tsx` has correct subdomain
    2. **Check Better Stack**: Visit your status page URL directly in browser
    3. **Test without ad blocker**: Disable uBlock Origin, Privacy Badger, etc.
    4. **Check CSP**: If using Content Security Policy, allow Better Stack:
       ```ts next.config.ts theme={null}
       headers: async () => [{
         key: 'Content-Security-Policy',
         value: "frame-src 'self' https://*.betteruptime.com"
       }]
       ```
    5. **Check console**: Browser DevTools → Console for iframe errors
  </Accordion>

  <Accordion title="Badge shows wrong theme">
    **Symptoms:** Badge has light theme in dark mode (or vice versa).

    **Causes:**

    1. Missing `?theme=${badgeTheme}` parameter
    2. Theme not resolving correctly
    3. Server-side render mismatch

    **Fix:**

    1. **Verify parameter**: Ensure URL includes `?theme=${badgeTheme}`
    2. **Check theme hook**: `resolvedTheme` should be "dark" or "light"
    3. **Test mounting**: Badge should show placeholder until `mounted === true`
    4. **Check `next-themes`**: Ensure `ThemeProvider` wraps your app in `layout.tsx`

    **Debug with console:**

    ```tsx theme={null}
    const badgeTheme = resolvedTheme === "dark" ? "dark" : "light";
    console.log("Theme:", resolvedTheme, "Badge theme:", badgeTheme);
    ```
  </Accordion>

  <Accordion title="Custom domain not working">
    **Symptoms:** `status.yourdomain.com` shows DNS error.

    **Causes:**

    1. CNAME record not created
    2. DNS not propagated
    3. Wrong CNAME target
    4. Cloudflare proxy enabled (should be DNS only)

    **Fix:**

    1. **Verify CNAME**: Use [DNS Checker](https://dnschecker.org) to verify:
       ```
       status.yourdomain.com → CNAME → status.betteruptime.com
       ```

    2. **Wait for propagation**: DNS can take up to 24 hours (usually 15 min)

    3. **Check target**: Must point to `status.betteruptime.com` exactly

    4. **Disable Cloudflare proxy**: Orange cloud should be OFF (gray cloud)

    5. **Test with dig**:
       ```bash theme={null}
       dig status.yourdomain.com CNAME

       # Should return:
       # status.yourdomain.com. 3600 IN CNAME status.betteruptime.com.
       ```
  </Accordion>

  <Accordion title="Badge not updating in real-time">
    **Symptoms:** Status badge shows old status after incident resolved.

    **Causes:**

    * Browser cache
    * iframe caching
    * CDN caching

    **Fix:**

    1. **Hard refresh**: Ctrl+Shift+R (Windows/Linux) or Cmd+Shift+R (Mac)
    2. **Clear iframe cache**: Better Stack badges have 60-second cache
    3. **Wait 1-2 minutes**: Badge auto-refreshes every 60 seconds
    4. **Check Better Stack**: Verify status page shows correct status

    <Note>
      Badge updates are not instant. Better Stack caches badge images for 60 seconds to reduce load.
    </Note>
  </Accordion>

  <Accordion title="Monitors showing false positives">
    **Symptoms:** Monitor reports down but service is actually up.

    **Causes:**

    1. Too aggressive timeout (\< 10 seconds)
    2. Checking from single region
    3. Rate limiting from monitoring checks
    4. SSL certificate issues
    5. Firewall blocking Better Stack IPs

    **Fix:**

    1. **Increase timeout**: Set to 30 seconds minimum
    2. **Multi-region checks**: Enable in monitor settings
    3. **Whitelist Better Stack**: Allow IPs `35.186.224.0/24` and `35.197.128.0/24`
    4. **Check SSL**: Ensure certificate is valid and not expired
    5. **Alert threshold**: Set to 3 consecutive failures instead of 1
    6. **Test manually**: Visit URL from different locations (VPN, mobile network)
  </Accordion>
</AccordionGroup>

***

## Going Further

### Integrations

Better Stack integrates with popular tools:

**Alerting:**

* Slack - Real-time incident notifications
* Discord - Status updates in your server
* PagerDuty - On-call management
* Microsoft Teams - Enterprise communications
* Webhooks - Custom integrations

**Configuration:**

1. Better Stack → **Integrations**
2. Select your tool
3. Authorize connection
4. Configure alert rules

<Tip>
  Set up Slack notifications for your team to stay informed about incidents immediately.
</Tip>

### Advanced Monitoring

**Multi-step checks:**

```
1. Visit login page
2. Submit credentials
3. Verify dashboard loads
4. Check API response
```

**Synthetic monitoring:**

* Simulate real user interactions
* Test complete user flows
* Validate complex workflows
* Paid plans only

**Global checks:**

* Test from 10+ regions worldwide
* Detect regional outages
* Optimize CDN performance
* Available on all plans

### Alternative Solutions

If Better Stack doesn't fit your needs:

| Solution          | Pros                           | Cons                           | Price          |
| ----------------- | ------------------------------ | ------------------------------ | -------------- |
| **Better Stack**  | Easy setup, generous free tier | Fewer regions on free plan     | Free - \$29/mo |
| **Statuspage.io** | Atlassian ecosystem, powerful  | Expensive, complex             | $29 - $699/mo  |
| **StatusCast**    | Simple, affordable             | Limited features               | $19 - $99/mo   |
| **Instatus**      | Beautiful design, fast         | Newer, smaller team            | $0 - $49/mo    |
| **Self-hosted**   | Full control, no cost          | Maintenance burden, setup time | Free           |

<Note>
  Sabo's status badge implementation works with any Better Stack-compatible status page provider. Simply update the iframe URL in `status-badge.tsx`.
</Note>

***
