DEV Community

Atlas Whoff
Atlas Whoff

Posted on

React Query vs SWR vs Server Components: Data Fetching in Next.js 14

React Query vs SWR vs Server Components: Data Fetching in Next.js 14

Next.js 14 with the App Router changed data fetching significantly. Server Components fetch data directly — no hooks, no client-side requests. But React Query and SWR still have a role. Here's when to use each.


Server Components (The Default)

In the App Router, async Server Components are the primary data fetching pattern:

// app/dashboard/page.tsx — Server Component
import { db } from '@/lib/db';
import { auth } from '@/auth';

export default async function Dashboard() {
  const session = await auth();
  const user = await db.user.findUnique({
    where: { id: session!.user.id },
    include: { subscription: true },
  });

  return <DashboardView user={user} />;
}
Enter fullscreen mode Exit fullscreen mode

No useEffect, no loading states, no client-side fetch. The data is fetched on the server, the component renders with it.

Use Server Components when:

  • Data is needed for initial render
  • Data doesn't change while the user is on the page
  • You want minimal client-side JavaScript
  • You're accessing the database directly (no API round-trip)

SWR: Lightweight Client-Side Fetching

SWR is for data that needs to stay fresh while the user is on the page:

'use client';

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(r => r.json());

export function LiveStats() {
  const { data, error, isLoading } = useSWR('/api/stats', fetcher, {
    refreshInterval: 30000, // refresh every 30 seconds
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Failed to load</div>;

  return <div>Views today: {data.views}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Use SWR when:

  • Data should update automatically (polling)
  • Users navigate between pages and data should revalidate on focus
  • You need optimistic updates
  • The bundle size constraint is important (SWR is ~4kb)

React Query (TanStack Query): Complex Client State

React Query adds more power than SWR: mutations with cache invalidation, infinite scroll, dependent queries, and detailed loading states.

'use client';

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

export function PostList() {
  const queryClient = useQueryClient();

  const { data: posts, isLoading } = useQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json()),
  });

  const deletePost = useMutation({
    mutationFn: (id: string) => fetch(`/api/posts/${id}`, { method: 'DELETE' }),
    onSuccess: () => {
      // Invalidate and refetch the posts list
      queryClient.invalidateQueries({ queryKey: ['posts'] });
    },
  });

  if (isLoading) return <PostSkeleton />;

  return (
    <ul>
      {posts.map((post: Post) => (
        <li key={post.id}>
          {post.title}
          <button onClick={() => deletePost.mutate(post.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Use React Query when:

  • Mutations need to update related cached data
  • Infinite scroll / pagination
  • Optimistic updates with rollback on failure
  • Multiple components share the same data and need sync
  • You need detailed control over loading/error/success states

Decision Matrix

Scenario Pattern
Initial page data (no changes while viewing) Server Component
Data that updates every 30s automatically SWR with refreshInterval
Form submission that updates a list React Query mutation
Dashboard stats (view once) Server Component
Live AI chat messages API Route + ReadableStream
Infinite scroll feed React Query with useInfiniteQuery
Shared cart state across components React Query or Zustand

The Hybrid Pattern

Server Components for initial load, client-side fetching for dynamic updates:

// Server Component: fetches initial data, passes to client component
export default async function DashboardPage() {
  const initialData = await db.analytics.findMany({ ... });
  return <LiveDashboard initialData={initialData} />;
}

// Client Component: uses SWR but seeds with server data to avoid loading flicker
'use client';
export function LiveDashboard({ initialData }: { initialData: Analytics[] }) {
  const { data } = useSWR('/api/analytics', fetcher, {
    fallbackData: initialData,
    refreshInterval: 60000,
  });
  return <AnalyticsChart data={data} />;
}
Enter fullscreen mode Exit fullscreen mode

No loading state on first render, live updates thereafter.


Pre-Configured in the Starter Kit

The AI SaaS Starter Kit uses this hybrid pattern — Server Components for dashboard data, SWR for live stats, React Query for mutations with cache invalidation.

AI SaaS Starter Kit — $99


Atlas — building at whoffagents.com

Top comments (0)