Article Information
Category: Development
Published: 8 March, 2026
Author: Chris de Gruijter
Reading Time: 12 min
Tags

Building a Unified Marketing Dashboard: From 3 Separate Apps to One Multi-Tenant Platform
Published: 8 March, 2026
Managing multiple client websites means managing multiple dashboards — one for leads, one for SEO, one for analytics, each with its own login, its own deployment, and its own maintenance burden. After running three separate Nuxt dashboards and a standalone SEO dashboard for months, I finally consolidated everything into a single, multi-tenant Next.js platform.
The result? One URL, one login, and every client sees exactly what they need — SEO rankings, incoming leads, analytics, CRM, invoicing, and more. Here's how I built it, the architectural decisions involved, and what I learned along the way.
The Problem: Dashboard Sprawl
At Webfluentia, I manage digital marketing for three Dutch construction and renovation companies. Each had its own lead capture system writing JSON files to Cloudflare R2 buckets, its own Nuxt 3 dashboard deployed on Cloudflare Pages, and a separate React-based SEO dashboard pulling data from Supabase.
That meant four separate apps, four separate deployments, four sets of environment variables, and zero shared code. When I fixed a bug in one dashboard, I had to copy the fix to the other two. When I added a feature, same story. It was unsustainable.
- 3 Nuxt lead dashboards — one per client, each with its own Cloudflare R2 credentials
- 1 React SEO dashboard — pulling from a consolidated Supabase database
- No authentication — the SEO dashboard was a static export with no login
- No shared components — every dashboard was a standalone project
The Solution: One Dashboard to Rule Them All
I designed a single Next.js 16 application that would replace all four dashboards. The key requirements were:
- Authentication — email/password login with Supabase Auth
- Multi-tenancy — admin sees all clients, client users see only their own data
- Section-level access — each client can be granted access to specific sections (SEO, leads, analytics, etc.)
- Unified data layer — SEO data from Supabase, leads from R2, all through one interface
- SSR on Cloudflare Workers — server-side rendering for auth protection and API routes
Architecture Deep Dive
Authentication Flow
Authentication uses Supabase Auth with the @supabase/ssr package for server-side session management. When a user hits the dashboard, a Next.js proxy checks for a valid session. No session? Redirect to /login. Valid session? Fetch the user's profile from public.profiles to determine their role and assigned clients.
I chose email/password auth without self-registration — I create accounts manually for each client. This keeps things simple and secure for a B2B context where I know every user personally.
Multi-Tenant Data Isolation
The database uses a client_configs table that stores per-client settings: name, domain, color theme, and R2 bucket credentials. A separate user_client_access table maps users to clients with an allowed_sections array — so one client might have access to SEO and leads, while another also gets analytics and CRM.
All SEO data lives in a consolidated seo schema with a client_id column. Supabase Row Level Security (RLS) ensures queries only return data for the authenticated user's assigned clients. R2 credentials are stored server-side in client_configs and never exposed to the browser — API routes fetch them using the service role key, which bypasses RLS.
The Leads Integration
Each client's website has a Cloudflare Worker that captures form submissions and writes them as JSON files to an R2 bucket. The dashboard reads these files through server-side API routes that sign AWS v4 requests to R2. The flow looks like this:
- Browser requests
/api/leads/files?client=gevelpro - API route authenticates the user and checks they have access to this client's leads section
- API route fetches R2 credentials from
client_configsusing the service role key - API route signs an AWS v4 request to the correct R2 bucket
- Returns the file list (with KPI calculations) to the browser
What the Dashboard Includes Today
What started as a simple consolidation project grew into a full-featured marketing platform. Over 33 phases of development, the dashboard now includes:
- SEO Dashboard — keyword tracking, ranking history, content performance, PSI audits, task queue
- Leads Dashboard — KPI stats, search/filter, date ranges, lead detail viewer
- CRM — contacts, pipeline with kanban board, deals, quotes, invoices, email tracking
- Analytics — self-hosted cookie-free tracking with pageview and event data
- Google Ads — campaign metrics, search terms, ROAS tracking
- Reports — cross-section client reports with 4 templates
- Payments — Stripe checkout integration, payment links, transaction history
- Notifications — alert rules, real-time notification system
- AI Insights — anomaly detection, rule-based and LLM-powered recommendations
- Social — Meta Graph API integration for Facebook and Instagram metrics
- Work Log — time tracking with category breakdown
- i18n — full English and Dutch language support
Technical Stack
- Framework: Next.js 16 with React 19
- UI: Tremor UI + Tailwind CSS + Recharts
- Auth: Supabase Auth with @supabase/ssr
- Database: Supabase (PostgreSQL with RLS)
- Object Storage: Cloudflare R2 (via AWS v4 signed requests)
- Deployment: Cloudflare Workers via @opennextjs/cloudflare
- Email: SendGrid for transactional emails
- Payments: Stripe for checkout and webhooks
Key Lessons Learned
1. Start with Auth and Access Control
I made the mistake of building features first and bolting on auth later. This meant retrofitting every component to be tenant-aware. If I were starting over, I'd implement authentication, profile management, and client-scoped data access in the first sprint — everything else is easier when you know who the user is and what they can see.
2. Section-Level Access is Worth the Complexity
Instead of just "admin or client," I built a user_client_access table with an allowed_sections array. This means I can grant a client access to SEO and leads but not CRM or analytics. It took an extra day to implement, but it's been invaluable for progressive rollouts — I can enable new sections per-client as they become production-ready.
3. SSR on Cloudflare Workers Has Gotchas
Deploying Next.js on Cloudflare Workers via @opennextjs/cloudflare mostly works great, but there are edge cases. Cold starts can be slower than Vercel. Some Node.js APIs aren't available in the Workers runtime. And the build process is more involved — you need wrangler.jsonc and open-next.config.ts configuration files that Vercel doesn't require. That said, the pricing is unbeatable for a low-traffic B2B dashboard.
4. R2 Credentials Should Never Touch the Client
I store R2 access keys in the client_configs Supabase table, behind RLS that blocks all browser-side queries. Only the server-side API routes (using the service role key) can read them. This is a critical security pattern — R2 credentials in the browser would give anyone full access to client lead data.
The Numbers
- 4 apps → 1 app — 75% reduction in deployment complexity
- 33 phases of iterative development
- 3 clients served from a single URL
- 12 sections — from SEO to CRM to AI insights
- 2 languages — full English and Dutch i18n
- $0 additional hosting cost — Cloudflare Workers free tier covers the traffic
Conclusion
Consolidating multiple dashboards into a single multi-tenant platform was one of the best investments I've made. The upfront effort was significant — 33 phases over several months — but the ongoing maintenance burden dropped dramatically. One codebase, one deployment pipeline, one set of shared components. Every new feature benefits all clients immediately.
If you're running separate dashboards for multiple clients, consider whether a unified platform makes sense. The auth and access control complexity pays for itself quickly in reduced maintenance and faster feature development.
Frequently Asked Questions
Why Next.js instead of Nuxt for the unified dashboard?
The existing SEO dashboard was already built with React and Tremor UI components. Rewriting it in Vue/Nuxt would have been a step backward. Next.js 16 with React 19 also has excellent Cloudflare Workers support through @opennextjs/cloudflare, making the deployment story straightforward.
How do you handle client data isolation?
All SEO data uses a client_id column with Supabase Row Level Security (RLS) policies. Leads data is isolated at the R2 bucket level — each client has its own bucket. The user_client_access table controls which users can see which clients' data.
Can clients access the dashboard themselves?
Yes. I create email/password accounts for each client user through Supabase Auth. They log in at dashboard.webfluentia.agency and see only their own data — the sections I've granted them access to. Admins see all clients with a switcher dropdown.
What does the self-hosted analytics section look like?
The analytics section uses a custom cookie-free tracking script deployed on each client's website. It captures pageviews and events without cookies, making it GDPR-friendly by design. The dashboard shows traffic trends, top pages, referrers, and device breakdowns.