Solopreneur Tools & Stack

How to Build Your Own Analytics Dashboard in 2026

September 1, 2026 · Solopreneur Tools, Analytics, Automation

If you run a lean online business, you do not need a bloated business intelligence platform to understand what is working. You need a simple analytics dashboard that shows the numbers that actually matter: revenue, traffic, conversions, subscribers, costs, and content output.

The goal is not to build a pretty wall of charts. The goal is to make better decisions faster.

For solopreneurs, indie hackers, and automation builders, the best analytics dashboard is usually a lightweight internal tool connected to the systems you already use: Stripe, Gumroad, Shopify, Google Analytics, Plausible, ConvertKit, Airtable, PostgreSQL, CSV exports, or even plain JSON files.

This guide walks through how to build your own analytics dashboard in 2026 using a practical stack: Node.js, SQLite, scheduled scripts, and a simple frontend. You can expand it later, but this version is enough to run a real business.

What Your Analytics Dashboard Should Track

Before choosing tools, decide what decisions the dashboard should help you make. Most solopreneur dashboards should track five categories.

1. Revenue Metrics

2. Traffic Metrics

3. Content Metrics

4. Funnel Metrics

5. Operating Metrics

Start with 8 to 12 numbers. If your dashboard has 50 metrics on day one, you will stop using it.

Recommended Lean Dashboard Stack

You can build this several ways. For a solopreneur dashboard, I prefer boring tools that are cheap, scriptable, and easy to debug.

LayerRecommended ToolWhy
Data collectionNode.js scriptsSimple API calls, easy scheduling, works everywhere
DatabaseSQLiteZero server, portable, good enough for most small businesses
BackendExpress or Next.js API routesFast to build and easy to extend
FrontendHTML, React, or Next.jsChoose based on your comfort level
ChartsChart.js or RechartsClean charts without heavy BI software
Schedulingcron, GitHub Actions, or a VPSRefresh metrics automatically
HostingVercel, Railway, Fly.io, or a small VPSLow-cost deployment options

If you want the absolute simplest version, use SQLite, Node.js, and a static HTML dashboard that reads from a generated JSON file.

Step 1: Define Your Dashboard Schema

Create a small database that stores daily metrics. Do not over-engineer this. A daily snapshot table is enough for most dashboards.

mkdir solopreneur-dashboard
cd solopreneur-dashboard
npm init -y
npm install better-sqlite3 express chart.js dotenv

Create a file called db.js:

const Database = require('better-sqlite3');
const db = new Database('analytics.db');

db.exec(`
  CREATE TABLE IF NOT EXISTS daily_metrics (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    date TEXT NOT NULL UNIQUE,
    revenue_cents INTEGER DEFAULT 0,
    orders INTEGER DEFAULT 0,
    visitors INTEGER DEFAULT 0,
    pageviews INTEGER DEFAULT 0,
    email_subscribers INTEGER DEFAULT 0,
    content_published INTEGER DEFAULT 0,
    ad_spend_cents INTEGER DEFAULT 0,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  );
`);

module.exports = db;

This gives you one row per day. You can add more tables later for product-level revenue, traffic sources, or campaign performance.

Step 2: Pull Data from Your Revenue Sources

Your revenue source might be Stripe, Gumroad, Shopify, Lemon Squeezy, PayPal, or Amazon. The exact API changes, but the pattern is the same:

Here is a simplified example using a generic API pattern. Replace the endpoint and fields with your actual provider.

require('dotenv').config();
const db = require('./db');

async function fetchRevenue(date) {
  const response = await fetch(`https://api.example.com/sales?date=${date}`, {
    headers: {
      Authorization: `Bearer ${process.env.REVENUE_API_KEY}`
    }
  });

  const sales = await response.json();

  const revenueCents = sales.reduce((sum, sale) => sum + sale.amount_cents, 0);
  const orders = sales.length;

  return { revenueCents, orders };
}

async function saveRevenue(date) {
  const { revenueCents, orders } = await fetchRevenue(date);

  db.prepare(`
    INSERT INTO daily_metrics (date, revenue_cents, orders)
    VALUES (?, ?, ?)
    ON CONFLICT(date) DO UPDATE SET
      revenue_cents = excluded.revenue_cents,
      orders = excluded.orders
  `).run(date, revenueCents, orders);

  console.log(`Saved revenue for ${date}`);
}

saveRevenue(process.argv[2] || new Date().toISOString().slice(0, 10));

