How to Fetch Data Without `useEffect` in Next.js

In the App Router you rarely need useEffect to load data. Learn how async Server Components fetch on the server, and what to do when a client component needs data.

7 min read

To fetch data without useEffect in Next.js, move the request into a Server Component. A Server Component can be async and await the data directly, so the page fetches on the server and renders the finished list before any client code runs.

App.tsxApp.tsx
// app/posts/page.tsx
type Post = { id: number; title: string };
 
export default async function PostsPage() {
  const res = await fetch("https://api.example.com/posts");
  const posts: Post[] = await res.json();
  return <ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}

The list arrives with data already inside. There is no loading flag, no effect, and no second render, because the fetch finished before the response was sent.

Why useEffect was the old pattern

In a client-only React app there is no server step, so data must be fetched after the component mounts. useEffect runs after paint, which forces a loading state, a first render with empty data, and a second render once the data arrives.

Server Components move that fetch earlier, so the work happens before the user sees anything. The browser receives rendered HTML instead of a component that still needs to load its own data.

When you still need a client component

If the list needs interactivity, mark a client component and keep the fetch in its server parent, then pass the data down as props.

App.tsxApp.tsx
// app/posts/page.tsx
import PostsClient from "./posts-client";
 
type Post = { id: number; title: string };
 
export default async function PostsPage() {
  const res = await fetch("https://api.example.com/posts");
  const posts: Post[] = await res.json();
  return <PostsClient posts={posts} />;
}

The server fetches the posts and hands them to the client component as serializable props. The boundary between these two kinds of components is covered in server components vs client components.

App.tsxApp.tsx
// app/posts/posts-client.tsx
"use client";
 
type Post = { id: number; title: string };
 
export default function PostsClient({ posts }: { posts: Post[] }) {
  return <ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}

The client component still gets data without an effect. It receives the posts as props, and the server is the only place that ever touches the network.

There is a second option when you do not want the page to wait: instead of awaiting the promise in the server parent, pass the unresolved promise down and read it in the client component with React's use hook, inside a Suspense boundary. The page shell renders immediately and the data streams in when it resolves, still with no effect anywhere.

When the data must be fetched on the client

If the fetch depends on user interaction or needs background revalidation, use a client data library. Data fetching with SWR adds caching, retries, and revalidation on top of a fetch inside a client component, and TanStack Query solves the same problem with a slightly different model.

Either library lets a client component load its own data without useEffect, because the library owns the fetching, caching, and re-rendering for you. They also handle the parts you would otherwise hand-code, such as deduplicating identical requests, refetching on focus, and keeping stale data on screen while a background refresh runs. The tradeoff is an extra dependency and an extra cache layer to reason about on the client.

Common mistake

Using useEffect to duplicate a fetch that a parent Server Component already made. The page loads the data on the server, then the client fetches it again, producing a visible flicker and a wasted request. Keep the fetch in one place, and pass the result down.

Rune AI

Rune AI

Key Insights

  • An async Server Component can await data directly, with no effect.
  • The fetch finishes before the response is sent, so there is no empty first paint.
  • Client Components cannot be async, so pass server data down as props.
  • Use SWR or TanStack Query when the fetch must happen on the client.
  • Do not duplicate a server fetch with a client useEffect.
RunePowered by Rune AI

Frequently Asked Questions

Do I ever need useEffect for data fetching in Next.js?

Rarely. Server Components fetch without it. Use it, or a data library, only when the fetch must happen in a Client Component after the page loads.

Can a Client Component be async?

No. A component with the use client directive cannot be an async function, which is why client data usually comes from props or a library.

What if the data changes after the page loads?

Use SWR or TanStack Query to refetch, revalidate, and cache on the client, or hydrate their cache with server-fetched data.

Conclusion

The App Router replaces the useEffect fetch with an async Server Component that loads data before the response is sent. Keep the fetch on the server and pass data down, and reach for a client data library only when the fetch truly belongs in the browser.