<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ashik Ahmed</title>
    <description>The latest articles on DEV Community by Ashik Ahmed (@ahmedashik).</description>
    <link>https://dev.to/ahmedashik</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3865151%2Ff5aa1cf4-e125-452e-a463-fe36c407d359.jpeg</url>
      <title>DEV Community: Ashik Ahmed</title>
      <link>https://dev.to/ahmedashik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmedashik"/>
    <language>en</language>
    <item>
      <title>TanStack Query 101: Stop Struggling with Data Fetching in Next.js</title>
      <dc:creator>Ashik Ahmed</dc:creator>
      <pubDate>Tue, 07 Apr 2026 18:28:12 +0000</pubDate>
      <link>https://dev.to/ahmedashik/tanstack-query-101-stop-struggling-with-data-fetching-in-nextjs-5edp</link>
      <guid>https://dev.to/ahmedashik/tanstack-query-101-stop-struggling-with-data-fetching-in-nextjs-5edp</guid>
      <description>&lt;p&gt;In modern web applications, managing server state (data from APIs) is often more complex than it should be. Handling loading states, caching, refetching, and errors manually quickly becomes messy. This is where TanStack Query (formerly React Query) shines. When combined with Next.js, it creates a powerful stack for building fast, scalable, and maintainable applications.&lt;/p&gt;

&lt;p&gt;In this guide, you’ll learn:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What TanStack Query is&lt;/li&gt;
&lt;li&gt;Why it pairs perfectly with Next.js&lt;/li&gt;
&lt;li&gt;How to set it up&lt;/li&gt;
&lt;li&gt;How to fetch, cache, and prefetch data&lt;/li&gt;
&lt;li&gt;Real-world patterns you’ll actually use&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is TanStack Query?
&lt;/h2&gt;

&lt;p&gt;TanStack Query is a powerful asynchronous state management library designed specifically for handling server state.&lt;br&gt;
Unlike traditional state management tools, it focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;li&gt;Synchronizing&lt;/li&gt;
&lt;li&gt;Updating server data efficiently&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  ✨ Key Features
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Data Fetching → Simplifies API calls&lt;/li&gt;
&lt;li&gt;Caching → Avoid unnecessary requests&lt;/li&gt;
&lt;li&gt;Background Refetching → Keeps data fresh&lt;/li&gt;
&lt;li&gt;Error Handling &amp;amp; Retries → Built-in resilience&lt;/li&gt;
&lt;li&gt;DevTools → Debug queries visually&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short:&lt;br&gt;
It removes the need for messy useEffect + useState logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use TanStack Query with Next.js?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Next.js supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSR (Server-Side Rendering)&lt;/li&gt;
&lt;li&gt;SSG (Static Site Generation)&lt;/li&gt;
&lt;li&gt;CSR (Client-Side Rendering)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TanStack Query complements this perfectly.&lt;/p&gt;

&lt;p&gt;** Benefits**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more manual data fetching logic&lt;/li&gt;
&lt;li&gt;Automatic caching = better performance&lt;/li&gt;
&lt;li&gt;Works across SSR, SSG, CSR seamlessly&lt;/li&gt;
&lt;li&gt;Cleaner and more maintainable code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  🛠️ Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create a Next.js App
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest my-tanstack-query-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-tanstack-query-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Install Dependencies
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @tanstack/react-query @tanstack/react-query-devtools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Create a Query Provider&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;TanStack Query requires a QueryClient to manage caching and queries.&lt;/p&gt;

&lt;p&gt;📁 providers.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { useState } from "react";

export default function Providers({ children }) {
  // Prevent recreating client on every render
  const [queryClient] = useState(() =&amp;gt; new QueryClient());

  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      {children}
      &amp;lt;ReactQueryDevtools initialIsOpen={false} /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  );
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Wrap Your Application
📁 app/layout.js
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`import Providers from "../providers";

export default function RootLayout({ children }) {
  return (
    &amp;lt;html lang="en"&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;Providers&amp;gt;{children}&amp;lt;/Providers&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🔥 Fetching Data with useQuery&lt;/p&gt;

&lt;p&gt;Now let’s fetch real data.&lt;/p&gt;

&lt;p&gt;📁 app/page.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`"use client";

import { useQuery } from "@tanstack/react-query";

async function fetchPosts() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");

  if (!res.ok) {
    throw new Error("Failed to fetch posts");
  }

  return res.json();
}

