diff --git a/src/app/hashtag/[tag]/page.tsx b/src/app/hashtag/[tag]/page.tsx
index d055d10..650fe0d 100644
--- a/src/app/hashtag/[tag]/page.tsx
+++ b/src/app/hashtag/[tag]/page.tsx
@@ -9,6 +9,7 @@ import { ResearchPanel } from './ResearchPanel'
import { Hash, Clock, Link as LinkIcon, AlertTriangle } from 'lucide-react'
import { formatDistanceToNow } from 'date-fns'
import Link from 'next/link'
+import { AutoRefresh } from '@/components/AutoRefresh'
const ZOMBIE_ZERO_COUNT = parseInt(process.env.ZOMBIE_ZERO_COUNT ?? '1000', 10)
const PRICE_UPDATE_INTERVAL_MINUTES = parseInt(process.env.PRICE_UPDATE_INTERVAL_MINUTES ?? '60', 10)
@@ -126,7 +127,8 @@ export default async function HashtagPage({ params, searchParams }: Props) {
return (
- {/* Header */}
+
+ {/* Header */}}
#{hashtag.displayTag}
diff --git a/src/app/leaderboard/page.tsx b/src/app/leaderboard/page.tsx
index 47b0230..33987e7 100644
--- a/src/app/leaderboard/page.tsx
+++ b/src/app/leaderboard/page.tsx
@@ -5,6 +5,7 @@ import { formatCurrency } from '@/lib/utils'
import { calcFundNav } from '@/lib/pricing'
import Link from 'next/link'
import { Trophy, TrendingUp, TrendingDown, Building2, Users } from 'lucide-react'
+import { AutoRefresh } from '@/components/AutoRefresh'
export const dynamic = 'force-dynamic'
@@ -119,6 +120,7 @@ export default async function LeaderboardPage({
return (
+
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6260335..0da1647 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -6,6 +6,7 @@ import { TrendingUp, Users, Hash, AlertTriangle } from 'lucide-react'
import Link from 'next/link'
import { formatPnl, pnlColor } from '@/lib/utils'
import { formatDistanceToNow } from 'date-fns'
+import { AutoRefresh } from '@/components/AutoRefresh'
const ZOMBIE_ZERO_COUNT = parseInt(process.env.ZOMBIE_ZERO_COUNT ?? '1000', 10)
const PRICE_UPDATE_INTERVAL_MINUTES = parseInt(process.env.PRICE_UPDATE_INTERVAL_MINUTES ?? '60', 10)
@@ -93,7 +94,7 @@ export default async function HomePage() {
return (
- {/* Hero */}
+
}
The{' '}
diff --git a/src/app/positions/page.tsx b/src/app/positions/page.tsx
index 1d7b4eb..e9fdba6 100644
--- a/src/app/positions/page.tsx
+++ b/src/app/positions/page.tsx
@@ -5,6 +5,7 @@ import { redirect } from 'next/navigation'
import { formatCurrency, formatNumber, formatPnl, pnlColor } from '@/lib/utils'
import Link from 'next/link'
import { Coins, ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react'
+import { AutoRefresh } from '@/components/AutoRefresh'
export const dynamic = 'force-dynamic'
@@ -130,6 +131,7 @@ export default async function PositionsPage({
return (
+
Open Positions
diff --git a/src/app/stocks/page.tsx b/src/app/stocks/page.tsx
index 7a5b5f3..6483031 100644
--- a/src/app/stocks/page.tsx
+++ b/src/app/stocks/page.tsx
@@ -2,6 +2,7 @@ import { prisma } from '@/lib/prisma'
import { formatCurrency, pnlColor } from '@/lib/utils'
import Link from 'next/link'
import { ArrowUp, ArrowDown, ArrowUpDown, BarChart2, Building2 } from 'lucide-react'
+import { AutoRefresh } from '@/components/AutoRefresh'
import { formatDistanceToNow } from 'date-fns'
import { calcFundNav } from '@/lib/pricing'
@@ -197,7 +198,8 @@ export default async function StocksPage({ searchParams }: PageProps) {
return (
- {/* Header */}
+
+ {/* Header */}}
diff --git a/src/components/AutoRefresh.tsx b/src/components/AutoRefresh.tsx
new file mode 100644
index 0000000..fc7ddb4
--- /dev/null
+++ b/src/components/AutoRefresh.tsx
@@ -0,0 +1,28 @@
+'use client'
+
+import { useRouter } from 'next/navigation'
+import { useEffect } from 'react'
+
+/**
+ * Silently refreshes all server-component data on the current page by calling
+ * router.refresh() on an interval and whenever the tab regains focus.
+ *
+ * Drop this anywhere inside a server-component page — it renders nothing.
+ */
+export function AutoRefresh({ intervalMs = 30_000 }: { intervalMs?: number }) {
+ const router = useRouter()
+
+ useEffect(() => {
+ const id = setInterval(() => router.refresh(), intervalMs)
+
+ const onFocus = () => router.refresh()
+ document.addEventListener('visibilitychange', onFocus)
+
+ return () => {
+ clearInterval(id)
+ document.removeEventListener('visibilitychange', onFocus)
+ }
+ }, [router, intervalMs])
+
+ return null
+}