Skip to content

Commit 0935d16

Browse files
committed
fix: render LivePulse skeleton during SSR to prevent hydration #418
LivePulse previously returned null when data hadn't loaded, causing an empty <div> in SSR that gained full content after client-side fetch. Now renders the complete panel structure with placeholder values ("—") so the DOM tree is identical between server and client. Made-with: Cursor
1 parent e21cee3 commit 0935d16

1 file changed

Lines changed: 10 additions & 12 deletions

File tree

components/LivePulse.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,30 +45,28 @@ export default function LivePulse() {
4545
return () => clearInterval(t);
4646
}, []);
4747

48-
if (!data) return null;
49-
5048
const items = [
51-
{ label: "Agents", value: data.agents, icon: "🤖" },
52-
{ label: "Love Letters", value: data.confessions, icon: "💌" },
53-
{ label: "Couples", value: data.couples, icon: "💕" },
54-
{ label: "Events", value: data.events, icon: "⚡" },
49+
{ label: "Agents", value: data?.agents ?? 0, icon: "🤖" },
50+
{ label: "Love Letters", value: data?.confessions ?? 0, icon: "💌" },
51+
{ label: "Couples", value: data?.couples ?? 0, icon: "💕" },
52+
{ label: "Events", value: data?.events ?? 0, icon: "⚡" },
5553
];
5654

5755
return (
5856
<div className="glass rounded-2xl p-4 sm:p-5" role="region" aria-label="Live platform statistics" aria-live="polite">
5957
<div className="flex items-center justify-between mb-3">
6058
<div className="flex items-center gap-2">
6159
<span className="relative flex h-2 w-2">
62-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
63-
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500" />
60+
<span className={`animate-ping absolute inline-flex h-full w-full rounded-full ${data ? 'bg-green-400' : 'bg-white/20'} opacity-75`} />
61+
<span className={`relative inline-flex rounded-full h-2 w-2 ${data ? 'bg-green-500' : 'bg-white/30'}`} />
6462
</span>
65-
<span className="text-[10px] uppercase tracking-[0.2em] text-white/50 font-bold">Live</span>
63+
<span className="text-[10px] uppercase tracking-[0.2em] text-white/50 font-bold">{data ? 'Live' : 'Loading'}</span>
6664
</div>
6765
<div className="text-[10px] text-white/60">
68-
{data.active_last_hour > 0 && (
66+
{data && data.active_last_hour > 0 && (
6967
<span>{data.active_last_hour} active now</span>
7068
)}
71-
{data.last_activity && (
69+
{data?.last_activity && (
7270
<span className="ml-2" suppressHydrationWarning>
7371
last activity: {timeAgo(data.last_activity)}
7472
</span>
@@ -79,7 +77,7 @@ export default function LivePulse() {
7977
{items.map(it => (
8078
<div key={it.label} className="text-center">
8179
<div className="text-lg sm:text-xl mb-1">{it.icon}</div>
82-
<div className="text-sm sm:text-base font-bold text-white/80 tabular-nums">{it.value.toLocaleString()}</div>
80+
<div className="text-sm sm:text-base font-bold text-white/80 tabular-nums">{data ? it.value.toLocaleString() : '—'}</div>
8381
<div className="text-[9px] text-white/60 uppercase tracking-wider">{it.label}</div>
8482
</div>
8583
))}

0 commit comments

Comments
 (0)