Vibe Code a SaaS Dashboard That Doesn't Crawl

Build a dashboard with AI — layout, charts, and the query patterns that stop it grinding to a halt once you have real data.

C
CodeIllusion Team
#vibe-coding #dashboard #saas #tutorial
Vibe Code a SaaS Dashboard That Doesn't Crawl

Dashboards are one of the best AI coding projects: conventional layout, visual output you can verify by looking, and a small security surface if it’s internal.

They’re also where people first discover that code which works with test data falls over with real data. That’s the part worth planning for.

What You’re Building

A dashboard for the project tracker from the CRUD tutorial: summary stats along the top, a chart of activity over time, a table of recent items, and a date-range filter.

Layout First, Data Second

Build the shell before wiring anything up. It’s faster to fix layout when there’s no data logic tangled into it.

Next.js App Router, TypeScript, Tailwind. Build a dashboard layout at /dashboard: fixed sidebar with nav, top bar with user menu, main content area. Responsive — sidebar collapses to a drawer under 768px.

In the content area, place four stat cards in a row, a full-width chart below, and a table below that. Use placeholder data for now. Keep it to as few files as possible.

Check it on a phone before continuing. Dashboard layouts break on mobile more than anything else, and it’s much cheaper to fix now.

Commit.

Stats — Aggregate in the Database

This is the step that determines whether your dashboard survives real usage.

The wrong version, which you’ll get by default:

const { data } = await supabase.from('projects').select('*')
const active = data.filter(p => p.status === 'active').length
const done   = data.filter(p => p.status === 'done').length

Four stats, one query that fetches every row you own, then counts in JavaScript. Fine at 50 rows. At 50,000 you’re transferring the whole table to compute four numbers.

Ask for the right thing:

Replace the stat calculations with database aggregation. Write a Postgres function that returns total count, active count, completed count, and count created in the last 30 days for a given user ID, in one query. Call that instead of fetching rows. Filter by the authenticated user.

Same for the chart:

The activity chart must aggregate in SQL — group by day, count per day, for the selected range. Do not fetch individual rows and group them in JavaScript.

Charts

Add a line chart of projects created per day for the last 30 days using Recharts. Responsive container. Format dates on the x-axis as short month and day. Include a loading skeleton and an empty state for when there’s no data.

Two things to request explicitly, because they’re always missing:

Empty states. A chart with no data renders as an unlabelled empty box that looks broken.

Loading skeletons. Without them the layout jumps as each section arrives.

Every dashboard section needs three states: loading skeleton, empty state with helpful text, and error state with a retry.

Filters Without Refetching Everything

Add a date range filter with presets — 7 days, 30 days, 90 days, custom. Changing it must re-run the aggregation query server-side with the new range, not filter already-loaded data. Put the range in the URL as a search param so it survives refresh and can be shared.

The URL detail is worth having. It makes dashboard states linkable, which people want immediately.

The Security Bit

Dashboards leak in a specific way: aggregate queries feel anonymous, so ownership filters get skipped.

A count that includes other users’ rows is still a data leak — it tells you how many records exist. Worse, a “recent activity” table without an ownership filter shows the actual content.

Every query on this dashboard, including aggregates and the Postgres function, must filter by the authenticated user’s ID. List each query and show me where it filters.

If the dashboard is admin-only, that’s a different model — see vibe code an admin panel for role checks.

Performance

Once it works, these are the requests that keep it working:

Add database indexes on the columns used for filtering and sorting — user_id, created_at, status. Show me the SQL.

Cache the stat aggregation for 60 seconds. Dashboard numbers don’t need to be real-time.

Stream the sections independently with Suspense so the stats render before the chart finishes loading.

That last one changes the feel considerably — the page becomes useful immediately rather than after the slowest query.

The Checklist

  • All aggregation happens in SQL, not JavaScript
  • Every query filters by authenticated user
  • Indexes on filter and sort columns
  • Loading, empty, and error states on every section
  • Filter state in the URL
  • Responsive — tested on an actual phone
  • Stats cached
  • Tested with a few thousand rows, not five

That final point is worth doing deliberately. Generate 5,000 fake rows and reload. Problems that don’t exist with test data appear immediately.

Write a SQL script that inserts 5000 realistic fake projects for my user ID, spread across the last 90 days.

Next

A dashboard reads data. The natural next steps are an admin panel that manages it across users, or a REST API that exposes it to other apps.

Related reading:

Frequently Asked Questions

What's the best chart library for an AI-built dashboard? +

Recharts is the most reliable choice — heavily used, so AI reproduces it accurately, and it works well with React. Tremor is a good alternative if you want dashboard-specific components. Avoid asking for D3 unless you need custom visualisations; the generated code is usually harder to maintain.

Why does my AI-generated dashboard get slow? +

Almost always because it fetches all rows and aggregates in JavaScript. That's fine with 50 records and unusable with 50,000. Aggregation belongs in the database — ask for SQL that returns computed totals, not raw rows.

Should dashboard data be fetched server-side or client-side? +

Server-side for the initial load, so the page renders with data and the queries stay off the client. Use client-side fetching only for interactive filters, and make those hit an API route that re-runs the aggregation in the database.

How do I stop a dashboard leaking other users' data? +

Every aggregate query needs the same ownership filter as a regular query. This gets missed more often on dashboards because a total looks anonymous — but a count that includes other users' rows is still a leak.

Tagged:

#vibe-coding #dashboard #saas #tutorial

Enjoyed this article?

Get more AI tool picks, coding tutorials, and no-code automation guides every week. No spam, ever.

Found this useful? Share it:

More in AI Coding Tools