How I Built a Personal Ops Dashboard with Next.js, Supabase, and AI
A look inside the private Next.js dashboard I built to manage 13 live sites — site status monitoring, personal AI, offline-capable notes, multi-tenant architecture, and more.
Why I Built It#
When you're running multiple web projects simultaneously — each with its own domain, database, analytics, and user base — managing everything becomes its own part-time job. I was constantly switching between Vercel, Supabase, Google Analytics, email, and three different todo apps. Nothing was connected.
So I built a private ops dashboard at wonsukchoi.co — a single Next.js app that centralizes everything I need to manage my projects. This post covers what's in it, the technical decisions behind it, and what I learned.
What's Inside#
The dashboard is organized into five tab groups. Here's what each one does.
System: Site Status Monitor#
The most immediately useful feature monitors all 13 of my live sites on a 30-second polling interval, checking two things per site:
HTTP reachability — a HEAD request to each domain with a 5-second timeout. Any response is "up"; a timeout or network error is "down."
Supabase DB health — a HEAD request to each project's PostgREST endpoint (https://<ref>.supabase.co/rest/v1/). Since multiple sites share the same Supabase instance, I deduplicate by project ref — 13 sites resolve to just 5 unique DB checks. Any HTTP response, even a 401, confirms the DB is reachable.
The widget also tracks domain expiry dates with color-coded urgency: green beyond 60 days, yellow at 30–60, orange at 14–30, red under 14. Dates are editable inline and persisted to localStorage.
Tools: Personal AI#
A private chat interface connected to OpenRouter — Claude Sonnet 4.5, Claude Opus 4.5, GPT-4o, GPT-4o Mini, and Gemini 2.0 Flash. Model switching is live mid-conversation. Responses stream token-by-token using the Vercel AI SDK.
Having model selection in-context is genuinely useful: draft quickly with a fast model, switch to Opus for anything that needs deeper reasoning.
Content: Journal and Notes#
Journal is an AI-assisted daily writing space. Notes is a persistent reference system — nested category hierarchy, optional per-note password protection, bulk move/delete, image uploads to Supabase Storage, and an offline sync queue so writes aren't lost when the network drops.
Offline sync is handled by a custom useOfflineSync hook that queues mutations in IndexedDB and replays them on reconnect. It's the kind of feature that's invisible when it works — and was the hardest to get right.
Work: Todos, Calendar, Subscriptions#
Todo tracks tasks with due dates, priorities, and categories. Overdue items surface in the tab badge. Calendar gives a monthly view of scheduled items across configurable timezones. Subscriptions is a lightweight financial tracker — cost per subscription, monthly vs annual, running total at a glance.
Site: Messages, Blog, Subscribers, Analytics#
Centralizes the inbound side: contact form submissions, newsletter subscribers, and analytics. The Blog tab lets me write and publish posts — including this one — without opening a separate CMS.
Technical Decisions#
Next.js App Router#
The dashboard uses the App Router with a deliberate split between server and client components. The initial page load is a server component that fetches the user's full data set from Supabase using the service role client — no loading spinners on first render, no client-side data fetching waterfalls.
Mutations go through server actions: form submissions, note updates, todo toggles, blog publishes. This eliminates an entire layer of API route boilerplate and keeps the data layer co-located with the components that use it.
Supabase for Auth, DB, and Storage#
Supabase handles authentication (email/password, session management), the primary PostgreSQL database, and file storage for note images and blog assets.
Row-Level Security is the core of the auth model. Every table enforces read/write policies at the database layer — not in application code. Even if a server action has a bug, the DB won't return data it shouldn't. This is the right place to enforce authorization.
Types are generated directly from the database schema via the Supabase CLI and imported throughout the app. A schema change immediately surfaces as a TypeScript error in consuming code — not a runtime crash in production.
Multi-Tenant Architecture#
The same codebase that powers this dashboard is the foundation for the ExpertSapiens platform — a multi-tenant directory with 7 live tenant sites (expertsapiens.com, airlinkee.com, mrvisakorea.com, seoultranslate.com, and others). Tenant context is resolved from the request hostname at the middleware layer and flows through the app as a typed config object. Adding a new tenant requires a config entry, not a new codebase or deployment.
This is the architecture pattern I use across all new projects now — single codebase, environment-resolved tenant, shared DB with RLS-based data isolation.
TypeScript End-to-End#
Everything is typed: server actions, API routes, component props, Supabase query results. The discipline pays off most in refactors — rename a column in the DB, regenerate types, and every usage that's now broken is highlighted immediately.
What I Would Do Differently#
Split the dashboard shell earlier — the main component grew past 6,000 lines. It works, but feature-level module splits from the start would make it far easier to navigate.
Service worker for offline sync — the current IndexedDB-based approach works well but a service worker with background sync would handle more edge cases, especially on flaky mobile connections.
Time-series store for analytics — basic Supabase queries are fine for low-traffic dashboards. For anything richer, a dedicated time-series store (Tinybird, ClickHouse) would be worth it.
What This Demonstrates#
A production-quality private dashboard requires the same skill set as any client-facing SaaS: real-time data, auth, background jobs, file uploads, offline resilience, and clean UI. The difference is that the only user is me, which means faster iteration and more opinionated decisions.
If you're looking for a developer who can take a project from blank repo to production — Next.js App Router, Supabase backend, TypeScript throughout, AI integrations, multi-tenant architecture — I'd be glad to talk.
फ्रीलांस
क्या इसमें मदद चाहिए?
मैं migration, नए प्रोडक्ट और performance fixes में मदद करता हूँ।
संपर्क करें →