I spent this week doing something developers are often told not to do: I rewrote a perfectly functional feature.
The user dashboard for Pantero—the offline AI companion I'm building from my FUTO hostel room—worked. It showed the waitlist position. It displayed the referral link. It did its job.
But it felt wrong.
Slow. Clunky. Like it was fighting me every time I opened it. So I tore it down and rebuilt it in Next.js. Here's what I learned.
The Problem with the Old Dashboard
The original dashboard was built early in the project, back when I was still thinking in plain HTML and scattered JavaScript. It was a single, massive component with:
- Mixed concerns (data fetching, UI rendering, state management all jammed together)
- Poor mobile responsiveness
- No loading states—just blank screens while data loaded
- Hardcoded values that made updates painful
It "worked" for 10 users. At 187+ waitlisters, it was starting to show its cracks.
The Rebuild: Goals
I set three clear goals for the new dashboard:
- Speed: Load fast, even on 3G.
- Clarity: Users should instantly understand their waitlist position, referral stats, and next steps.
- Maintainability: I should be able to add new features without breaking everything.
The Tech Approach
Since Pantero is already a Next.js app, I leaned into the framework's strengths:
| Feature | Implementation |
|---|---|
| Data Fetching | Server Components for initial load, client-side fetch for real-time updates |
| State Management | React Context for global user state, useState for local UI |
| Loading States |
Suspense boundaries with skeleton loaders |
| Styling | Tailwind CSS (already in the project) |
The Hardest Part: Balancing Server and Client
The dashboard needs to show:
- Static data (user's email, join date) → perfect for Server Components
- Dynamic data (current waitlist position, referral count) → needs client-side updates
I kept running into hydration mismatches because I was mixing server-rendered content with client-updated numbers. The fix was simple once I understood it: keep the dynamic parts isolated in client components and pass static data as props from the server.
`jsx
// Server Component (app/dashboard/page.tsx)
export default async function DashboardPage() {
const user = await getUser(); // server-side fetch
return (
<div>
<h1>Welcome, {user.email}</h1>
<WaitlistPosition initialPosition={user.position} />
<ReferralStats initialCount={user.referralCount} />
</div>
);
}
// Client Component (components/WaitlistPosition.tsx)
'use client';
export function WaitlistPosition({ initialPosition }) {
const [position, setPosition] = useState(initialPosition);
// Refresh every 30 seconds
useEffect(() => {
const interval = setInterval(async () => {
const newPosition = await fetchPosition();
setPosition(newPosition);
}, 30000);
return () => clearInterval(interval);
}, []);
return <p>Your position: #{position}</p>;
}
`
This pattern saved me so much headache.
Before and After
Here's what the dashboard looked like before the rework:

And here's the new version (still a work in progress):

The new dashboard:
Shows waitlist position prominently
Displays referral link with a one-click copy button
Includes a simple referral count tracker
Has a clear call-to-action to join the WhatsApp Channel
Loads with skeleton states instead of blank screens
What's Still Missing
Animated transitions between states
Dark mode toggle (coming soon)
Real-time referral notifications
Mobile bottom sheet for quick actions
The Bigger Picture
This dashboard rework isn't just about making things pretty. It's the foundation for everything coming next:
Verified credential generation
AI mentorship history
Community leaderboards
If the dashboard felt clunky at 187 users, it would have collapsed at 1,000. Now I have a solid base to build on.
Current Pantero Metrics
Metric Count
Waitlist 187+
Countries Nigeria, Ghana, Kenya
Dashboard rebuild time 4 days
Bugs fixed during rebuild 7
New bugs created 3 (working on it)
Join the Waitlist
If you're curious about the offline AI companion I'm building through all this Next.js chaos:
🔗 pantero.vercel.app
The waitlist is open. The dashboard is better. The build continues.
Have you ever rewritten a feature that "worked" because you knew it could be better? How did it go? Let's swap war stories in the comments.
Top comments (0)