For Gumroad products, you can also keep this simple by exporting sales CSVs manually at first. Once the dashboard proves useful, automate the integration. If you sell digital products, templates, or prompt packs, you can link readers to your product library at https://opsdesk0.gumroad.com where relevant, but your dashboard should still track actual sales data instead of vanity clicks.

Step 3: Pull Traffic Data

If you use Plausible, Fathom, Google Analytics, or Cloudflare Web Analytics, pull your traffic metrics into the same daily table.

Here is a simplified Plausible-style example:

require('dotenv').config();
const db = require('./db');

async function fetchTraffic(date) {
  const params = new URLSearchParams({
    site_id: process.env.SITE_DOMAIN,
    period: 'day',
    date,
    metrics: 'visitors,pageviews'
  });

  const response = await fetch(`https://plausible.io/api/v1/stats/aggregate?${params}`, {
    headers: {
      Authorization: `Bearer ${process.env.PLAUSIBLE_API_KEY}`
    }
  });

  const data = await response.json();

  return {
    visitors: data.results.visitors?.value || 0,
    pageviews: data.results.pageviews?.value || 0
  };
}

async function saveTraffic(date) {
  const { visitors, pageviews } = await fetchTraffic(date);

  db.prepare(`
    INSERT INTO daily_metrics (date, visitors, pageviews)
    VALUES (?, ?, ?)
    ON CONFLICT(date) DO UPDATE SET
      visitors = excluded.visitors,
      pageviews = excluded.pageviews
  `).run(date, visitors, pageviews);

  console.log(`Saved traffic for ${date}`);
}

saveTraffic(process.argv[2] || new Date().toISOString().slice(0, 10));

Do not worry about perfect attribution at the start. A dashboard that tells you “traffic is up, revenue is flat, and email subscribers are growing” is already useful.

Step 4: Track Manual or Internal Metrics

Some useful metrics do not come from APIs. For example:

You can track these with a simple script.

const db = require('./db');

const date = process.argv[2] || new Date().toISOString().slice(0, 10);
const contentPublished = Number(process.argv[3] || 0);

/db.prepare(`
  INSERT INTO daily_metrics (date, content_published)
  VALUES (?, ?)
  ON CONFLICT(date) DO UPDATE SET
    content_published = content_published + excluded.content_published
`).run(date, contentPublished);

console.log(`Added ${contentPublished} content items for ${date}`);

Remove the accidental slash before db.prepare if copying this into your project. The correct line should be:

db.prepare(`
  INSERT INTO daily_metrics (date, content_published)
  VALUES (?, ?)
  ON CONFLICT(date) DO UPDATE SET
    content_published = content_published + excluded.content_published
`).run(date, contentPublished);

Step 5: Create a Simple API

Now expose your metrics to a frontend. Create server.js:

const express = require('express');
const db = require('./db');

const app = express();
const port = process.env.PORT || 3000;

app.use(express.static('public'));

app.get('/api/metrics', (req, res) => {
  const days = Number(req.query.days || 30);

  const rows = db.prepare(`
    SELECT * FROM daily_metrics
    ORDER BY date DESC
    LIMIT ?
  `).all(days).reverse();

  res.json(rows.map(row => ({
    date: row.date,
    revenue: row.revenue_cents / 100,
    orders: row.orders,
    visitors: row.visitors,
    pageviews: row.pageviews,
    emailSubscribers: row.email_subscribers,
    contentPublished: row.content_published,
    adSpend: row.ad_spend_cents / 100,
    profit: (row.revenue_cents - row.ad_spend_cents) / 100
  })));
});

app.listen(port, () => {
  console.log(`Dashboard running at http://localhost:${port}`);
});

Step 6: Build the Dashboard Frontend

Create a public folder and add index.html:

