Skip to main content
RapidDev - Software Development Agency

How to Build Internal dashboard with V0

Build a real-time internal metrics dashboard with V0 using Next.js, Supabase Realtime for live-updating data, and Recharts for sparklines and charts. You'll create configurable metric widgets, an activity feed, and an API endpoint for external metric ingestion — all in about 1-2 hours without touching a terminal.

What you'll build

  • Metric tile Cards that update in real time via Supabase Realtime subscriptions
  • Sparkline and bar charts for KPI trends using Recharts wrapped in client components
  • Activity feed Table showing recent user actions with entity type badges and timestamps
  • Configurable widget system where dashboard layout is stored in the database
  • API endpoint for external metric ingestion with API key authentication
  • Time-range switching with Tabs to view metrics for today, this week, or this month
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate10 min read1-2 hoursV0 PremiumApril 2026RapidDev Engineering Team
TL;DR

Build a real-time internal metrics dashboard with V0 using Next.js, Supabase Realtime for live-updating data, and Recharts for sparklines and charts. You'll create configurable metric widgets, an activity feed, and an API endpoint for external metric ingestion — all in about 1-2 hours without touching a terminal.

What you're building

Non-technical founders need a single pane of glass showing team KPIs, recent activity, and system health without paying for Retool or Datadog. A custom internal dashboard provides exactly this with real-time updates.

V0 generates the metric cards, chart components, and activity feed from prompts. Supabase stores metrics and activity data, with Realtime subscriptions pushing live updates to the browser.

The architecture uses Next.js App Router with a Server Component for the initial data load, client components for live-updating metric tiles and Recharts sparklines, a Supabase Realtime subscription for push updates, and an API route for external metric ingestion with API key authentication.

Final result

An internal dashboard with real-time metric tiles, sparkline charts, activity feed, configurable widgets, and an API endpoint for external metric ingestion.

Tech stack

V0AI Code Generator
Next.jsFull-Stack Framework
Tailwind CSSStyling
shadcn/uiComponent Library
SupabaseDatabase & Realtime
RechartsCharts

Prerequisites

  • A V0 account (Premium recommended for real-time features)
  • A Supabase project (free tier works — Realtime is included)
  • Basic understanding of KPIs and metrics you want to track
  • No advanced coding experience needed

Build steps

1

Set up the project and metrics schema

Open V0 and create a new project. Connect Supabase via the Connect panel. Create the schema for metrics, activity logs, and dashboard widgets.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build an internal metrics dashboard. Create a Supabase schema with:
3// 1. metrics: id (uuid PK), metric_name (text), metric_value (numeric), recorded_at (timestamptz default now()), source (text)
4// 2. activity_log: id (uuid PK), user_id (uuid FK to auth.users), action (text), entity_type (text), entity_id (text), metadata (jsonb), created_at (timestamptz default now())
5// 3. dashboard_widgets: id (uuid PK), widget_type (text check in 'number','sparkline','bar_chart','activity_feed','table'), title (text), query_config (jsonb), position (int), size (text check in 'small','medium','large')
6// Enable Realtime on the metrics table via Supabase Dashboard → Database → Realtime
7// Seed dashboard_widgets with 5 default widgets: Total Users (number), Revenue Today (number), Signups Trend (sparkline), Activity Feed (activity_feed), Top Pages (table)
8// Seed some sample metrics data for testing
9// Add RLS: authenticated users can read all metrics and activity, only service role can insert metrics

Pro tip: Use Design Mode (Option+D) to visually reposition dashboard cards and adjust spacing without burning credits — perfect for getting the layout right.

Expected result: Database schema created with metrics, activity_log, and dashboard_widgets tables. Realtime enabled on the metrics table for live updates.

2

Build the dashboard grid with metric tiles

Create the main dashboard page with a responsive grid of metric Cards. Number widgets show the current value, sparkline widgets show trends, and all tiles update in real time.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build the dashboard at app/dashboard/page.tsx.
3// Requirements:
4// - Server Component that fetches dashboard_widgets and initial metric values
5// - Responsive CSS grid: 3 columns desktop, 2 tablet, 1 mobile
6// - Widget types:
7// 1. 'number' Card: large metric_value, metric_name label, percentage change from previous period
8// 2. 'sparkline' Card: small Recharts LineChart showing last 30 data points (wrap in 'use client')
9// 3. 'bar_chart' Card: Recharts BarChart for categorical data (wrap in 'use client')
10// 4. 'activity_feed': last 10 activity_log entries as a compact list
11// 5. 'table': Table widget showing tabular metric data
12// - Tabs for time range: Today, This Week, This Month
13// - Each metric Card uses Supabase Realtime subscription to update live:
14// Create a 'use client' wrapper component that subscribes to metrics table
15// .on('postgres_changes', { event: 'INSERT', filter: 'metric_name=eq.{name}' })
16// Unsubscribe on cleanup in useEffect return
17// - Use Card for all widgets, Badge for entity types in activity feed

