Next.js Discord

Discord Forum

Server Components vs TanStack Query for Dashboard - Which Pattern for Next.js 16?

Unanswered
rob posted this in #help-forum
Open in Discord
robOP
I'm building a property management dashboard (Next.js 16.0.0, React 19, App Router) and trying to decide between two data fetching patterns.

Current Implementation (Server Components):

// app/(dashboard)/page.tsx
export const dynamic = 'force-dynamic'

export default async function Home() {
  const [stats, hasAIAccess] = await Promise.all([
    getDashboardStats(),
    canUseAIFeatures(),
  ])

  return (
    <DashboardOverview stats={stats}>
      <Suspense fallback={<Skeleton />}>
        <DeferredTenants />
      </Suspense>
    </DashboardOverview>
  )
}


Performance

* First load: ~650ms (Sydney → US East database)
* Uses React cache() for auth deduplication
* Zero client-side JS for data fetching

Considering Adding: TanStack Query for client-side caching

* Goal: Faster subsequent navigations (650ms → 100ms)
* Trade-off: Slower first load due to hydration (~1000ms)
* +40KB bundle size

Questions

* Is Server Components + Suspense the recommended pattern for dashboards in Next.js 16?
* Should I optimize for first load (Server Components) or navigation speed (TanStack Query)?
* Is there a hybrid approach where I use Server Components for initial data and TanStack Query for background updates?
* Are there Next.js 16 native caching features I'm missing that could help?

Context

* Data changes infrequently (user's tenants/payments, not real-time)
* Users don't rapidly navigate away/back from dashboard
* Already using Edge API routes with Cache-Control: private, s-maxage=30

Would love to hear what the recommended pattern is for this use case

2 Replies

Standard Chinchilla
Server Components are terrible for data fetching.

They completely fall apart once you try to handle errors or retries at a component level.

You can’t recover from errors atomically — error boundaries only catch the error, but you can’t remount a failed Server Component.

That means error recovery has to happen at the page level(error.tsx), not inside the component that fetched the data.

On top of that, if you use 'use-cache' with a short expiration(cacheLife({expire:3})), development becomes painful: Next.js recompiles every route on navigation, so you constantly hit rebuilds and blocked transitions.

And finally, there’s the artificial component separation. Since the error fallback must be a client component, you can’t colocate everything (data view, loading skeleton, and error state) in a single file. You end up splitting things just because of 'use client' boundaries — not because it makes sense architecturally.
And don’t even think about using Server Functions.

If you throw a custom error there, you’ll see your message in development — but in production, Next.js replaces it with a generic one.

They claim it’s “for security reasons,” apparently assuming you’re some careless kid who’d leak company secrets through error messages.

It’s a terrible developer experience. You lose all visibility into what failed, and it completely kills proper error handling in production