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/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/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/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
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.
Frequently Asked Questions
Do I ever need useEffect for data fetching in Next.js?
Can a Client Component be async?
What if the data changes after the page loads?
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.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.