Expected result: The dashboard shows a responsive grid of metric widgets that update in real time when new data arrives in the metrics table.

3

Build the Realtime subscription component

Create the client component that wraps metric tiles with Supabase Realtime subscriptions for live updates.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a RealtimeMetric client component at components/realtime-metric.tsx.
3// Requirements:
4// - 'use client' component that accepts metricName (string) and initialValue (number)
5// - useEffect that subscribes to Supabase Realtime:
6// const channel = supabase
7// .channel(`metric-${metricName}`)
8// .on('postgres_changes', {
9// event: 'INSERT',
10// schema: 'public',
11// table: 'metrics',
12// filter: `metric_name=eq.${metricName}`
13// }, (payload) => setValue(payload.new.metric_value))
14// .subscribe()
15// - Cleanup: return () => { supabase.removeChannel(channel) }
16// - Animate value changes with a brief pulse animation (scale then back)
17// - Show a green dot indicator when connected, red when disconnected
18// - Display the value with appropriate formatting (number, currency, percentage)
19// - Also build RealtimeSparkline: same pattern but appends new values to a data array for the Recharts LineChart
20// - Use Card wrapper with the metric title and a Badge showing 'Live' in green

Pro tip: Always unsubscribe from Supabase Realtime channels in the useEffect cleanup function to prevent memory leaks when components unmount.

Expected result: Metric tiles show live-updating values with pulse animations and a green Live badge. Sparklines append new data points in real time.

4

Build the metric ingestion API

Create an API route that external services call to push metrics into the dashboard. The endpoint uses API key authentication for security.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build a metric ingestion API at app/api/metrics/route.ts.
3// Requirements:
4// - POST handler that accepts JSON body: { metric_name: string, metric_value: number, source?: string }
5// - API key authentication: check x-api-key header against DASHBOARD_API_KEY env var
6// - Validate input with Zod: metric_name non-empty, metric_value is number, source optional string
7// - Insert into metrics table using Supabase service role client
8// - Return 201 with the inserted metric
9// - GET handler for batch retrieval: accepts ?metric_name and ?from query params
10// - Return 401 for invalid API key, 400 for invalid payload
11// - Support batch inserts: accept array of metrics in a single POST
12// - Rate limit: simple in-memory counter (100 requests per minute per API key)
13// - Also build app/api/activity/route.ts for logging activity events with same auth pattern

Expected result: External services can POST metrics via API key-authenticated endpoint. Inserted metrics trigger Realtime updates on the dashboard automatically.

5

Add widget configuration and activity feed

Build the dashboard settings page for configuring widgets and the activity feed component showing recent user actions.

prompt.txt
1// Paste this prompt into V0's AI chat:
2// Build settings and activity feed:
3// 1. app/dashboard/settings/page.tsx — widget configuration:
4// - List of current widgets as Card components with title Input, widget_type Select, size Select
5// - Reorder widgets by dragging (update position field)
6// - Sheet sidebar for advanced query_config editing (which metric_name to display, chart colors)
7// - "Add Widget" Button with Dialog for new widget creation
8// - "Delete Widget" Button with AlertDialog confirmation
9// - Server Actions for widget CRUD and reordering
10//
11// 2. Activity feed component:
12// - Real-time activity feed using Supabase Realtime on activity_log table
13// - Each entry: timestamp, user avatar, action description, entity_type Badge, entity_id link
14// - New entries slide in from the top with animation
15// - Select filter for entity_type (user, order, payment, system)
16// - "View All" link to a full activity log page
17// - Compact mode (for dashboard widget) and expanded mode (for dedicated page)

Expected result: Dashboard layout is configurable via the settings page. The activity feed shows live user actions with type badges and real-time animation.

Complete code

app/api/metrics/route.ts
1import { createClient } from '@supabase/supabase-js'
2import { NextRequest, NextResponse } from 'next/server'
3import { z } from 'zod'
4
5const supabase = createClient(
6 process.env.SUPABASE_URL!,
7 process.env.SUPABASE_SERVICE_ROLE_KEY!
8)
9
10const metricSchema = z.object({
11 metric_name: z.string().min(1).max(100),
12 metric_value: z.number(),
13 source: z.string().max(50).optional(),
14})
15
16const batchSchema = z.union([
17 metricSchema,
18 z.array(metricSchema).min(1).max(100),
19])
20
21export async function POST(request: NextRequest) {
22 const apiKey = request.headers.get('x-api-key')
23 if (apiKey !== process.env.DASHBOARD_API_KEY) {
24 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
25 }
26
27 const body = await request.json()
28 const parsed = batchSchema.parse(body)
29 const metrics = Array.isArray(parsed) ? parsed : [parsed]
30
31 const { data, error } = await supabase
32 .from('metrics')
33 .insert(metrics)
34 .select()
35
36 if (error) {
37 return NextResponse.json({ error: error.message }, { status: 500 })
38 }
39
40 return NextResponse.json({ inserted: data }, { status: 201 })
41}
42
43export async function GET(request: NextRequest) {
44 const apiKey = request.headers.get('x-api-key')
45 if (apiKey !== process.env.DASHBOARD_API_KEY) {
46 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
47 }
48
49 const { searchParams } = new URL(request.url)
50 const name = searchParams.get('metric_name')
51 const from = searchParams.get('from')
52
53 let query = supabase.from('metrics').select('*').order('recorded_at', { ascending: false }).limit(100)
54 if (name) query = query.eq('metric_name', name)
55 if (from) query = query.gte('recorded_at', from)
56
57 const { data, error } = await query
58 if (error) return NextResponse.json({ error: error.message }, { status: 500 })
59
60 return NextResponse.json({ metrics: data })
61}

