From Static to Dynamic: Using TanStack Query for Real-Time Data in Next.js
Mudasir FayazWeb apps today are all about real-time experiences. Whether it’s dashboards, analytics, or notifications — users expect live updates without page reloads. Traditionally, handling real-time data in React or Next.js required complex state management and manual API refresh logic.
That’s where TanStack Query (formerly React Query) changes everything. It simplifies fetching, caching, and syncing server state — making your app blazing fast, resilient, and dynamic.
Why TanStack Query?
TanStack Query is not just a data-fetching library — it’s a completedata synchronization framework. It automatically caches, refetches, and updates UI as data changes, letting you focus on building features, not managing request states.
“TanStack Query turns API fetching into a declarative experience — no manual loading states or refetch timers required.”
Installing TanStack Query
Let’s start by setting up a fresh Next.js project and installing the required dependencies:
npx create-next-app@latest tanstack-demo
cd tanstack-demo
npm install @tanstack/react-queryOnce installed, we’ll configure the QueryClientProvider in our app to enable caching and global query management.
Step 1: Setup Query Client in Next.js
// app/providers/QueryProvider.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactNode, useState } from 'react';
export default function QueryProvider({ children }: { children: ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}Then, wrap your root layout with QueryProvider so it’s available across your app:
// app/layout.tsx
import QueryProvider from './providers/QueryProvider';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<QueryProvider>{children}</QueryProvider>
</body>
</html>
);
}Step 2: Fetching Data with TanStack Query
Let’s fetch some live API data (for example, posts from JSONPlaceholder) and render it dynamically:
// app/page.tsx
'use client';
import { useQuery } from '@tanstack/react-query';
async function fetchPosts() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
return res.json();
}
export default function HomePage() {
const { data, isLoading, isError, refetch } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
refetchInterval: 10000, // auto-refresh every 10 seconds
});
if (isLoading) return <p>Loading posts...</p>;
if (isError) return <p>Failed to load posts.</p>;
return (
<main className="p-6">
<h1 className="text-2xl font-bold mb-4">Latest Posts</h1>
<button
onClick={() => refetch()}
className="px-4 py-2 bg-blue-600 text-white rounded-md mb-4 hover:bg-blue-700 transition"
>
Refresh Now
</button>
<ul className="space-y-4">
{data.slice(0, 5).map((post: any) => (
<li key={post.id} className="p-4 bg-gray-100 rounded-lg shadow">
<h2 className="text-lg font-semibold">{post.title}</h2>
<p className="text-gray-600">{post.body}</p>
</li>
))}
</ul>
</main>
);
}In just a few lines, you have a dynamic, auto-updating list that fetches data, caches results, and refetches periodically — no manual effect hooks or state variables needed.
Step 3: Real-Time Behavior
You can configure refetchInterval for live updates, or use event-based refetching (like WebSocket triggers or button clicks). TanStack Query’s smart caching ensures minimal network calls and instant UI updates.
Bonus: Optimistic Updates
If your app lets users update data, TanStack Query also supportsoptimistic updates — updating the UI instantly before the server responds. This keeps the app feeling smooth and responsive.
const mutation = useMutation({
mutationFn: updatePost,
onMutate: async (newPost) => {
await queryClient.cancelQueries(['posts']);
const previousPosts = queryClient.getQueryData(['posts']);
queryClient.setQueryData(['posts'], (old: any) => [...old, newPost]);
return { previousPosts };
},
onError: (err, newPost, context) => {
queryClient.setQueryData(['posts'], context.previousPosts);
},
onSettled: () => {
queryClient.invalidateQueries(['posts']);
},
});This ensures your UI never feels laggy — users see updates immediately, even before the server confirms them.
Conclusion
TanStack Query revolutionizes how data is handled in Next.js. It replaces manual fetching logic with declarative, reactive state — helping developers build real-time apps with cleaner code and fewer bugs. If your Next.js app still relies onuseEffect() and local state for API calls, it’s time to gofrom static to dynamic with TanStack Query.