Not every request belongs on the server. Client-side fetching suits live dashboards, infinite scroll, user-specific widgets after hydration, and third-party APIs that cannot run on your server.
useEffect + fetch (know the trade-offs)
'use client';
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/metrics').then(r => r.json()).then(setData);
}, []);
Users see loading states after paint—acceptable for private app shells, weak for SEO-critical content.
Libraries
- SWR / TanStack Query — caching, deduping, refetch on focus
- Server prefetch + dehydrate — hydrate client cache from server data when you need both
Decision guide
- Can this data render on the server for the first paint? If yes, prefer Server Components.
- Does it change every few seconds? Client polling or websockets.
- Is it behind user interaction? Client fetch on demand.
Self-check
- Why is client fetch weaker for public blog posts?
- What does TanStack Query add over raw useEffect?
Challenge
Client fetch
- Click Fetch and confirm items appear after loading.
- Handle the loading and empty states in the UI.
Done when: list renders after simulated client fetch completes.
Tip: SEO-critical content belongs in Server Components—reserve client fetch for live dashboards and post-hydration widgets.