Customization ideas

Add alerting rules

Define threshold alerts per metric (e.g., error rate > 5%) that send Slack notifications when triggered.

Add user-specific dashboards

Let each team member create their own dashboard layout with different widget selections and arrangements.

Add data export

Build a CSV/JSON export feature for metric data with date range filtering for offline analysis.

Add embedded analytics

Create embeddable iframe widgets that other internal tools can display using a signed token for authentication.

Common pitfalls

Pitfall: Not unsubscribing from Realtime channels on unmount

How to avoid: Always return a cleanup function from useEffect that calls supabase.removeChannel(channel) when the component unmounts.

Pitfall: Using the anon key for the metric ingestion API

How to avoid: Create a dedicated Supabase client in the API route using SUPABASE_SERVICE_ROLE_KEY (never NEXT_PUBLIC_) for server-side metric insertion.

Pitfall: Querying all metrics without time bounds

How to avoid: Always include a time range filter (recorded_at >= cutoff) in metric queries. Use Tabs to let users select today, this week, or this month.

Pitfall: Not enabling Realtime on the metrics table

How to avoid: Enable Realtime on the metrics table via Supabase Dashboard → Database → Realtime → toggle the table on.

Best practices

  • Always unsubscribe from Supabase Realtime channels in the useEffect cleanup to prevent memory leaks.
  • Use SUPABASE_SERVICE_ROLE_KEY (never NEXT_PUBLIC_) for the metric ingestion API route that external services call.
  • Enable Realtime explicitly on the metrics table in Supabase Dashboard — it is not on by default.
  • Use Design Mode (Option+D) to visually adjust dashboard card layout and spacing without burning credits.
  • Add time range filters to all metric queries to prevent unbounded data loading as the table grows.
  • Use connection pooling via Supavisor for serverless API route connections to prevent connection exhaustion.
  • Validate all ingested metrics with Zod in the API route to reject malformed data before insertion.

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a real-time dashboard with Next.js and Supabase. I need a 'use client' component that subscribes to Supabase Realtime postgres_changes on a metrics table, updates the displayed value on INSERT events, and properly cleans up the subscription on unmount. Show me the component code, the Supabase channel setup, and how to animate value changes.

Build Prompt

Build the RealtimeMetric component for an internal dashboard. Create a 'use client' component that accepts metricName and initialValue props. In useEffect, subscribe to Supabase Realtime on the metrics table filtered by metric_name. On INSERT events, update the displayed value with a pulse animation. Show a green Live Badge when connected. Return the cleanup function to removeChannel. Format values as numbers, currency, or percentages based on a format prop.

Frequently asked questions

Can I build this with the free V0 plan?

The core metric tiles and activity feed fit within the free tier. V0 Premium is recommended for the full system with Realtime components and widget configuration.

How do real-time updates work?

Supabase Realtime sends postgres_changes events via WebSocket when rows are inserted into the metrics table. A 'use client' component subscribes to these events and updates the displayed value instantly.

How do I send metrics from external services?

POST JSON to /api/metrics with an x-api-key header. The endpoint validates the payload with Zod and inserts into the metrics table, which triggers Realtime updates on the dashboard.

Can I customize the dashboard layout?

Yes. The dashboard_widgets table stores each widget's type, title, position, and size. The settings page lets you add, remove, reorder, and configure widgets.

What about Supabase Realtime limits?

The free tier supports up to 200 concurrent connections and 2 million Realtime messages per month. For most internal dashboards with small teams, this is more than sufficient.

How do I deploy?

Publish via V0's Share menu. Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in the Vars tab (no NEXT_PUBLIC_ prefix for the ingestion route). Generate a DASHBOARD_API_KEY and distribute to services that push metrics.

Can RapidDev help build a custom dashboard platform?

Yes. RapidDev has built 600+ apps including real-time analytics platforms with custom visualizations, alerting, and embedded reporting. Book a free consultation to discuss your needs.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.