Building Monitoring Dashboards with Next.js

The @unblind/nextjs package provides a complete solution for building customer-facing monitoring dashboards in your Next.js application. It includes a secure server-side proxy, React hooks for data fetching, and pre-built chart components.


Installation

Install the Next.js package, which includes both client and server components:

npm install @unblind/nextjs

Setup

1. Configure the Proxy Handler

The Unblind handler acts as a secure proxy between your frontend and the Unblind API. It automatically injects authentication and tenant context into requests.

1. Configure proxy/middleware

Create or update your proxy.ts or middleware.ts file:

Code

import { unblindProxy } from '@unblind/nextjs/server'
import { NextRequest, NextResponse } from 'next/server'

export async function proxy(request: NextRequest) {
  // Handle Unblind API requests
  if (request.nextUrl.pathname.startsWith('/api/unblind')) {
    // Extract tenant ID from your auth system
    // This could be a user ID, organization ID, etc.
    const tenantId = request.cookies.get('userId')?.value

    // The handler proxies requests to Unblind API with proper auth
    const response = await unblindProxy(request, tenantId)
    if (response) {
      return response
    }
  }

  return NextResponse.next()
}

export const config = {
  matcher: '/api/unblind/:path*',
}

2. Environment Variables

Set your Unblind API key in your .env:

.env

UNBLIND_API_KEY=your_api_key_here

2. Add Unblind css

Add Unblind styles to your global tailwind file.

globals.css

@import "@unblind/react/styles.css";
@import "tailwindcss";
/* ... */

4. Wrap Your App with UnblindProvider

The UnblindProvider provides configuration to all Unblind components and hooks.

app/layout.tsx

import { UnblindProvider } from '@unblind/nextjs'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <UnblindProvider>
          {children}
        </UnblindProvider>
      </body>
    </html>
  )
}

Build Your Dashboard

Use the Timeseries component to display metrics:

app/dashboard/page.tsx

'use client'

import { Timeseries } from '@unblind/nextjs'

export default function Dashboard() {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div>
        <h3>CPU Usage</h3>
        <Timeseries
          metrics={['system.cpu.utilization']}
          type="area"
        />
      </div>

      <div>
        <h3>Memory Usage</h3>
        <Timeseries
          metrics={['system.memory.usage']}
          type="line"
        />
      </div>

      <div>
        <h3>Request Duration</h3>
        <Timeseries
          metrics={['http.server.duration']}
          groupBy={['http.route']}
          operator="p95"
          type="bar"
        />
      </div>

      <div>
        <h3>Error Rate</h3>
        <Timeseries
          metrics={['http.server.request.count']}
          attributes={{
            'http.status_code': ['500', '503', '504']
          }}
          type="line"
        />
      </div>
    </div>
  )
}

Advanced Features

Dynamic Time Ranges

Allow users to change the time range dynamically:

app/dashboard/page.tsx

'use client'

import { useState } from 'react'
import { UnblindScope, Timeseries, TimeRange } from '@unblind/nextjs'

export default function Dashboard() {
  const [timeRange, setTimeRange] = useState<TimeRange>('6h')

  return (
    <div>
      <select
        value={timeRange}
        onChange={(e) => setTimeRange(e.target.value as TimeRange)}
      >
        <option value="1h">Last Hour</option>
        <option value="6h">Last 6 Hours</option>
        <option value="24h">Last 24 Hours</option>
        <option value="7d">Last 7 Days</option>
      </select>

      <UnblindScope timeRange={timeRange}>
        <div className="grid grid-cols-2 gap-4">
          <Timeseries metrics={['system.cpu.utilization']} />
          <Timeseries metrics={['system.memory.usage']} />
        </div>
      </UnblindScope>
    </div>
  )
}

Using Hooks for Custom Components

For more control, use hooks directly:

'use client'

import { useTimeseries, useMetrics } from '@unblind/nextjs'

export function CustomMetricCard() {
  // Get list of available metrics
  const { list, isLoading: metricsLoading } = useMetrics()

  // Fetch timeseries data
  const { data, isLoading, hasError } = useTimeseries({
    queries: [
      {
        metrics: ['system.cpu.utilization'],
        operator: 'avg',
        groupBy: ['host.name'],
      }
    ],
    timeRange: '1h',
  })

  if (isLoading || metricsLoading) return <div>Loading...</div>
  if (hasError) return <div>Error loading data</div>

  const { series, times, metadata } = data

  return (
    <div>
      {series.map((serie) => (
        <div key={serie.metric}>
          <h4>{metric.name}</h4>
          <p>Latest: {serie.values[serie.values.length - 1]}</p>
        </div>
      ))}
    </div>
  )
}

Fetching Logs

Use the useLogs hook for log data with infinite scroll:

'use client'

import { useLogs } from '@unblind/nextjs'

export function LogViewer() {
  const { logs, isLoading, hasNextPage, fetchNextPage } = useLogs({
    timeRange: '1h',
    filters: [
      { name: 'severity', value: 'ERROR' },
      { name: 'service.name', value: 'api' }
    ]
  })

  return (
    <div>
      {logs.map((log, i) => (
        <div key={i}>
          <span>{new Date(log.timestamp).toISOString()}</span>
          <span>{log.severity_text}</span>
          <span>{log.body}</span>
        </div>
      ))}

      {hasNextPage && (
        <button onClick={() => fetchNextPage()}>
          Load More
        </button>
      )}
    </div>
  )
}

Usage Analytics

Track and display usage metrics:

'use client'

import { useUsage } from '@unblind/nextjs'

export function UsageChart() {
  const { usage, isLoading } = useUsage({
    timeRange: '30d'
  })

  if (isLoading) return <div>Loading...</div>

  return (
    <div>
      {usage.map((day) => (
        <div key={day.date}>
          <span>{day.date}</span>
          <span>Metrics: {day.metrics.units}</span>
          <span>Logs: {day.logs.units} ({day.logs.bytes} bytes)</span>
        </div>
      ))}
    </div>
  )
}

Refresh Data

Use the useRefresh hook to manually refresh all queries:

'use client'

import { useRefresh } from '@unblind/nextjs'

export function RefreshButton() {
  const refresh = useRefresh()

  return (
    <button onClick={refresh}>
      Refresh Dashboard
    </button>
  )
}

Next Steps

Was this page helpful?