<!doctype html>
<html>
<head>
  <title>Solopreneur Analytics Dashboard</title>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; margin: 40px; background: #f8fafc; color: #0f172a; }
    .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 32px; }
    .card { background: white; padding: 20px; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
    .metric { font-size: 28px; font-weight: 700; }
    canvas { background: white; padding: 20px; border-radius: 12px; }
  </style>
</head>
<body>
  <h1>Analytics Dashboard</h1>

  <div class="grid">
    <div class="card"><div>Revenue</div><div id="revenue" class="metric">$0</div></div>
    <div class="card"><div>Orders</div><div id="orders" class="metric">0</div></div>
    <div class="card"><div>Visitors</div><div id="visitors" class="metric">0</div></div>
    <div class="card"><div>Profit</div><div id="profit" class="metric">$0</div></div>
  </div>

  <canvas id="revenueChart" height="100"></canvas>

  <script>
    async function loadDashboard() {
      const res = await fetch('/api/metrics?days=30');
      const data = await res.json();

      const totals = data.reduce((acc, row) => {
        acc.revenue += row.revenue;
        acc.orders += row.orders;
        acc.visitors += row.visitors;
        acc.profit += row.profit;
        return acc;
      }, { revenue: 0, orders: 0, visitors: 0, profit: 0 });

      document.getElementById('revenue').textContent = '$' + totals.revenue.toFixed(0);
      document.getElementById('orders').textContent = totals.orders;
      document.getElementById('visitors').textContent = totals.visitors;
      document.getElementById('profit').textContent = '$' + totals.profit.toFixed(0);

      new Chart(document.getElementById('revenueChart'), {
        type: 'line',
        data: {
          labels: data.map(row => row.date),
          datasets: [{
            label: 'Revenue',
            data: data.map(row => row.revenue),
            borderWidth: 2,
            tension: 0.3
          }]
        }
      });
    }

    loadDashboard();
  </script>
</body>
</html>

Run it:

node server.js

Open http://localhost:3000 and you have a working analytics dashboard.

Step 7: Automate Data Refreshes

A dashboard is only useful if it updates automatically. On a server or Mac Mini, use cron:

0 6 * * * cd /path/to/solopreneur-dashboard && node sync-revenue.js
5 6 * * * cd /path/to/solopreneur-dashboard && node sync-traffic.js

This refreshes your revenue and traffic every morning at 6:00 and 6:05. If you prefer GitHub Actions, schedule a workflow:

name: Sync Dashboard Metrics

on:
  schedule:
    - cron: '0 10 * * *'
  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm install
      - run: node sync-revenue.js
        env:
          REVENUE_API_KEY: ${{ secrets.REVENUE_API_KEY }}

Step 8: Add Alerts for Important Changes

The real power comes when your dashboard tells you what needs attention. Add simple alert logic before you add more charts.

const db = require('./db');

const rows = db.prepare(`
  SELECT * FROM daily_metrics
  ORDER BY date DESC
  LIMIT 2
`).all();

const today = rows[0];
const yesterday = rows[1];

if (today && yesterday) {
  const revenueDrop = yesterday.revenue_cents > 0 && today.revenue_cents < yesterday.revenue_cents * 0.5;

  if (revenueDrop) {
    console.log(`Alert: revenue dropped more than 50% today.`);
  }
}

You can send these alerts to Slack, Discord, Telegram, email, or your personal operations assistant. Keep alerts rare. If everything is urgent, nothing is urgent.

What to Avoid

A Practical Dashboard Roadmap

Build in phases instead of trying to create the perfect internal system immediately.

Version 1: Manual Snapshot

Track revenue, traffic, and content output in SQLite or Google Sheets. Update once per day. Time required: 1 to 2 hours.

Version 2: Automated Pulls

Add API scripts for revenue and analytics. Schedule them daily. Time required: 3 to 6 hours.

Version 3: Product and Channel Breakdown

Add tables for products, campaigns, and sources. Time required: 1 weekend.

Version 4: Alerts and Recommendations

Add threshold alerts, anomaly detection, and weekly summaries. Time required: 1 to 2 days.

Most solopreneurs never need more than version 3. The dashboard should help you run the business, not become the business.

FAQ

What is the best tool for building your own analytics dashboard?

The best tool is the simplest stack you can maintain. For most solopreneurs, Node.js, SQLite, Express, and Chart.js are enough to build a useful dashboard without paying for business intelligence software.

How much does it cost to build a custom analytics dashboard?

It can cost $0 to $20 per month if you build it yourself. SQLite is free, Node.js is free, and you can host a small dashboard on a cheap VPS, Railway, Fly.io, or an existing server.

Should I use Google Looker Studio instead?

Google Looker Studio is a good option if your data already lives in Google products. A custom dashboard is better when you need API integrations, private business logic, automation alerts, or metrics from multiple small revenue streams.

How often should my dashboard update?

Daily updates are enough for most solopreneur businesses. Real-time dashboards look impressive, but they are rarely necessary unless you run paid ads, high-volume ecommerce, or time-sensitive trading systems.

What metrics should a solopreneur dashboard show first?

A solopreneur dashboard should show revenue, orders, visitors, conversion rate, email subscribers, content published, ad spend, and profit first. Add more metrics only after those numbers are accurate and useful.

Resources & Tools

Level up your solopreneur stack:

Zero-SaaS Stack Template → The Pragmatic Programmer →

The OpsDesk Dispatch

Weekly: revenue numbers, automation wins, and tools that work. No fluff.