export default function Home() {
  const { data, isLoading, isError, error } = useQuery({
    queryKey: ["posts"],
    queryFn: fetchPosts,
  });

  if (isLoading) return &amp;lt;p&amp;gt;Loading posts...&amp;lt;/p&amp;gt;;
  if (isError) return &amp;lt;p&amp;gt;Error: {error.message}&amp;lt;/p&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Posts&amp;lt;/h1&amp;gt;
      &amp;lt;ul&amp;gt;
        {data.map((post) =&amp;gt; (
          &amp;lt;li key={post.id}&amp;gt;{post.title}&amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧩 &lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Core Concepts
&lt;/h2&gt;

&lt;p&gt;🔑 &lt;strong&gt;Query Keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Unique identifiers for caching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;posts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"posts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specific&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;post&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing the key = different cache entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Query Function
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;An async function that returns data:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`const fetchPosts = async () =&amp;gt; { ... }`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  📊 Query States
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;isLoading → First fetch&lt;/li&gt;
&lt;li&gt;isError → If request fails&lt;/li&gt;
&lt;li&gt;error → Error details&lt;/li&gt;
&lt;li&gt;data → Final result&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ⚡ Server-Side Prefetching (SSR)
&lt;/h2&gt;

&lt;p&gt;One of the most powerful features when using Next.js.&lt;br&gt;
You can fetch data on the server and hydrate it on the client.&lt;/p&gt;

&lt;p&gt;📁 app/page.js (Server Component)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`import {
  dehydrate,
  HydrationBoundary,
  QueryClient,
} from "@tanstack/react-query";
import Posts from "../components/Posts";

async function fetchPosts() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");

  if (!res.ok) {
    throw new Error("Failed to fetch posts");
  }

  return res.json();
}

export default async function Home() {
  const queryClient = new QueryClient();

  await queryClient.prefetchQuery({
    queryKey: ["posts"],
    queryFn: fetchPosts,
  });

  return (
    &amp;lt;HydrationBoundary state={dehydrate(queryClient)}&amp;gt;
      &amp;lt;Posts /&amp;gt;
    &amp;lt;/HydrationBoundary&amp;gt;
  );
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📁 components/Posts.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`"use client";

import { useQuery } from "@tanstack/react-query";

export default function Posts() {
  const { data } = useQuery({
    queryKey: ["posts"],
  });

  return (
    &amp;lt;ul&amp;gt;
      {data.map((post) =&amp;gt; (
        &amp;lt;li key={post.id}&amp;gt;{post.title}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;** Result:**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No loading state on first render&lt;/li&gt;
&lt;li&gt;Faster performance&lt;/li&gt;
&lt;li&gt;Better SEO&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🛠️ DevTools
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The DevTools help you:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect queries&lt;/li&gt;
&lt;li&gt;See cache behavior&lt;/li&gt;
&lt;li&gt;Debug easily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Already added in providers.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`&amp;lt;ReactQueryDevtools initialIsOpen={false} /&amp;gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔁 Common Real-World Patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;📄 Pagination&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`const { data } = useQuery({
  queryKey: ["posts", page],
  queryFn: () =&amp;gt; fetchPosts(page),
});`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;♾️ Infinite Scrolling&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="s2"&gt;`import { useInfiniteQuery } from "@tanstack/react-query";

const { data, fetchNextPage } = useInfiniteQuery({
  queryKey: ["posts"],
  queryFn: ({ pageParam = 1 }) =&amp;gt; fetchPosts(pageParam),
  getNextPageParam: (lastPage) =&amp;gt; lastPage.nextPage,
});`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;TanStack Query is a true game-changer when it comes to managing server state in modern applications. Instead of manually handling loading states, caching logic, and refetching strategies, it provides a clean and declarative way to work with asynchronous data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With features like:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smart caching&lt;/strong&gt; that reduces unnecessary network requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background update&lt;/strong&gt;s to keep your UI always in sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in support for SSR and hydration&lt;/strong&gt; with Next.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…it significantly simplifies your codebase while also improving performance and user experience.&lt;/p&gt;

&lt;p&gt;As your application grows, these benefits become even more noticeable. What starts as a small improvement quickly turns into a major advantage in terms of scalability, maintainability, and developer productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;Now that you’ve learned the fundamentals, the next step is to explore more advanced and real-world features of TanStack Query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handling mutations (POST, PUT, DELETE)&lt;/li&gt;
&lt;li&gt;Implementing optimistic UI updates for better user experience&lt;/li&gt;
&lt;li&gt;Managing cache with query invalidation strategies&lt;/li&gt;
&lt;li&gt;Structuring queries in larger applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These concepts will help you move from basic usage to building production-ready applications.&lt;/p&gt;

&lt;p&gt;If you're learning in public like this, you're already ahead of many developers. Consistency is the key — keep building, keep sharing, and keep improving.&lt;/p&gt;

&lt;p&gt;More in-depth topics are coming in the next part. &lt;strong&gt;Keep Learning, Keep Coding, and Never Stop Dreaming&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
